← Back to skills

Domain skill

x

Markdown synced from browser-harness domain skills.

Agent prompt

Use this skill

Copy this prompt into your coding agent to make it enable browser-harness domain skills and read this exact domain folder before automating.

Set up https://github.com/browser-use/browser-harness for me if it is not already installed. If setup is needed, read `install.md` first to install and connect it to my real browser. Then read `SKILL.md` for normal usage and always read `helpers.py` because that is where the browser-harness functions are.

Enable domain skills if they are not already enabled by setting `BH_DOMAIN_SKILLS=1` for browser-harness. Use the `x` domain skill from `agent-workspace/domain-skills/x/`. Read every markdown file for this domain before inventing an approach:
- agent-workspace/domain-skills/x/article-source-recovery.md
- agent-workspace/domain-skills/x/posting.md

Use those domain-skill notes to complete my task for `x` in my real browser. When you open a setup, verification, or task tab, activate it so I can see the active browser tab.

Skill contents

What the agent will read

Recover canonical sources from public X article cards

article-source-recovery.md

Source
  • Use this when a public x.com/<handle>/status/<id> post contains an X Article card, but opening the card itself redirects an anonymous browser to login.
  • A public status page can render the post and article card without authentication even when the article route is gated. Inspect links in the post before clicking:
  • Article cards commonly point to https://x.com/i/article/<id>. An anonymous visit to that route may redirect to x.com/i/jf/onboarding/web?...mode=login; do not interpret this as evidence that the status itself is...
  • Authors sometimes cross-post the same article on LinkedIn or another public profile and link the canonical external page there. A LinkedIn sign-in modal may cover the page visually while the public post text and...
Show full markdown

Use this when a public x.com/<handle>/status/<id> post contains an X Article card, but opening the card itself redirects an anonymous browser to login.

Public status extraction

A public status page can render the post and article card without authentication even when the article route is gated. Inspect links in the post before clicking:

javascript
Array.from(document.querySelectorAll('article a')).map(a => ({
  text: (a.innerText || '').trim(),
  href: a.href,
})).filter(x => x.href)

Article cards commonly point to https://x.com/i/article/<id>. An anonymous visit to that route may redirect to x.com/i/jf/onboarding/web?...mode=login; do not interpret this as evidence that the status itself is private or deleted.

Cross-post fallback

Authors sometimes cross-post the same article on LinkedIn or another public profile and link the canonical external page there. A LinkedIn sign-in modal may cover the page visually while the public post text and anchors remain in the DOM.

Inspect the public DOM rather than entering credentials:

javascript
({
  description: document.querySelector('meta[name="description"]')?.content || null,
  body: (document.body.innerText || '').slice(0, 12000),
  links: Array.from(document.querySelectorAll('a')).map(a => ({
    text: (a.innerText || '').trim(),
    href: a.href,
  })),
})

LinkedIn wraps external links as https://www.linkedin.com/redir/redirect?url=<encoded-url>&.... Decode the url query parameter, open that canonical URL directly, and validate the article title and content there.

Guardrails

  • Treat the X status and the author's canonical page as primary sources; comments and search snippets are secondary context.
  • If no author-controlled canonical page is recoverable, cite the public status and mark the X Article body as inaccessible rather than reconstructing it from commentary.
  • Never type credentials from screenshots or attempt to bypass a login wall.

Posting & Auth

posting.md

Source
  • Navigate to https://x.com/i/flow/login (not https://x.com/) to get the full sign-in modal. The homepage may render a truncated version of the auth buttons.
  • The "Sign in as <user>" Google button uses Chrome's Federated Credential Management (FedCM) API. CDP mouse events (Input.dispatchMouseEvent) do not count as a trusted user gesture for FedCM, and the button lives in a...
  • If the user wants Google sign-in, ask them to click the button themselves, then wait for confirmation before proceeding.
  • Use the standard form on https://x.com/i/flow/login. Ask the user for credentials — do not guess or read them from screenshots.
Show full markdown

Login

Navigate to https://x.com/i/flow/login (not https://x.com/) to get the full sign-in modal. The homepage may render a truncated version of the auth buttons.

Google sign-in (FedCM) — cannot be automated

The "Sign in as " Google button uses Chrome's Federated Credential Management (FedCM) API. CDP mouse events (Input.dispatchMouseEvent) do not count as a trusted user gesture for FedCM, and the button lives in a sandboxed cross-origin iframe (accounts.google.com). Clicking it via automation has no effect.

If the user wants Google sign-in, ask them to click the button themselves, then wait for confirmation before proceeding.

Username / password flow

Use the standard form on https://x.com/i/flow/login. Ask the user for credentials — do not guess or read them from screenshots.

Composing and posting a tweet

Once logged in, the home feed is at https://x.com/home.

python
import json

# Find the compose box and type
result = js(r'''
  var el = document.querySelector("[data-testid=\"tweetTextarea_0\"]");
  if (!el) return null;
  var r = el.getBoundingClientRect();
  return JSON.stringify({x: Math.round(r.x + r.width/2), y: Math.round(r.y + r.height/2)});
''')
if result is None:
    raise RuntimeError("compose textarea not found — are you logged in and on x.com/home?")
pos = json.loads(result)
click_at_xy(pos["x"], pos["y"])
type_text("hello world!")

# Find and click Post
btn = js(r'''
  var b = document.querySelector("[data-testid=\"tweetButtonInline\"]")
       || document.querySelector("[data-testid=\"tweetButton\"]");
  if (!b) return null;
  var r = b.getBoundingClientRect();
  return JSON.stringify({x: Math.round(r.x + r.width/2), y: Math.round(r.y + r.height/2)});
''')
if btn is None:
    raise RuntimeError("post button not found — did the compose surface fail to open?")
pos = json.loads(btn)
click_at_xy(pos["x"], pos["y"])

Stable selectors

ElementSelector
Compose textbox[data-testid="tweetTextarea_0"]
Post button (home feed inline)[data-testid="tweetButtonInline"]
Post button (modal)[data-testid="tweetButton"]

Confirmation

A toast "Your post was sent." appears at the bottom of the page after a successful post. Verify with a screenshot.

Gotchas

  • The home feed compose area and the sidebar "Post" button both open a compose surface, but tweetButtonInline is specific to the inline feed composer. If you open a modal (e.g. via the sidebar "Post" button), use tweetButton instead.
  • If the Grok side panel opens unexpectedly, click a neutral area (e.g. left sidebar) to dismiss it before interacting with the compose box.
  • twitter.com redirects to x.com — all canonical URLs use x.com.