test-driven-development
OBobra/superpowers298k2,631 new stars in the last 7 days
Publish tweets via Chrome DevTools Protocol (CDP) without any browser extension, using a fixed post layout. Use for posting X/Twitter updates from scripts or agents, especially the periodic awesome-list sweep posts.
Post tweets by driving the already-logged-in Chrome through the DevTools Protocol. No extension needed.
Canonical copy: awesome-autoresearch/.agents/skills/tweet-posting-cdp/SKILL.md
~/.agents/skills/tweet-posting-cdp symlinks here so awesome-jev and other repos resolve the same content. Edit one place only.
所有巡查/更新帖用同一版式,空行必须是真的空行(脚本按段落写入,见下)。
📋 {repo} 周期巡检
本轮新增 {n} 条:
1. {名称}({分类}):{一句话:是什么 + 为什么值得加}
2. {名称}({分类}):{一句话}
📂 github.com/yibie/{repo}
📊 {total} entries
规则:
本轮新增 N 条:,空行,然后逐条。序号. 名称(分类):说明。条目之间空一行。📂 / 📊)。📢 {repo} 上线
{一句话背景}
· {亮点 1}
· {亮点 2}
📂 github.com/yibie/{repo}
📊 {total} entries
内容校验通过:N 字符 / M 段落,M 必须等于期望段落数)。DRY_RUN=1 先跑一遍,确认读回渲染与预期完全一致再真发。echo "推文内容" | ~/.agent-reach/scripts/post-tweet-cdp.mjs
# 或
~/.agent-reach/scripts/post-tweet-cdp.mjs "推文内容"
# 只写入并校验、不发帖、结束前清空(推荐先跑)
DRY_RUN=1 ~/.agent-reach/scripts/post-tweet-cdp.mjs "推文内容"
# 排查写入过程:每次读回值都打出来
STEP_TRACE=1 DRY_RUN=1 ~/.agent-reach/scripts/post-tweet-cdp.mjs "..."
脚本成功时会打印 ✅ 内容校验通过:N 字符 / M 段落,发布后再确认 ✅ 发布成功(composer 已清空)。任一步校验失败即中止,不会发布未校验内容。失败时会自动清空 composer 残稿。
Chrome running with remote debugging and logged into X. The script finds the port itself:
CDP_PORT env override (if set).9333, 9222, 9223 via http://127.0.0.1:<port>/json/version.~/Library/Application Support/Google/Chrome/DevToolsActivePort.The live instance on this Mac is usually /tmp/chrome-pub-profile on 9333 (started with --user-data-dir=/tmp/chrome-pub-profile --remote-debugging-port=9333). The main-profile DevToolsActivePort file can be stale (pointing at a dead 9222) — that is why probing comes first.
Chrome v152: /json/version does work and returns webSocketDebuggerUrl; use it instead of constructing the URL from the file.
Target.getTargets → take the page whose pathname === '/home'. Only /home is acceptable — a status page also has [data-testid="tweetTextarea_0"], but that is the reply box. If no /home tab exists, create one.Target.attachToTarget with flatten: true → session-scoped commands.['selectAll'] then ['delete'].发帖/Post (a 回复/Reply button means we are on the wrong page → abort).[data-testid="tweetButtonInline"] (fallback [data-testid="tweetButton"]).Input.insertText with "\n" does not create a paragraph in X's Draft.js composer. Measured results for inserting AAA, then a line break, then BBB:
| Method | Blocks | Content |
|---|---|---|
Input.insertText("\n") | 1 | AAA BBB — no break at all |
keyDown+char+keyUp, text: "\r" | 2 | ["AAA", "\nBBB"] — literal \n leaks into the text |
execCommand('insertParagraph') | 1 | mangled |
rawKeyDown+keyUp, no text | 2 | ["AAA","BBB"] ✅ |
So: Enter = {type:'rawKeyDown', key:'Enter', code:'Enter', windowsVirtualKeyCode:13, nativeVirtualKeyCode:13} then a matching keyUp. Never pass text.
textContent| State | innerText per block | textContent per block |
|---|---|---|
| empty composer | ["\n\n\n"] — reads as 3 newlines ❌ | [""] ✅ |
AAA + Enter + BBB | ["AAA\n\n","BBB"] ❌ | ["AAA","BBB"] ✅ |
| trailing empty paragraph | ["AAA\n\n","BBB","\n"] ❌ | ["AAA","BBB",""] ✅ |
innerText appends \n/\n\n per block (unusable for exact comparison); editor.textContent concatenates all blocks and loses newlines. Join per-block textContent with \n.
document.execCommand('selectAll'/'delete') bypasses Draft.js's internal state; the next insertText then gets reverted. A bare rawKeyDown with modifiers: 4 (Cmd+A) also does not reliably select. Use commands: ['selectAll'] and commands: ['delete'] on Input.dispatchKeyEvent, then re-read to confirm the composer is empty.
Historical bug worth remembering: the combination of an uncleared draft plus chunked inserts silently dropped the first ~50 characters of every post (and collapsed newlines), so published tweets began mid-sentence. Full-content verification exists to catch exactly this.
If you strip trailing newlines before comparing, a step that only adds a blank line looks like no progress → the loop declares a stall. Keep newlines in the readback and use it for both the prefix check and the cursor; only strip trailing newlines for the final equality check.
A draft left in the composer for more than a few seconds is saved by X and restored by a reload, so reloading alone does not always discard it (observed with a 672-character draft: two reloads both came back with the text). Clearing via DOM manipulation does not reliably clear the saved copy either — a Range selection plus execCommand('delete') empties the visible editor but also strips its paragraph nodes, leaving Draft.js desynced until the next reload repairs it.
Consequences, in order of importance:
resetComposer retries the reload up to four times, reading the composer twice each round (a restored draft can appear later than the DOM finishing), and aborts if the composer is still dirty. The failure mode is a safe abort, never writing on top of an old draft.DRY_RUN right before a real post can therefore leave a draft behind. The real run will either clear it or abort — it will not silently prepend it./home tabtweetTextarea_0 exists on status pages as the reply composer. Replying to a stranger's tweet is the worst possible failure. The script hard-blocks on pathname !== '/home' and on a button label that isn't 发帖/Post.
Insert line-by-line in ~24-char chunks with ~130 ms between chunks. Earlier notes recommended ~50-char chunks for Input.insertText; that is still fine for enabling the Post button, but smaller chunks plus after-each-step verification is what actually makes the write reliable.
opencli twitter post) — breaks with attach failed: Cannot access a chrome-extension:// URL of different extension when the extension ID drifts.execCommand('insertText') for the composer body — React never registers it, Post stays disabled.ensure-chrome.sh / post-tweet.sh — opencli-era helpers, superseded.OBobra/superpowers298k2,631 new stars in the last 7 days
MAmattpocock/skills288k10,178 new stars in the last 7 days
AFaffaan-m/ECC278k4,046 new stars in the last 7 days
Audit accessibility on Web, iOS, and Android to meet WCAG 2.2, with keyboard navigation and screen-reader support.
AFaffaan-m/ECC278k4,046 new stars in the last 7 days
AFaffaan-m/ECC278k4,046 new stars in the last 7 days
FIfirecrawl/firecrawl191k2,010 new stars in the last 7 days