Skill v1.0.1
currentAutomated scan100/100+2 new
version: "1.0.1" description: "X.com (Twitter) platform operations playbook for agent-browser. Covers browse, engagement, content creation, social graph, profile, navigation, and lists operations with tested step-by-step agent-browser commands." allowed-tools:
- Bash
user-invocable: true
X.com (Twitter) — Agent Browser Playbook
Platform-specific operation sequences for agent-browser on x.com. Each operation documents: preconditions, step-by-step agent-browser commands, verification method, and known pitfalls.
Status: All sections (1-7) tested and documented. Tested with: agent-browser 0.24.0, Chrome CDP on port 9222, account @mashijiann. Last updated: 2026-05-06
Table of Contents
- 1. Browse & Read
- 1.1 Open Home Timeline
- 1.2 Open a Specific Tweet by URL
- 1.3 Search Tweets by Keyword
- 1.4 View a User Profile
- 1.5 Read Tweet Thread / Replies
- 1.6 Read Notifications
- 1.7 Read DMs (Direct Messages)
- 2. Engagement — React to Content
- 2.1 Like a Tweet
- 2.2 Unlike a Tweet
- 2.3 Retweet (Repost)
- 2.4 Undo Retweet
- 2.5 Bookmark a Tweet
- 2.6 Remove Bookmark
- 3. Content Creation
- 3.1 Post a New Tweet (Text Only)
- 3.2 Post a Tweet with Image
- 3.3 Reply to a Tweet
- 3.4 Quote Tweet
- 3.5 Delete Own Tweet
- 4. Social Graph
- 4.1 Follow a User
- 4.2 Unfollow a User
- 4.3 View Followers List
- 4.4 View Following List
- 5. Profile Management
- 5.1 View Own Profile
- 5.2 Edit Bio / Display Name
- 5.3 Pin a Tweet
- 6. Navigation & Utility
- 6.1 Switch Between Tabs (For You / Following)
- 6.2 Scroll to Load More Tweets
- 6.3 Handle Login Popups / Modals
- 6.4 Handle Cookie Consent Banner
- 6.5 Dismiss "Turn on notifications" Prompt
- 7. Lists
- 7.1 View Own Lists
- 7.2 View a List Timeline
- 7.3 Browse List Members
- 7.4 Create a New List
- 7.5 Add Member to List
- 7.6 Remove Member from List
- 7.7 Delete a List
Device targeting: Operations run against desktop Chrome by default. Tooperate the mobile web surface, setDEVICE_PROFILE=ios|androidin.envorprefix agent-browser commands with-p ios|android. When logging an action,pass--device <target>for provenance (it does not change dedup — a like isaccount-level).
1. Browse & Read
1.1 Open Home Timeline
Goal: Navigate to the home timeline and confirm it loaded.
Preconditions: Logged in (Chrome CDP session with cookies).
Steps:
agent-browser open https://x.com/home
Verification:
# Title should contain "Home / X"agent-browser get title# URL should be https://x.com/homeagent-browser get url# Snapshot should show region "Your Home Timeline" and tab "For you"agent-browser snapshot -i -c -s '[role="region"]'
Key Elements:
- Navigation sidebar:
navigation "Primary"— contains links: Home, Search and explore, Notifications, Direct Messages, Grok, Premium, Bookmarks, Profile - Timeline tabs:
tab "For you"(default selected),tab "Following", possibly custom tabs - Compose area:
textbox "Post text"— inline compose on home page - Account button:
button "Account menu"— shows logged-in username - Timeline region:
region "Your Home Timeline"— contains tweetarticleelements
Known Issues:
- None
1.2 Open a Specific Tweet by URL
Goal: Navigate directly to a tweet and read its content.
Preconditions: Have the tweet URL (e.g. https://x.com/user/status/123456).
Steps:
agent-browser open "https://x.com/<username>/status/<tweet_id>"
Verification:
# Title should contain the author's name and tweet content previewagent-browser get title# URL should match the tweet URLagent-browser get url# Snapshot should show the tweet article with full text and engagement buttonsagent-browser snapshot -i -c -s 'article'
Key Elements (tweet detail page):
- Tweet article:
article— contains full tweet text, author info, engagement stats - Author links:
link "<DisplayName> Verified account",link "@<username>" - Timestamp:
link "<time> · <date>"(e.g."2:30 PM · May 5, 2026") - View count:
link "<N> Views" - Engagement buttons:
button "N Reply. Reply",button "N reposts. Repost",button "N Likes. Like",button "N Bookmarks. Bookmark",button "Share post" - Subscribe button:
button "Subscribe to @<username>"
Known Issues:
- None
1.3 Search Tweets by Keyword
Goal: Search for tweets matching a keyword or query, read the results.
Preconditions: Logged in.
Steps:
# For "Top" results (default):agent-browser open "https://x.com/search?q=<url_encoded_query>&src=typed_query"# For "Latest" results:agent-browser open "https://x.com/search?q=<url_encoded_query>&src=typed_query&f=live"# For "People" results:agent-browser open "https://x.com/search?q=<url_encoded_query>&src=typed_query&f=user"# For "Media" results:agent-browser open "https://x.com/search?q=<url_encoded_query>&src=typed_query&f=image"
Verification:
# Title should contain "<query> - Search / X"agent-browser get title# Check which tab is activeagent-browser snapshot -i -c -s '[role="tablist"]'# Read search result tweetsagent-browser snapshot -i -c -s 'article'
Key Elements:
- Search tabs:
tab "Top",tab "Latest",tab "People",tab "Media",tab "Lists" - The
fURL parameter controls tab: omitted = Top,live= Latest,user= People,image= Media - Back button:
button "Back"— returns to previous page - Each result is an
articleelement identical in structure to timeline tweets
Known Issues:
- URL-encode the query string (spaces →
%20, etc.) - Search results may show "login wall" for non-authenticated sessions
- X advanced search operators work:
from:user,since:2026-01-01,min_faves:100, etc.
1.4 View a User Profile
Goal: Navigate to a user's profile and read their bio and multiple recent tweets.
Preconditions: Have the username or profile URL.
Steps:
# 1. Navigate to profileagent-browser open "https://x.com/<username>"# 2. Initial snapshot — typically only shows the PINNED tweet (if any)agent-browser snapshot -i -c -s 'article'# WARNING: The first article is usually the pinned tweet, NOT the latest post.# You MUST scroll down to see actual recent posts.# 3. Scroll down to load recent posts (past the pinned tweet and profile header)agent-browser scroll down 3000agent-browser wait 2000agent-browser snapshot -i -c -s 'article'# Now you should see the first recent (non-pinned) post# 4. Continue scrolling to load more posts — repeat as neededagent-browser scroll down 3000agent-browser wait 2000agent-browser snapshot -i -c -s 'article'
Verification:
# Title should contain "<DisplayName> (@<username>) / X"agent-browser get title# URL should be https://x.com/<username>agent-browser get url
Key Pattern — Browsing Multiple Posts:
# To browse N posts from a user's profile, use this scroll loop:# Each scroll+wait cycle reveals ~1 new post due to virtualized listfor i in $(seq 1 N); doagent-browser scroll down 3000sleep 2 # or: agent-browser wait 2000agent-browser snapshot -i -c -s 'article'# Process the visible article(s)done
Known Issues:
- CRITICAL: The initial page view typically only shows the pinned tweet. You MUST scroll down to see recent posts. Agents often mistakenly think the pinned tweet is the only content.
- X uses a virtualized list — only 1-2 articles in the viewport are in the DOM at any time. Scrolled-past articles are removed from DOM.
- Scroll amount of 3000px + 2 second wait reliably loads next batch
- Pinned tweet has text
"Pinned"at the beginning of its article description — use this to identify and skip it - Profile has tabs: Posts, Replies, Highlights, Media, Likes — default is "Posts" which excludes replies. Use
https://x.com/<username>/with_repliesfor posts + replies.
1.5 Read Tweet Thread / Replies
Goal: Open a tweet and read the full thread and replies below it.
Preconditions: Have the tweet URL.
Steps:
# 1. Open the tweetagent-browser open "https://x.com/<username>/status/<tweet_id>"# 2. Scroll down to load repliesagent-browser scroll down 800# 3. Read replies (they appear as additional article elements below the main tweet)agent-browser snapshot -i -c -s 'article'
Verification:
# Multiple article elements should be present — first is the main tweet, rest are repliesagent-browser snapshot -i -c -s 'article'
Known Issues:
- Replies may be paginated — look for
button "Show more replies"and click it - "Show probably spam" section may appear — skip it
- Thread context (parent tweets) appears above the main tweet
- Quote tweet click trap: If a reply or timeline entry is a quote tweet, clicking the article body navigates to the quoted original post instead of the quote tweet detail page. To navigate to the correct tweet, click the timestamp link instead. See section 3.4 Known Issues for full details.
1.6 Read Notifications
Goal: Open the notifications tab and read recent notifications.
Preconditions: Logged in.
Steps:
agent-browser open https://x.com/notifications
Verification:
# Title should contain "Notifications / X"agent-browser get title# URL should be https://x.com/notificationsagent-browser get url
Key Elements:
- Notification tabs: All, Verified, Mentions
- Sidebar link shows unread count:
link "Notifications (N unread notifications)"
Known Issues:
- Notifications have sub-tabs: All, Verified, Mentions
1.7 Read DMs (Direct Messages)
Goal: Open DMs and read recent messages.
Preconditions: Logged in.
Steps:
# Note: /messages redirects to /i/chatagent-browser open https://x.com/messages
Verification:
# URL will be https://x.com/i/chat (auto-redirect)agent-browser get url
Known Issues:
- URL redirects from
/messagesto/i/chat - DM UI is a side panel or full page depending on viewport
- May require X Premium for some DM features
2. Engagement — React to Content
2.1 Like a Tweet
Goal: Like a specific tweet.
Preconditions: On a page where the tweet is visible (timeline, tweet detail, search results).
Steps:
# 1. Snapshot to find the Like button for the target tweetagent-browser snapshot -i -c -s 'article'# Look for: button "N Likes. Like" (not yet liked) inside the target article# 2. Click the Like buttonagent-browser click <like_button_ref>
Verification:
# Snapshot again — button text should change from "Like" to "Liked"agent-browser snapshot -i -c -s 'article'# Look for: button "N Likes. Liked"
Element Identification:
- Not liked:
button "N Likes. Like"— text ends with. Like - Already liked:
button "N Likes. Liked"— text ends with. Liked - The like count N increments by 1 after liking
Known Issues:
- Rate limiting: X may restrict rapid likes
- The button ref may change after state change (re-snapshot to get new ref)
2.2 Unlike a Tweet
Goal: Remove a like from a previously liked tweet.
Preconditions: Tweet is already liked.
Steps:
# 1. Snapshot to find the Liked buttonagent-browser snapshot -i -c -s 'article'# Look for: button "N Likes. Liked"# 2. Click the same button to unlikeagent-browser click <liked_button_ref>
Verification:
# Button text should revert from "Liked" to "Like"agent-browser snapshot -i -c -s 'article'# Look for: button "N Likes. Like"
Element Identification:
- Same button toggles between Like/Liked states
- Ref may change between states — always re-snapshot
Known Issues:
- None
2.3 Retweet (Repost)
Goal: Repost a tweet to own timeline.
Preconditions: On a page where the tweet is visible.
Steps:
# 1. Snapshot to find the Repost buttonagent-browser snapshot -i -c -s 'article'# Look for: button "N reposts. Repost" [expanded=false]# 2. Click Repost button — this opens a DROPDOWN MENU, not the action itselfagent-browser click <repost_button_ref># 3. Snapshot to see the dropdown menu itemsagent-browser snapshot -i -c -s '[role="menuitem"]'# Shows: menuitem "Repost" and menuitem "Quote"# 4. Click "Repost" menu item to execute the repostagent-browser click <repost_menuitem_ref>
Verification:
# Snapshot — button text should change from "Repost" to "Reposted"agent-browser snapshot -i -c -s 'article'
Element Identification:
- Repost button:
button "N reposts. Repost" [expanded=false] - Dropdown items:
menuitem "Repost"andmenuitem "Quote" - After repost: button becomes
button "N reposts. Reposted"
Known Issues:
- Two-step operation: clicking the button opens a dropdown, you must then click the menuitem
- To cancel/close the dropdown without acting:
agent-browser press Escape
2.4 Undo Retweet
Goal: Remove a repost.
Preconditions: Tweet is already retweeted.
Steps:
# 1. Snapshot to find the Reposted buttonagent-browser snapshot -i -c -s 'article'# Look for: button "N reposts. Reposted"# 2. Click — opens dropdown with "Undo repost" optionagent-browser click <reposted_button_ref># 3. Find and click "Undo repost" menu itemagent-browser snapshot -i -c -s '[role="menuitem"]'agent-browser click <undo_repost_menuitem_ref>
Verification:
# Button text should revert to "Repost"agent-browser snapshot -i -c -s 'article'
Element Identification:
- Already reposted:
button "N reposts. Reposted" - Dropdown item:
menuitem "Undo repost"
Known Issues:
- Same two-step dropdown pattern as Repost
2.5 Bookmark a Tweet
Goal: Bookmark a tweet for later reading.
Preconditions: On a page where the tweet is visible.
Steps:
# 1. Snapshot to find the Bookmark buttonagent-browser snapshot -i -c -s 'article'# Look for: button "Bookmark" (on timeline) or button "N Bookmarks. Bookmark" (on tweet detail)# 2. Click Bookmark button — single click, no dropdownagent-browser click <bookmark_button_ref>
Verification:
# Button text should change from "Bookmark" to "Bookmarked"agent-browser snapshot -i -c -s 'article'# Look for: button "Bookmarked" or button "N Bookmarks. Bookmarked"
Element Identification:
- Timeline view:
button "Bookmark"→button "Bookmarked" - Tweet detail view:
button "N Bookmarks. Bookmark"→button "N Bookmarks. Bookmarked" - Single click — no dropdown or confirmation needed
Known Issues:
- Bookmark button ref changes after state change (e.g. e137 → e139) — re-snapshot after clicking
2.6 Remove Bookmark
Goal: Remove a previously bookmarked tweet.
Preconditions: Tweet is already bookmarked.
Steps:
# 1. Snapshot to find the Bookmarked buttonagent-browser snapshot -i -c -s 'article'# Look for: button "Bookmarked"# 2. Click to remove bookmarkagent-browser click <bookmarked_button_ref>
Verification:
# Button text should revert from "Bookmarked" to "Bookmark"agent-browser snapshot -i -c -s 'article'
Element Identification:
- Same toggle pattern as Like: click "Bookmarked" button to un-bookmark
Known Issues:
- None
3. Content Creation
3.1 Post a New Tweet (Text Only)
Goal: Compose and publish a new tweet with text only.
Preconditions: Logged in, on home timeline (compose area visible).
Steps:
# 1. Navigate to home (compose area is inline on home page)agent-browser open https://x.com/home# 2. Snapshot to find the compose textboxagent-browser snapshot -i -c# Look for: textbox "Post text"# 3. Fill the compose area with tweet textagent-browser fill <textbox_ref> "Your tweet text here"# 4. Snapshot to confirm text is filled and Post button is enabledagent-browser snapshot -i -c# Look for: button "Post" (without [disabled])# 5. Click Post to sendagent-browser click <post_button_ref>
Verification:
# After posting, the compose area should be empty and the tweet should appear in timelineagent-browser snapshot -i -c -s 'article'# Look for your tweet as the first article with your username and "Now" timestamp
Element Identification:
- Compose textbox:
textbox "Post text"— this is a contenteditable div, NOT a standard<input> - Post button (empty):
button "Post" [disabled]— disabled when no text - Post button (ready):
button "Post"— enabled when text is present - After posting: tweet appears as
articlewith"@<your_username> Now"in timeline
Known Issues:
- `fill` works,
typeandkeyboard typedo NOT work on this contenteditable div - CRITICAL — beforeunload trap: Once you
filltext into compose, X.com registers abeforeunloadevent. If you navigate away (open, reload) WITHOUT posting or clearing, Chrome shows a "Leave site?" dialog that freezes agent-browser completely. The Playwright daemon cannot dismiss this dialog when connected via CDP. - Prevention: Always either post the tweet or clear text before navigating. To clear: use
agent-browser eval "const el = document.querySelector('[data-testid=\"tweetTextarea_0\"]'); if(el) { el.focus(); document.execCommand('selectAll'); document.execCommand('delete'); }" - Recovery: If the dialog appears, the only option is to manually click "Leave" in the Chrome UI, then kill and restart the agent-browser daemon (
pkill -9 -f agent-browser && agent-browser connect 9222) - 280 character limit
get valuereturns empty on this textbox — use snapshot to verify content
3.2 Post a Tweet with Image
Goal: Compose and publish a tweet with one or more images attached.
Preconditions: Logged in, on home timeline, image file available on local filesystem.
Steps:
# 1. Navigate to homeagent-browser open https://x.com/home# 2. Snapshot to find upload elementsagent-browser snapshot -i -c# Look for: button "Add photos or video" and button "Choose Files"# 3. Upload image file using the hidden file inputagent-browser upload <choose_files_button_ref> /path/to/image.png# 4. Wait for image to processagent-browser wait 2000# 5. Fill tweet text (optional — image-only tweets are allowed)agent-browser fill <textbox_ref> "Your tweet text here"# 6. Click Postagent-browser click <post_button_ref>
Element Identification:
- Add media button:
button "Add photos or video" - Hidden file input:
button "Choose Files"— use this withuploadcommand - GIF button:
button "Add a GIF" - Generate image:
button "Generate image"
Known Issues:
- Same beforeunload trap as 3.1 applies after filling text or uploading image
- Image processing takes time; wait before clicking Post
- Max 4 images per tweet
3.3 Reply to a Tweet
Goal: Post a reply to a specific tweet.
Preconditions: On the tweet detail page (navigate to the tweet URL first).
Steps:
# 1. Open the tweet detail pageagent-browser open "https://x.com/<username>/status/<tweet_id>"# 2. Snapshot to find the reply compose area (it's below the main tweet)agent-browser snapshot -i -c# Look for: textbox "Post text" inside a "Post your reply" container# Also look for: button "Reply" [disabled]# 3. Fill the reply textagent-browser fill <reply_textbox_ref> "Your reply text here"# 4. Snapshot to confirm Reply button is enabledagent-browser snapshot -i -c# Look for: button "Reply" (without [disabled])# 5. Click Reply to sendagent-browser click <reply_button_ref>
Verification:
# After replying, your reply should appear as a new article in the threadagent-browser snapshot -i -c -s 'article'# Look for article with your username and "Now" timestamp
Element Identification:
- Reply compose container:
generic "Post your replyReplyEveryone can reply" - Reply textbox:
textbox "Post text"— same element type as main compose - Reply button (empty):
button "Reply" [disabled] - Reply button (ready):
button "Reply"— enabled when text is present - Note: The main tweet also has
button "N Reply. Reply"(engagement stat) — this is NOT the submit button
Known Issues:
- Recommended approach: Navigate to tweet detail page first, then use the inline reply box. This is more reliable than clicking reply from timeline (which opens a modal).
- Same beforeunload trap as 3.1 — always post or clear before navigating away
fillworks on the reply textbox (same contenteditable div behavior)- CRITICAL — Quote tweet navigation trap: When browsing a timeline and you see a quote tweet (a tweet that embeds another tweet), clicking the article body navigates to the quoted (embedded) original post, NOT the quote tweet itself. This means if you want to reply to user A's quote tweet of user B, clicking the article takes you to user B's original post instead. See section 3.4 Known Issues for the correct approach.
3.4 Quote Tweet
Goal: Quote-retweet a tweet with added commentary.
Preconditions: On a page where the tweet is visible (timeline, search results, tweet detail).
Steps:
# 1. Snapshot to find the Repost button on the target tweetagent-browser snapshot -i -c -s 'article'# Look for: button "N reposts. Repost" [expanded=false]# 2. Click Repost button to open dropdownagent-browser click <repost_button_ref># 3. Snapshot to find the Quote menu itemagent-browser snapshot -i -c -s '[role="menu"]'# Shows: menuitem "Repost" and menuitem "Quote"# 4. Click "Quote" menu item — opens a compose modalagent-browser click <quote_menuitem_ref># 5. Snapshot the compose modal — find textbox and Post buttonagent-browser snapshot -i -c# Look for: textbox "Post text" and button "Post"# The modal also shows a preview of the quoted tweet# 6. Fill the quote textagent-browser fill <textbox_ref> "Your commentary here"# 7. Click Post to sendagent-browser click <post_button_ref>
Verification:
# After posting, the quote tweet should appear in timeline with your text + embedded quoted tweetagent-browser snapshot -i -c -s 'article'# Look for article containing your text + "Quote" + original author's content
Element Identification:
- Repost dropdown:
menuitem "Repost"andmenuitem "Quote" - Quote compose modal: contains
textbox "Post text"andbutton "Post"— same structure as main compose - The quoted tweet preview appears as embedded content below the textbox
Known Issues:
- Two-step process: Repost button → dropdown → Quote menuitem → compose modal
- Same beforeunload trap as 3.1 in the compose modal
- To cancel: press Escape to close the modal (but clear text first if you've filled any)
- CRITICAL — Navigating to a quote tweet from timeline: When a quote tweet appears in a timeline or search results, the article body contains an embedded
linkelement pointing to the quoted original post. Clicking the article's main clickable area (genericelement) navigates to the quoted original post's URL — NOT the quote tweet's own URL. This is because the embedded quoted content is wrapped in alinkthat captures click events. - Wrong: Clicking the article body → navigates to
https://x.com/<quoted_user>/status/<quoted_id>(the original post) - Correct — Option 1: Click the quote tweet's timestamp link (e.g.,
link "Apr 24",link "May 5") → navigates tohttps://x.com/<quoting_user>/status/<quote_id>(the quote tweet itself) - Correct — Option 2: Click the Reply button (
button "N Reply. Reply") directly from the timeline → opens reply compose targeting the quote tweet's author (no navigation needed) - Correct — Option 3: If you already know the quote tweet's URL, navigate directly:
agent-browser open "https://x.com/<quoting_user>/status/<quote_id>"
3.5 Delete Own Tweet
Goal: Delete a tweet posted by the agent's account.
Preconditions: Own tweet is visible (on profile, timeline, or search results).
Steps:
# 1. Find the tweet — using search is the most reliable methodagent-browser open "https://x.com/search?q=from%3A<username>%20<keyword>&src=typed_query&f=live"# 2. Snapshot to find the More button on the target tweetagent-browser snapshot -i -c -s 'article'# Look for: button "More" [expanded=false] inside the target article# 3. IMPORTANT: scroll the More button into view, then clickagent-browser scrollintoview <more_button_ref>agent-browser click <more_button_ref># 4. Snapshot to find the Delete menu itemagent-browser snapshot -i -c# Look for: menuitem "Delete"# 5. Click Deleteagent-browser click <delete_menuitem_ref># 6. Confirmation dialog appears — click the confirm Delete buttonagent-browser snapshot -i -c# Look for: heading "Delete post?" + button "Delete" + button "Cancel"agent-browser click <confirm_delete_button_ref>
Verification:
# The tweet should disappear from the page / search resultsagent-browser snapshot -i -c -s 'article'# The deleted tweet should no longer be present
Element Identification:
- More button:
button "More" [expanded=false]— the "..." menu on each tweet - Menu items (own tweet):
menuitem "Delete",menuitem "Edit post",menuitem "Pin to your profile",menuitem "Add/remove from Highlights",menuitem "Add/remove from Lists",menuitem "Change who can reply",menuitem "View post activity",menuitem "Embed post" - Confirmation dialog:
heading "Delete post?"+button "Delete"+button "Cancel"
Known Issues:
- Three-step operation: More button → Delete menuitem → Confirm dialog
- `scrollintoview` before clicking More is important — without it the click may not trigger the dropdown (likely the button is outside the viewport or behind another element)
- Finding own tweets: use search
from:<username> <keyword>withf=livetab for most reliable results. Profile page may have caching delay. - After deletion, you may be redirected to the previous page or see a "This post was deleted" placeholder
4. Social Graph
4.1 Follow a User
Goal: Follow a user from their profile page.
Preconditions: On the target user's profile page, not already following.
Steps:
# 1. Navigate to the user's profileagent-browser open "https://x.com/<username>"# 2. Snapshot to find the Follow buttonagent-browser snapshot -i -c# Look for: button "Follow @<username>"# 3. Click Followagent-browser click <follow_button_ref>
Verification:
# Button should change from "Follow @<username>" to "Unfollow @<username>"agent-browser snapshot -i -c# Look for: button "Unfollow @<username>" [expanded=false]
Element Identification:
- Not following:
button "Follow @<username>" - Already following:
button "Unfollow @<username>" [expanded=false] - Note: The button text includes the
@username— use this to distinguish from "Who to follow" sidebar buttons
Known Issues:
- Some accounts are protected; follow sends a request instead of immediately following
- Single click — no confirmation needed
4.2 Unfollow a User
Goal: Unfollow a user.
Preconditions: On the target user's profile page, already following.
Steps:
# 1. Snapshot to find the Unfollow buttonagent-browser snapshot -i -c# Look for: button "Unfollow @<username>" [expanded=false]# 2. Click Unfollow — this opens a DROPDOWN (not direct action)agent-browser click <unfollow_button_ref># 3. Snapshot to find the confirm menuitemagent-browser snapshot -i -c# Look for: menuitem "Unfollow @<username>"# 4. Click the menuitem to confirmagent-browser click <unfollow_menuitem_ref>
Verification:
# Button should revert to "Follow @<username>"agent-browser snapshot -i -c# Look for: button "Follow @<username>"
Element Identification:
- Unfollow button:
button "Unfollow @<username>" [expanded=false] - Dropdown confirm:
menuitem "Unfollow @<username>" - After unfollow: reverts to
button "Follow @<username>"
Known Issues:
- Two-step operation: click Unfollow button → dropdown appears → click menuitem to confirm
- To cancel: press Escape to close dropdown
4.3 View Followers List
Goal: View the followers list of a user.
Preconditions: Have the username.
Steps:
agent-browser open "https://x.com/<username>/followers"
Verification:
# Title should contain "People following <DisplayName> (@<username>) / X"agent-browser get title
Known Issues:
- List is virtualized — only visible items are in DOM. Scroll to load more.
4.4 View Following List
Goal: View the following list of a user.
Preconditions: Have the username.
Steps:
agent-browser open "https://x.com/<username>/following"
Verification:
# Title should contain "People followed by <DisplayName> (@<username>) / X"agent-browser get title
Known Issues:
- Same virtualization as followers list
5. Profile Management
5.1 View Own Profile
Goal: Navigate to the agent's own profile page.
Preconditions: Logged in, know own username.
Steps:
agent-browser open "https://x.com/<own_username>"
Verification:
# Title should contain "<DisplayName> (@<username>) / X"agent-browser get title
Known Issues:
- None
5.2 Edit Bio / Display Name
Goal: Update the agent account's bio or display name.
Preconditions: On own profile page.
Steps:
# 1. Navigate to own profileagent-browser open "https://x.com/<own_username>"# 2. Snapshot to find Edit profile linkagent-browser snapshot -i -c# Look for: link "Edit profile"# 3. Click Edit profile — opens a modalagent-browser click <edit_profile_ref># 4. Snapshot the modal to find fieldsagent-browser snapshot -i -c# Look for:# textbox "Name": current display name# textbox "Bio": current bio# textbox "Location": current location# textbox "Website": current website# button "Save"# 5. Edit the desired field(s) using fillagent-browser fill <bio_textbox_ref> "New bio text here"# 6. Click Saveagent-browser click <save_button_ref>
Verification:
# Modal should close. Reload profile and check updated content.agent-browser open "https://x.com/<own_username>"agent-browser snapshot -i -c
Element Identification:
- Edit profile link:
link "Edit profile"— on own profile page only - Modal fields:
textbox "Name",textbox "Bio",textbox "Location",textbox "Website" - Save button:
button "Save" - Close without saving: press Escape (may trigger beforeunload if fields were modified)
Known Issues:
- These are standard
textboxelements (not contenteditable divs) —fillworks normally - Bio has a 160 character limit
- Name has a 50 character limit
5.3 Pin a Tweet
Goal: Pin a tweet to the top of own profile.
Preconditions: On own tweet (visible on profile, search results, or tweet detail page).
Steps:
# 1. Find own tweet and its More button (same as 3.5 Delete flow)agent-browser snapshot -i -c -s 'article'# Look for: button "More" [expanded=false] on the target tweet# 2. Scroll into view and click Moreagent-browser scrollintoview <more_button_ref>agent-browser click <more_button_ref># 3. Find and click "Pin to your profile"agent-browser snapshot -i -c# Look for: menuitem "Pin to your profile"agent-browser click <pin_menuitem_ref># 4. Confirm in the dialog (if one appears)agent-browser snapshot -i -c# Look for confirmation button and click it
Element Identification:
- Menu item:
menuitem "Pin to your profile" - If already pinned:
menuitem "Unpin from profile"appears instead
Known Issues:
- Same More button pattern as Delete (3.5) — use
scrollintoviewbefore clicking - Only one tweet can be pinned at a time; pinning a new one unpins the old one
6. Navigation & Utility
6.1 Switch Between Tabs (For You / Following)
Goal: Switch the home timeline between "For you" and "Following" tabs.
Preconditions: On home timeline.
Steps:
# 1. Navigate to homeagent-browser open https://x.com/home# 2. Snapshot the tablist to see available tabsagent-browser snapshot -i -c -s '[role="tablist"]'# Shows: tab "For you" [selected], tab "Following", possibly custom tabs# 3. Click the desired tabagent-browser click <tab_ref>
Verification:
# The clicked tab should now have [selected] attributeagent-browser snapshot -i -c -s '[role="tablist"]'
Element Identification:
- Tabs are
tabrole elements inside atablist - Selected tab has
[selected]attribute - Default tabs:
tab "For you",tab "Following" - Users may have custom tabs (e.g. Lists)
Known Issues:
- Tab names may vary by locale
- Custom tabs may be present (user-created Lists pinned to home)
6.2 Scroll to Load More Tweets
Goal: Scroll down to trigger lazy-loading of additional tweets.
Preconditions: On any timeline or search results page.
Steps:
# Scroll down by pixel amountagent-browser scroll down 2000# Wait for new content to loadagent-browser wait 2000# Check loaded contentagent-browser snapshot -i -c -s 'article'
Verification:
# New articles should be present in the snapshotagent-browser snapshot -i -c -s 'article'
Known Issues:
- X uses a virtualized list — only articles in the current viewport are in the DOM. Scrolled-past articles are removed. So
snapshot -s 'article'always shows ~1-3 articles regardless of how many have been loaded. - Multiple scroll operations may be needed:
agent-browser scroll down 2000repeated - Scroll amount of 2000-3000px is typically enough to trigger lazy load
6.3 Handle Login Popups / Modals
Goal: Dismiss the "Sign in" or "Create account" overlay that X shows to non-authenticated users or as a periodic prompt.
Preconditions: Popup is visible. Usually only appears for non-authenticated sessions.
Steps:
# Snapshot to identify the popup type and close/dismiss buttonagent-browser snapshot -i -c# Common patterns:# 1. Close button (X icon): look for button with close-related label# 2. "Not now" or "Skip" link/button# 3. Click outside the modal# Try pressing Escape firstagent-browser press Escape
Known Issues:
- Multiple types of login prompts exist (bottom bar, full modal, interstitial)
- When using CDP with a logged-in profile, these should not appear
- **A full login wall ("Sign in", "Email or username", "Continue with…") means the isolated
CDP profile is LOGGED OUT — NOT something to dismiss or work around.** Do not try to log in, do not type credentials, do not open a fresh session. STOP and tell the user to log into X in the isolated Chrome window (bun run setup-chrome reconnects without wiping; --reset for a clean re-login). Resume only after they confirm they are logged in.
- A small periodic in-app prompt (not a full wall) on an otherwise logged-in timeline can be
dismissed with agent-browser press Escape.
6.4 Handle Cookie Consent Banner
Goal: Accept or dismiss the cookie consent banner.
Preconditions: Banner is visible (usually first visit without saved cookies).
Steps:
# Snapshot to find the consent banneragent-browser snapshot -i -c# Look for accept/decline buttons — common patterns:# button "Accept all cookies"# button "Refuse non-essential cookies"agent-browser click <accept_button_ref>
Known Issues:
- When using CDP with an existing Chrome profile, cookie consent is usually already accepted
- Banner may not appear at all in CDP sessions
6.5 Dismiss "Turn on notifications" Prompt
Goal: Close the browser notification permission prompt or X's in-app notification suggestion.
Preconditions: Prompt is visible.
Steps:
# For X's in-app prompt (DOM element):agent-browser snapshot -i -c# Look for dismiss/close button near the notification suggestionagent-browser click <dismiss_button_ref># For browser-level notification prompt:# This is handled automatically by agent-browser (auto-dismiss dialogs)# If not, use --no-auto-dialog flag to prevent it from appearing
Known Issues:
- Browser-level notification prompt is auto-dismissed by agent-browser by default
- X's in-app prompt is a regular DOM element — find and click the close button
- When using CDP with existing profile, notification permissions are usually already set
7. Lists
7.1 View Own Lists
Goal: Navigate to the user's lists management page.
Preconditions: Logged in, know own username.
Steps:
agent-browser open "https://x.com/<username>/lists"
Verification:
# Title should contain "Lists created by @<username> / X"agent-browser get title
Key Elements:
- Create button:
link "Create a new List" - Your lists section:
region "Your Lists"— contains links to each list - Each list link:
link "<ListName> N members <OwnerName> @<owner> Pin List" - Pin button:
button "Pin List"— pins list to home timeline sidebar - Discover section:
heading "Discover new Lists"
Known Issues:
- None
7.2 View a List Timeline
Goal: Open a specific list and read its timeline (posts from list members).
Preconditions: Know the list URL or navigate from lists page.
Steps:
# Option A: Direct URL (list ID from the lists page)agent-browser open "https://x.com/i/lists/<list_id>"# Option B: From lists page, click the list linkagent-browser snapshot -i -c# Look for: link "<ListName> N members ..."agent-browser click <list_link_ref>
Verification:
# URL should be https://x.com/i/lists/<list_id>agent-browser get url# Articles from list members should appearagent-browser snapshot -i -c -s 'article'
Key Elements (list timeline page):
- List header with name and member count
- Edit link:
link "Edit List"— only for own lists - Members link:
link "N Members"— opens members modal - Share button:
button "Share Menu" - More button:
button "More"— additional list options - Timeline: same
articlestructure as home timeline
Known Issues:
- Same virtualized list behavior as home timeline — scroll to load more posts
- List URL format is
/i/lists/<numeric_id>, not a human-readable slug
7.3 Browse List Members
Goal: View all members of a list.
Preconditions: On the list timeline page.
Steps:
# 1. On the list page, find and click the Members linkagent-browser snapshot -i -c# Look for: link "N Members"agent-browser click <members_link_ref># 2. Members modal opens — shows all membersagent-browser snapshot -i -c# Each member has: button "<Name> @<username> Remove ..." (for own lists)
Verification:
# Modal should show heading "List members" and list of membersagent-browser snapshot -i -c# Look for: heading "List members" and button elements for each member
Element Identification:
- Members modal:
heading "List members" - Each member (own list):
button "<Name> Verified account @<username> Remove ..." - Each member has a
button "Remove"to remove them from the list - Close modal: press Escape
Known Issues:
- Members list may be long — scroll to see all members
7.4 Create a New List
Goal: Create a new list and optionally add initial members.
Preconditions: Logged in.
Steps:
# 1. Navigate to lists pageagent-browser open "https://x.com/<username>/lists"# 2. Click "Create a new List"agent-browser snapshot -i -c# Look for: link "Create a new List"agent-browser click <create_link_ref># 3. Fill in list detailsagent-browser snapshot -i -c# Modal shows: textbox "Name", textbox "Description", checkbox "Make private", button "Next" [disabled]agent-browser fill <name_textbox_ref> "My List Name"# Optionally: agent-browser fill <description_textbox_ref> "Description"# Optionally: agent-browser click <make_private_checkbox_ref># 4. Click Next (enabled after name is filled)agent-browser click <next_button_ref># 5. Add members page appears — search and add usersagent-browser snapshot -i -c# Shows: combobox "Search query", tab "Members (0)", tab "Suggested", button "Done"agent-browser fill <search_combobox_ref> "<username_to_search>"# Wait for search resultsagent-browser wait 1000agent-browser snapshot -i -c# Look for: button "Add" next to the useragent-browser click <add_button_ref># 6. Click Done to finishagent-browser click <done_button_ref>
Verification:
# Should redirect to the new list's timeline pageagent-browser get url# URL should be https://x.com/i/lists/<new_list_id>
Element Identification:
- Create modal step 1:
textbox "Name",textbox "Description",checkbox "Make private",button "Next" [disabled] - Create modal step 2:
combobox "Search query",button "Done",tab "Members (N)",tab "Suggested" - Each search result:
button "<Name> @<username> Add ..."with nestedbutton "Add" - After adding: button changes from
"Add"to"Remove"
Known Issues:
- List name cannot contain special characters like
[,], etc. — validation error: "A list's name can't include a disallowed" - Name max length: 25 characters
- After clicking Next, there's an optional banner photo upload step (can be skipped by proceeding to add members)
7.5 Add Member to List
Goal: Add a user to an existing list.
Preconditions: On the list page (own list).
Steps:
# 1. Click Edit Listagent-browser snapshot -i -c# Look for: link "Edit List"agent-browser click <edit_list_ref># 2. Click "Manage members" tab in the edit modalagent-browser snapshot -i -c# Look for: tab "Manage members"agent-browser click <manage_members_tab_ref># 3. Switch to "Suggested" tab to search for usersagent-browser snapshot -i -c -s '[role="tablist"]'# Look for: tab "Members (N)" and tab "Suggested"agent-browser click <suggested_tab_ref># 4. Search for the user to addagent-browser snapshot -i -c# Look for: combobox "Search query"agent-browser fill <search_combobox_ref> "<username>"agent-browser wait 1000# 5. Click Add on the search resultagent-browser snapshot -i -c# Look for: button "Add" next to the useragent-browser click <add_button_ref>
Verification:
# The button should change from "Add" to "Remove"# Members tab count should increment: tab "Members (N+1)"agent-browser snapshot -i -c
Element Identification:
- Manage members modal:
heading "Manage members" - Sub-tabs:
tab "Members (N)"[selected],tab "Suggested" - Search:
combobox "Search query" - Each user:
button "Add"(not added) orbutton "Remove"(already in list)
Known Issues:
- None
7.6 Remove Member from List
Goal: Remove a user from an existing list.
Preconditions: On the list page (own list), user is a member.
Steps:
# Option A: From Members modal (7.3)# Each member has a "Remove" buttonagent-browser click <remove_button_ref># Option B: From Edit List → Manage members → Members tabagent-browser snapshot -i -c# Each member row has: button "Remove"agent-browser click <remove_button_ref>
Verification:
# Member count should decrement# The user should disappear from the members listagent-browser snapshot -i -c
Known Issues:
- Remove is immediate — no confirmation dialog
- Member count updates instantly in the tab label
7.7 Delete a List
Goal: Delete an entire list.
Preconditions: On the list page (own list).
Steps:
# 1. Click Edit Listagent-browser snapshot -i -c# Look for: link "Edit List"agent-browser click <edit_list_ref># 2. Click Delete Listagent-browser snapshot -i -c# Look for: button "Delete List"agent-browser click <delete_list_button_ref># 3. Confirmation dialog appearsagent-browser snapshot -i -c# Look for: heading "Delete List?" + button "Delete" + button "Cancel"agent-browser click <confirm_delete_ref>
Verification:
# Should redirect to lists pageagent-browser get url# URL should be https://x.com/<username>/lists/
Element Identification:
- Edit modal:
button "Delete List"— red/destructive button at bottom - Confirmation:
heading "Delete List?"+button "Delete"+button "Cancel"
Known Issues:
- Deletion is permanent — no undo
- Redirects to
/<username>/lists/after successful deletion
Testing Notes
General Approach
For each operation above:
- Preflight (
bun run doctor --check-cdp; if down,bun run setup-chrome). agent-browser is pinned to the isolated CDP Chrome viaagent-browser.json, so commands auto-attach — noconnect/--cdpneeded. Never open a fresh--session-name/--headedbrowser to log in. - Run
agent-browser snapshot -ito see current interactive elements - Execute each step, taking snapshots between steps to verify state changes
- Record the working command sequence
- Note any element identification strategies (aria-label, role, text content)
- Test at least 3 times to confirm reliability
Element Identification Strategy
Do NOT hardcode @ref numbers (e.g. @e3). Refs change between page loads. Instead, document:
- The aria-label or aria-role to look for
- The text content pattern
- The CSS selector as fallback
- The relative position in the accessibility tree
Session Management
There is exactly ONE authorized way to reach a logged-in X session: the isolated CDP Chrome on port 9222 (launched by bun run setup-chrome). See the "Browser Connection Contract" in the system prompt — it is binding for every operation here.
agent-browser is pinned to that CDP port via agent-browser.json, so every command auto-attaches — no connect/--cdp needed. Preflight, then just open:
bun run doctor --check-cdp # if DOWN: bun run setup-chrome (idempotent, never touches your normal Chrome)agent-browser open https://x.com/homeagent-browser snapshot -i -c # confirm you are logged in (timeline visible, not a login wall)
A Timeout connecting to CDP at 127.0.0.1:9222 error means the isolated Chrome is not running (run bun run setup-chrome) — it is NOT a login problem.
Do NOT use --session-name, --headed, or --profile to open a fresh browser and log in — a fresh session has no cookies and pulls you into an automated login, which gets the account rate-limited ("We've temporarily limited your login"). Never type credentials or attempt a login. If the CDP profile is logged out, STOP and ask the user to log in once in the isolated Chrome window (bun run setup-chrome --reset for a clean slate), then resume.