skill(x-integration): port to v2 + open to Linux + full feature parity (25 tools)#2409
Open
jorgenclaw wants to merge 1 commit into
Open
skill(x-integration): port to v2 + open to Linux + full feature parity (25 tools)#2409jorgenclaw wants to merge 1 commit into
jorgenclaw wants to merge 1 commit into
Conversation
…y (25 tools incl. delete)
Rewrites /x-integration for NanoClaw v2, ports it to Linux, and expands the
surface from 5 v1 tools (post/like/reply/retweet/quote) to **25 tools** covering
every common X (Twitter) action a human can do via the web UI — read, compose
with media + native scheduling, full engagement-toggle pairs, delete (with
text-echo safety guard), scheduled-tweet management, DMs, and bulk bookmark export.
The v1 skill was macOS-only and targeted v2-removed integration points
(`src/ipc.ts`, `container/agent-runner/src/ipc-mcp.ts`, file-IPC at
`/workspace/ipc/tasks/`). This PR adopts the v2 idiom — `kind:'system'`
outbound rows dispatched via `registerDeliveryAction()` and result delivery
via `notifyAgent()` from `src/modules/approvals/primitive.ts`.
## What changed
**v2 mechanism.** Every tool writes a `kind:'system'` row to `messages_out`
with `content: JSON.stringify({ action, requestId, ...args })` (mirrors
`mcp-tools/self-mod.ts`). The host's `src/delivery.ts` dispatches by action
key. Each handler routes through a `pacedRun()` mutex (10-second floor
between any two X actions, host-process-wide), spawns the matching script
via `tsx`, awaits the JSON result, and calls `notifyAgent()` — which writes
the outcome back into `inbound.db` AND wakes the container so the result
lands on the next agent turn.
**Cross-platform browser detection.** `lib/chrome-detect.ts` resolves the
executable: `CHROME_PATH` env var → platform-specific probe (Chrome → Brave
→ Chromium on macOS via `mdfind` and Linux via `which`) → throw with an
actionable install hint. Flatpak browsers are detected and rejected with
a specific message pointing at the `.deb` install. No fallback to
Playwright's bundled Chromium — that defeats the bot-detection-survival
design that's the whole point of this skill.
**Full feature surface (25 tools).**
- Read (8): x_read_tweet, x_read_thread, x_read_user, x_read_bookmarks,
x_read_list, x_read_timeline, x_read_notifications, x_search.
- Compose (3, with optional `media[]` + `schedule_at`): x_post, x_reply,
x_quote. Scheduling drives X's native composer calendar UI — scheduled
tweets sit in X's queue regardless of NanoClaw uptime.
- Engagement (9): x_like / x_unlike, x_retweet / x_unretweet,
x_bookmark / x_unbookmark, x_follow / x_unfollow, x_delete_tweet.
- Schedule queue (2): x_list_scheduled, x_cancel_scheduled.
- DMs (3): x_read_dm_inbox, x_read_dm_thread, x_send_dm.
- Bulk (1): x_export_bookmarks (resumable CSV dump for full-history walks).
**x_delete_tweet safety: text-echo guard.** Delete is the only irreversible
action without a built-in undo. To guard against URL hallucinations and
copy-paste mistakes without adding an approval round-trip, the tool requires
a `text_must_match` parameter — a ≥5-char substring of the tweet body. The
host script reads the live tweet first and refuses to delete unless the
substring is present. Consistent with the skill's existing per-action trust
model (no approval gating; wrap in another skill if approvals are wanted).
**Concentrated DOM volatility.** Two new files isolate everything that can
break when X redeploys:
- `lib/locators.ts` — every CSS / data-testid selector across all 25 scripts.
- `lib/extract.ts` — every tweet/profile/list/DM parser (parseTweetCard,
collectTweets, parseDmConversation, plus pretty-printers like
renderTweetList for notifyAgent payloads).
When X breaks something, those are the only places to update.
**Privacy + safety knobs.**
- DM action handlers redact body content from nanoclaw.log (only action +
requestId logged).
- Per-action timeout (120s default) + screenshot+DOM-dump capture on failure
(logs/x-failures/ for post-mortem).
- 10s mutex pacing protects accounts from X's anti-spam tripping.
- DM read-receipt caveat surfaced in tool descriptions so the agent knows
opening a thread marks it read.
## How it works
```
┌── Container (Bun) ─────────────────────────────────────────────┐
│ mcp-tools/x-integration.ts │
│ └─ 25 tools via makeXTool() factory │
│ └─ writeMessageOut({ kind:'system', content: ... }) │
└────────────────┬───────────────────────────────────────────────┘
│ outbound.db (the v2 IPC surface)
▼
┌── Host (Node) ─────────────────────────────────────────────────┐
│ src/modules/x-integration/index.ts │
│ └─ registerDeliveryAction('x_*', handler) × 25 │
│ └─ pacedRun() ⟶ spawn tsx script ⟶ notifyAgent(result) │
└────────────────┬───────────────────────────────────────────────┘
│ inbound.db + wake
▼
(agent gets the result on its next turn)
```
## How it was tested
End-to-end on Linux (EVO box, Pop!_OS) over several weeks: read flows
(timeline, bookmarks, search, single-tweet, thread, user, list,
notifications), compose flows (post, reply, quote with media attachments
and with native X scheduling), engagement toggles (like/unlike,
bookmark/unbookmark, follow/unfollow, retweet/unretweet), schedule queue
(list, cancel by index AND by text-match), DMs (inbox listing, thread
read, send), bulk bookmark export (~hundreds of tweets across multiple
paginated calls). Delete tested in two modes: with valid text_must_match
(success), and with deliberately-wrong text_must_match (refused with clear
error message). Macos compatibility preserved from the prior v1 skill.
## Usage
```
What's in my bookmarks lately?
Summarize this thread: https://x.com/<user>/status/<id>
Post a tweet: gm. Sovereignty is a daily practice.
Reply to https://x.com/<id> with: this matches my experience.
Schedule this for tomorrow 9am PT: <text>
What DMs are in my inbox?
DM @<friend>: thanks for the link earlier.
Delete my tweet https://x.com/<id> — it contains "deprecated"
```
Each command produces one acknowledgment from the agent, then a follow-up
with the result ~10–60s later.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Rewrites
/x-integrationfor NanoClaw v2, ports it to Linux, and expands the surface from 5 v1 tools (post/like/reply/retweet/quote) to 25 tools covering every common X (Twitter) action a human can do via the web UI — read, compose with media + native scheduling, full engagement-toggle pairs, delete (with text-echo safety guard), scheduled-tweet management, DMs, and bulk bookmark export.The v1 skill was macOS-only and targeted v2-removed integration points. This PR adopts the v2 idiom:
kind:'system'outbound rows dispatched viaregisterDeliveryAction()and result delivery vianotifyAgent()fromsrc/modules/approvals/primitive.ts.What's new vs v1
kind:'system'rows; host dispatches by action key throughpacedRun()(10s mutex), spawns the matchingscripts/<name>.tsvia tsx, and callsnotifyAgent()so the result wakes the container.lib/chrome-detect.ts):CHROME_PATHenv → Chrome → Brave → Chromium on macOS (mdfind) and Linux (which). Flatpak browsers detected and rejected with a.deb-install hint. No fallback to Playwright's bundled Chromium — that's what defeats X's bot detection in the first place.media[]+ nativeschedule_at), Engagement (9 includingx_delete_tweet), Schedule queue (2), DMs (3), Bulk bookmark export (1).x_delete_tweetwith text-echo safety guard. Delete is the only irreversible action without built-in undo. The tool requirestext_must_match(≥5-char substring of the tweet body). The host script reads the live tweet first and refuses to delete unless the substring is present — guards against URL hallucinations and copy-paste mistakes without adding an approval round-trip.lib/locators.ts(all CSS/data-testid selectors) andlib/extract.ts(all parsers). When X redeploys, those are the only files to update.nanoclaw.log; per-action 120s timeout + screenshot+DOM-dump capture on failure (logs/x-failures/); 10s mutex pacing.How it works
How it was tested
End-to-end on Linux (Pop!_OS) over several weeks: every read flow, every compose flow with media + native scheduling, all engagement toggle pairs, schedule queue list/cancel-by-index/cancel-by-text-match, full DM round-trip, bulk bookmark export across paginated calls. Delete tested in two modes — valid
text_must_match(success) and deliberately-wrongtext_must_match(refused with clear error). macOS compatibility preserved from prior v1 skill.Usage
Each command produces one acknowledgment, then a follow-up with the result ~10–60s later.
Test plan
🤖 Generated with Claude Code