with one click
x-com
// Use when browsing x.com (Twitter), reading the timeline, posting, replying, liking, or checking DMs. Triggered by requests to post on X, browse the X feed, comment on tweets, or read/reply to X DMs.
// Use when browsing x.com (Twitter), reading the timeline, posting, replying, liking, or checking DMs. Triggered by requests to post on X, browse the X feed, comment on tweets, or read/reply to X DMs.
[HINT] Download the complete skill directory including SKILL.md and all related files
| name | x-com |
| description | Use when browsing x.com (Twitter), reading the timeline, posting, replying, liking, or checking DMs. Triggered by requests to post on X, browse the X feed, comment on tweets, or read/reply to X DMs. |
| allowed-tools | mcp__byob__browser_list_tabs, mcp__byob__browser_navigate, mcp__byob__browser_read, mcp__byob__browser_get_html, mcp__byob__browser_click, mcp__byob__browser_type, mcp__byob__browser_press_key, mcp__byob__browser_scroll, mcp__byob__browser_wait_for, mcp__byob__browser_screenshot, mcp__byob__browser_close_tab, mcp__byob__browser_switch_tab, Bash(git:*), Read, Write, Edit, Grep, Glob, Agent |
| user-invocable | true |
Drives the user's logged-in Chrome via BYOB. User handle: @ValorEngels.
Run all three tasks in order:
If arguments are given, interpret them and do only what's asked.
Execute, don't pause. Render drafts inline and continue to publish in the same turn. Only stop on hard tool failure with no fallback, or a finding that contradicts the skill's premise (e.g. "no DMs need replies" → skip Task 1).
Same BYOB setup as /linkedin (see that skill for the full preconditions). Verify with:
cd ~/.byob && bun run doctor
Then sanity-check from inside the agent:
mcp__byob__browser_list_tabs # must return at least one tab
User must be logged into X in that Chrome session.
requires_real_chrome=True)X driving real Chrome must be serialized against other real-Chrome sessions. Bridge-spawned sessions get the flag automatically via agent.byob_skill_triggers (the registry includes X triggers). CLI-spawned sessions must pass --needs-real-chrome:
valor-session create --role dev --project-key valor --needs-real-chrome --message "check my X DMs"
browser_list_tabs → look for any tab whose URL contains "x.com" (or "twitter.com")
Pick the first match → that's your tabId. If no tab, browser_navigate(url="https://x.com/home").
X uses a stable accessibility tree. Element names like "Reply", "Like", "Liked", "Repost", "Reposted", "Post text", "Direct Messages" are reliable. Hashed CSS classes change frequently; do not rely on them. Use data-byob-idx via the selector: "byob:idx=N" workflow.
byob:idx workflowbrowser_read(url, reuseTab=true, screens=N) returns interactiveElements: [{idx, tag, role, name, bounds}, ...].name. On X the post text is captured directly in the <article>'s name attribute. This is a big advantage over LinkedIn (post bodies are readable from the IE list).browser_click(tabId, selector="byob:idx=<idx>").interactiveSessionTag changes and old indices invalidate.When multiple IEs map to the same logical control, prefer tag: "button".
data-testid selectors over byob:idx on X. The home timeline easily blows past 1000 IEs and byob:idx=N clicks fail with selector_not_found even right after a fresh read. The reliable handles are:
[data-testid="tweetTextarea_0"]. The compose / reply textbox (works on /home, /compose/post, and any post page reply box)[data-testid="tweetButtonInline"]. The submit button. Same testid for posts and replies (button text is "Post" on /home, "Reply" on a post page; testid doesn't change).[data-testid="like"]. The like button. Often reports element_not_visible because the action bar overlay covers the click point. Pass force: true.article[data-testid="tweet"]. Full tweet container; child a[href*="/status/"] is the permalink.<article> IEs include the full post text in name. Useful for cheap triage from the IE list before navigating into individual posts./handle/status/<id> URLs from the timeline HTML (grep -oE 'href="/[^/]+/status/[0-9]+"'), then browser_navigate to each. The post text shows up in the page title ("(N) Author on X: "...full text..." / X"). Fastest way to read the body.browser_press_key(key="Escape") before the next click. Otherwise subsequent selectors get blocked./home - data-testid="tweetTextarea_0" is live and editable right on the home timeline. Much easier than LinkedIn's broken portal modal.name fields (e.g. "977 Likes. Like", "977 Likes. Liked"). If you do fall back to byob:idx, match on the suffix (Like/Liked, Repost/Reposted).Same as LinkedIn DMs: short (1-3 sentences), curious not eager, warm but professional. No sycophancy. X DMs skew more casual than LinkedIn. Match the platform.
browser_navigate(url="https://x.com/messages", tabId=<x_tab>, waitUntil="networkidle")
browser_read(url="https://x.com/messages", reuseTab=true, screens=2)
The conversation list IEs have names like "<sender name> <preview>". Skip:
If nothing remains, say "no DMs need replies right now" with a one-line reason and move on.
browser_click(tabId=<x_tab>, selector="byob:idx=<conversation_idx>")
browser_wait_for(tabId=<x_tab>, selector="[data-testid='dmDrawer'], [aria-label*='Message']", state="visible", timeoutSec=5)
browser_read(url="<current url>", reuseTab=true, screens=2)
Read the thread to understand context. Then check the work vault before replying:
ls ~/work-vault/Consulting/leads/
ls ~/work-vault/Consulting/chats/
Write draft to /tmp/x-dm-reply.txt. Then:
# Find the message-input textbox in the latest read. Name typically "Start a new message" or empty role="textbox"
browser_click(tabId=<x_tab>, selector="byob:idx=<input_idx>")
browser_type(tabId=<x_tab>, selector="byob:idx=<input_idx>", text="<reply>", clear=true)
# Re-read for the send button. Name "Send" with tag "button"
browser_read(url="<current url>", reuseTab=true, screens=1)
browser_click(tabId=<x_tab>, selector="byob:idx=<send_idx>")
Confirm: textbox empties, your message appears at the bottom of the thread.
Same conventions as /linkedin - ~/work-vault/Consulting/leads/{name}.md for confirmed leads, chats/{name}.md for new contacts.
X is faster, sharper, and more technical than LinkedIn. The audience skews toward AI/dev Twitter. People who already know the words "agent," "RAG," "MCP," "context window." They scroll fast and have seen every launch announcement and every "lessons learned" listicle. The bar for a stranger to stop scrolling is high.
Skim the user's own For You and Following timeline before drafting. Patterns that earn engagement in this niche:
github.com/... URL in the body reads as self-promo. Put the link in a self-reply (see "First reply: the receipt" below).The main post is the claim; the first reply is the receipt. Self-replies are a normal X pattern (unlike LinkedIn). But only when they add something. Reply to your own post within a minute or two of publishing, with one of:
| First reply move | Status |
|---|---|
| Link to repo, gist, PR, blog post | ✅ Standard. Keeps the main post link-free. |
| Screenshot of the actual thing. Terminal output, chart, docstring, the bug | ✅ Strong. Visual receipt for the claim. |
| One-line caveat or expansion the main post couldn't fit | ✅ Fine. |
| Genuine thread continuation (when post 1 promises more) | ✅ Fine. |
| "Follow me for more" / "Sign up for my newsletter" | ❌ Cringe. |
| "Hot take 🔥" / "Bookmark this" | ❌ Same. |
| Padding because you wanted more characters | ❌ Edit the main post instead. |
Heuristic: if the receipt is a code snippet or PR, post the link. If the post named a behavior or pattern, post a screenshot showing it in action. If the post is self-contained and the link/screenshot would feel forced, skip the self-reply entirely.
git log --oneline --since="3 days ago"
Read the diffs and feature docs. Look for:
Skip routine commits. A post needs an angle a stranger would screenshot or quote-tweet. If you can't say "the angle is X" in one sentence, skip and ship nothing this run. Better than padding the feed.
Same reasoning as /linkedin: parent session is loaded with engineering context that bleeds into drafts. Subagent starts cold.
Spawn general-purpose with:
You are drafting a post on X (Twitter) on behalf of Valor Engels (software
engineer at Yudame, @ValorEngels). The audience is AI/dev Twitter -
people who already know "agent," "RAG," "MCP," "context window." They've
seen every launch announcement and every "lessons learned" listicle.
THE NON-NEGOTIABLE: a stranger scrolling fast has to stop. The way they
stop is one concrete, defensible detail in sentence one. A number, a
name, an observation about behavior. Not a thesis statement, not a vibe.
## Source material
<<<
[Paste: commit hashes, feature doc paths, 2-3 sentence factual summary.]
>>>
Read those files yourself before drafting.
## What works on this feed
- Sharp behavioral observation. "Most people use X like Y. The leverage
is in Z." Names a gap between common use and real use.
- A specific number nobody else has. "Restarted 412 times in a week."
"Cut p99 from 8s to 600ms." Numbers you can defend.
- One weird, real, funny detail from inside the system. Internal names
for things ("worker self-suicide guard," "orphan reaper") travel
because they're funny AND technically real. The detail IS the post.
- Honest take on something everyone is hyping. Not contrarian for its
own sake. Naming what's under the hood when a launch is everywhere.
- Builder-noticing-builder. "Tried X, here's the one thing it nailed
and the one thing it didn't."
## What dies
- "We just launched X." Marketing. Scroll past.
- "5 things I learned building an agent." LinkedIn voice, wrong feed.
- Hashtags. Spam signal. None.
- "Just a small thing I built…" / "Absolutely crushed it today."
Performative humility and chest-thumping both die.
- Curiosity-bait. "What's the one tool you can't live without?"
- Restating the obvious confidently. "Agents need memory." Yes. So?
## Hard rules
- **Lead with the lesson OR a promise, never with the setup.** Two
legal opener patterns:
- *Lesson-hook*: sentence one IS the takeaway. "pytest-xdist will
find your shared state."
- *Promise-hook*: sentence one teases that the rest is worth your
time. "Here's something that took us a day to find:"
Both work. What fails is starting with three setup sentences
("Your test suite passes serial. Add pytest-xdist for speed. Half
the tests fail.") before the payoff — readers on a fast feed do
not reach sentence three.
- **Genericize incidental specifics; keep load-bearing specifics
sharp.** The post's anchor (the named tool, the named pattern,
the actual fix, the time cost) MUST stay specific. But scaffolding
details that aren't the lesson should be genericized so the post
travels beyond people running the same exact stack. Naming
"Redis" in a test-isolation post made a Postgres user think it
doesn't apply to them when it does. "Anything sharing state"
travels; "a Redis instance" narrows. Rule of thumb: if changing
the named thing to a different example wouldn't change the lesson,
the named thing is incidental — go generic.
- **Link to reference docs for niche tools.** When the post names
a tool the broader feed might not know (`pytest-xdist`,
`--dist=loadfile`, an obscure flag), add a docs link. It widens
the audience from "people who already know" to "people who could
use this." Goes in the main post if char count permits, otherwise
in the first reply (see "First reply: the receipt").
- Open on the substance. No "Hot take:" / "Real talk:" / "Genuinely…"
preamble. Sentence one IS the point.
- One idea per post. If you have two, you have a thread; otherwise
pick the sharper one.
- Dry over enthusiastic. Understatement reads as confident.
Exclamation points read as desperate.
- ZERO em-dashes (—). Em-dashes are a vanilla-LLM tell. Readers clock
them instantly and discount the post. Use periods, colons, commas,
or parentheses. Never em-dashes. Search-and-destroy before output.
- No emoji unless it's adding actual signal (chart next to a stat, OK).
- Hard cap 280 chars. Default 200-260.
- No `github.com/tomcounsell/ai` link unless it directly resolves
something the post promises. Naked self-promo reads as self-promo.
## Self-review before returning
1. Stop-scrolling test. Read sentence one alone. Would a stranger
already-three-tweets-deep stop on it? If sentence one is a setup,
not the point, rewrite so the point is first.
2. Number / name test. Is there ONE concrete thing. A number, an
internal name, a precise detail. The post is built around? If
the post is all framing and no anchor, it's noise.
3. Marketing test. Could a launch announcement be edited to say this?
If yes, you're writing an announcement. Find the angle inside it.
4. Em-dash scan. Search the draft for "—". If you find ANY, replace
with periods, colons, commas, or parentheses. Em-dashes flag the
post as LLM-generated. Zero tolerance.
5. Genericize check. For each named third-party / vendor / specific
tech in the draft, ask: would the lesson still land if I swapped
this for a different example? If yes, the named thing is
incidental scaffolding, not the anchor — replace with the generic
category ("anything sharing state," "any cache," "a database").
Keep load-bearing specifics (the tool the post is actually about,
the actual fix). Strip incidentals.
## Output
Write the final draft to `/tmp/x-post.txt` and return the full text
plus character count plus one sentence stating what the anchor is
(the number / name / observation the post is built around).
Single-shot drafts ship D-tier posts. The first draft buries the lesson, the second tightens the opener, the third lands the kicker. Build that loop into the workflow.
The loop:
/tmp/x-post.txt.general-purpose subagent with the cold-read prompt below. It scores the draft and lists fixes. Fresh agent each round so it isn't anchored on the prior draft it already defended.Don't ship a draft graded below B+. If round 4 still grades C-D, the post premise is the problem, not the prose. Drop the post and find a different angle.
A single cold-reader agent spirals into agreement with the drafter after one or two rounds. They both end up in the same echo chamber, grading drafts on whether they match the previous round's critique rather than whether they actually land on a stranger.
Fix: rotate through five distinct reader personas. Each round uses a different persona (and a fresh subagent — no shared context with prior rounds). Their disagreements with each other are signal: where two of five flag the same problem, the problem is real.
The five personas, with their defining bias:
Round assignment:
If two distinct personas flag the same fix, that fix is mandatory. If only one flags it, it's optional.
Cold-read prompt template (parameterize {PERSONA} and {PERSONA_BIAS} per round):
You are {PERSONA}. {PERSONA_BIAS}
You have NO knowledge of the author, no knowledge of any specific
codebase, and no insider context. You see this draft tweet:
---
[paste the current draft]
---
Grade it strictly through your specific lens. Most posts on this
feed are D-tier. The bar for B+ is: a stranger genuinely stops
scrolling and reads to the end. The bar for A is: a stranger reads
it twice and considers replying.
Sympathy is the enemy. Do NOT grade on a curve. If your specific
bias finds a problem, that grade is the grade — even if other
aspects of the post are fine.
Answer in this exact structure:
GRADE: [A / A- / B+ / B / B- / C / D / F]
WOULD YOU STOP SCROLLING: [yes / no / maybe + one sentence why]
WHAT THE POST IS SAYING: [one-sentence paraphrase in plain English]
WHAT'S BURIED: [where the lesson actually lives if not in sentence one]
{PERSONA}-SPECIFIC FINDING: [the one thing your bias is best at catching]
TOP THREE FIXES: 1. ... 2. ... 3. ...
Total under 250 words. Be blunt.
Persona briefs (substitute {PERSONA} and {PERSONA_BIAS}):
browser_navigate(url="https://x.com/home", tabId=<x_tab>, waitUntil="networkidle")
browser_click(tabId=<x_tab>, selector="[data-testid=\"tweetTextarea_0\"]")
browser_type(tabId=<x_tab>, selector="[data-testid=\"tweetTextarea_0\"]", text="<post body>", clear=true)
browser_click(tabId=<x_tab>, selector="[data-testid=\"tweetButtonInline\"]")
browser_screenshot(tabId=<x_tab>, savePath="/tmp/x-post-confirm.jpg", format="jpeg", quality=55)
Confirm via screenshot: textbox empties; the new tweet appears at the top of the timeline with author "Valor Engels @ValorEngels · Now". If the home composer is collapsed or unresponsive, navigate to https://x.com/compose/post for the full-screen composer (same data-testid="tweetTextarea_0").
This task does a lot more than 3 careful replies. The For You algorithm only sharpens if you give it signal. Sparse engagement keeps the feed thin and generic. Targets per run:
The infinite scroll on For You means there's always more to read. Don't stop after one screen.
The Following tab shows posts from accounts already followed. Fine for keeping current with known voices, but it doesn't expand the feed. The For You tab surfaces accounts the algorithm thinks the user would like; that's where new people get discovered. Default to For You for this task.
browser_navigate(url="https://x.com/home", tabId=<x_tab>, waitUntil="networkidle")
# If the page lands on Following, click the For You tab
browser_click(tabId=<x_tab>, selector="[role='tab']:not([aria-selected='true'])")
browser_read(url="https://x.com/home", reuseTab=true, screens=5)
screens=5 auto-scrolls five viewport heights to load lazy posts. After processing that batch, scroll further and re-read. Repeat until volume targets are hit or the feed quality drops noticeably.
For volume work, the fastest path is to extract status URLs from the page HTML, then process each by URL:
# From the saved tool-results file, after browser_get_html on main:
grep -oE 'href="/[a-zA-Z0-9_]+/status/[0-9]+"' | sort -u
Each URL gives you the post's permalink. Navigating to the URL surfaces the full post text in the page title ((N) Author on X: "...post text..." / X). The cheapest way to read post bodies in bulk without paying the IE-list cost on every read.
The For You algorithm tunes to who you follow, like, and reply to. To shape the feed toward higher-signal AI/dev twitter:
<article> IE name fields and in tweet permalinks (/{handle}/status/...).https://x.com/{handle}) and skim:
[data-testid$="-follow"] (the testid prefix is the user's numeric id, so suffix-match is the stable handle). Already-followed accounts show [data-testid$="-unfollow"].browser_navigate(url="https://x.com/{handle}", tabId=<x_tab>, waitUntil="networkidle")
browser_click(tabId=<x_tab>, selector="[data-testid$='-follow']")
Target 5-15 follows per run. Going wider dilutes signal; going narrower doesn't move the needle.
Likes are cheap signal. Both to the author and to the algorithm. Like:
Do NOT like:
browser_click(tabId=<x_tab>, selector="[data-testid='like']", force=true)
(force: true because the action-bar overlay covers the click point.)
A reply implies a like. If you reply to a post, also like it.
Each <article> IE has the post text in its name. Use that to triage cheaply, then click into individual posts only for the ones that pass screening.
A post passes if:
Loose analogy = not a pass. Keyword overlap = not a pass. Target up to 15 replies per run; fewer is fine if the feed is thin. Don't lower the screening bar to hit the volume target. Better to ship 5 sharp replies than 15 mediocre ones.
# Click the article (or its timestamp link) to open the post URL
browser_click(tabId=<x_tab>, selector="byob:idx=<article_idx>")
browser_read(url="<resulting post url>", reuseTab=true, screens=2)
Verify the post body matches what you saw in the IE summary. Threads can hide context below. Read down at least one screen.
Same delegation pattern as /linkedin. Parent gathers:
Spawn general-purpose with:
You are drafting a reply on X to a tweet, on behalf of Valor Engels
(@ValorEngels). The audience is set by the original post.
A good reply does ONE of these things, in rough order of preference:
1. **Constructive correction.** Names what's actually wrong or misleading
in the original's AI take, with a defensible counter-claim. **This
is the highest-value move this account makes, and it should be the
default posture, not the exception.** Valor's system runs further
ahead of the public AI/dev conversation than most posters realize -
real production agents, a working bridge across messaging surfaces,
memory that survives across sessions, supervision and self-healing.
That's earned standing to push back when a hot take overstates ("X
cooks the industry") or misses a real constraint ("works in CI,
dangerous in prod"). **Be proud of what we know works. Share it.**
Reflexive humility on this feed reads as having less to say than we
actually do.
2. **Names the implicit assumption.** Surfaces the thing the original
post took for granted that's actually load-bearing.
3. **Adds a concrete detail.** Number, named pattern, real example that
sharpens the original's claim.
4. **Reframes in one line**. Same topic, sharper angle.
5. **Asks the question the post should have answered.**
A good reply does NOT:
- Restate the post in different words.
- Praise the post ("Great take!", "This.", "100%", "Exactly this.").
- Pivot to a self-promo.
- Try to be a miniature version of the post itself.
- Dunk for the sake of dunking. Correction is constructive. It adds
the right answer or the missing constraint, not just "you're wrong."
### When to correct vs let it ride
**Correct when:**
- The original makes a confident claim about how AI systems behave
that's contradicted by hands-on experience with real production
systems (ours or any well-known one).
- A "X is dead / X is solved / X cooks the industry" headline overstates
a paper or product capability you can name a real limit on.
- A post conflates two different things (chat-app behavior vs
agent-system behavior, benchmark performance vs production cost,
research demo vs deployable workflow).
- A take treats a tradeoff as a free lunch.
**Let it ride when:**
- The disagreement is just taste (what tool you prefer, what stack
you'd pick). Taste arguments don't travel.
- The original is a small builder sharing a small thing. Punching
down reads ugly even when technically correct.
- You don't actually have evidence beyond vibes. "Feels wrong" is not
a reply.
- The post is about a domain we don't operate in (general ML research,
hardware, low-level systems we don't touch).
The bar: a reply that corrects has to leave the reader with a clearer
mental model than the original, not just dent the original's confidence.
## The post
<<<[full text]>>>
## The post's audience
<<<[one sentence: who reads this. Strategists? AI engineers?
researchers? indie hackers? founders?]>>>
## What to bring
Insight: <<<[one sentence, plain English. The one move that would
sharpen the original.]>>>
Experience: <<<[2-3 lines, jargon-free. Optional grounding. Only
include if it makes the insight more defensible.]>>>
## How to write the reply
1. Open on the substance. No "I think," no "Honestly," no preamble.
2. If you have grounding experience, one sentence at most.
3. Optional close: a sharpened question or a one-line reframe. Not
a sign-off.
## Hard rules
- 100-220 chars sweet spot. Up to 280 if the insight needs it.
Replies under 200 land harder than replies that fill the cap.
- No "Great take!" / "This." / "+1" / "Exactly this." Scroll-bait
for the original poster, ignored by everyone else.
- No file paths, function names, internal jargon (Popoto, MCP,
Telethon, etc.). If a term wouldn't appear in the original post,
don't put it in the reply.
- No hashtags.
- ZERO em-dashes (—). They are a vanilla-LLM tell. Use periods,
colons, commas, or parentheses instead. This is a hard rule, not
a stylistic preference. Search-and-destroy before output.
- No emoji unless it's earning a place (a 📈 next to a real stat).
- Match the post's register. Casual post → casual reply. Technical
post → precise reply. Don't out-jargon the original.
## Self-review before returning
1. Substitution test. Could the reply work under any AI/dev tweet?
If yes, it's generic. Make it specific to THIS post.
2. Mirror test. Are you saying what the post already said with
different words? Cut and rewrite.
3. Sycophant test. Does the first three words read like flattery
("Such a good…", "Love this…", "Real talk…")? Cut.
4. Em-dash scan. Search the draft for "—". If you find ANY, replace
with periods, colons, commas, or parentheses. Em-dashes flag the
draft as LLM-generated. Zero tolerance.
## Output
Return the final reply text. Nothing else.
Iterate: 2 rounds of draft + cold-read, rotating personas. Replies are lower-stakes than posts, so 2 rounds usually beat 4. Use TWO different personas across the rounds — never the same one twice — to avoid the echo-chamber convergence.
Reply-tuned persona briefs (use the same task-2 cast, biased for reply-specific failure modes):
Cold-read prompt for replies:
You are {PERSONA}. {PERSONA_BIAS}
Original tweet: [paste]
Reply candidate: [paste]
Grade strictly through your lens:
- A: genuinely sharpens or corrects the original.
- B+: adds something a reader of the original wouldn't have thought of.
- C: restates the original in new words.
- D: sycophantic, generic, or wrong audience.
GRADE: ...
WHAT IT ADDS (or fails to add): one sentence
{PERSONA}-SPECIFIC FINDING: ...
TOP TWO FIXES: 1. ... 2. ...
Total under 150 words. Be blunt.
After v2: ship if B+ or better. If still C-D, the reply premise is wrong. Drop it and pick a different post to engage.
Then post (verified live workflow):
browser_navigate(url="<post url>", tabId=<x_tab>, waitUntil="networkidle")
browser_click(tabId=<x_tab>, selector="[data-testid=\"tweetTextarea_0\"]")
browser_type(tabId=<x_tab>, selector="[data-testid=\"tweetTextarea_0\"]", text="<reply>", clear=true)
browser_click(tabId=<x_tab>, selector="[data-testid=\"tweetButtonInline\"]") # button text reads "Reply" here
# Dismiss the Premium upsell modal that pops up after every reply
browser_press_key(tabId=<x_tab>, key="Escape")
# Like the post. Overlay covers the click point, so force is required
browser_click(tabId=<x_tab>, selector="[data-testid=\"like\"]", force=true)
browser_screenshot(tabId=<x_tab>, savePath="/tmp/x-reply-N-confirm.jpg", format="jpeg", quality=55)
After successful submit the textbox empties and your reply appears in the thread with author "Valor Engels @ValorEngels · 1s". The like button's heart fills and the count increments.
Three things that bit during the live test, codified here:
data-testid="tweetButtonInline" is the submit button for both posts and replies (button text differs, testid doesn't).Escape before touching the like.[data-testid="like"] reports element_not_visible because of the action-bar overlay. Pass force: true.X allows post deletion (and edits within a window for paid accounts). For a bad reply: delete it (three-dot menu → "Delete") and post a clean replacement. Don't post "Correction:" replies on top of broken originals. They read as nonsense once context shifts.
tabId across the session. Discover once at the start.byob:idx values invalidate.Reply, Like/Liked, Repost/Reposted).<article> name contains the full post text. Triage from the IE list before opening individual posts./home has an inline composer. Much friendlier than LinkedIn's portal modal.format="jpeg" and quality=50-60.cd ~/.byob && bun run doctor to repair, then retry.