$npx -y skills add MoizIbnYousaf/marketing-cli --skill mktg-xRead tweets, threads, bookmarks, articles, and user timelines on X (formerly Twitter) using an authenticated GraphQL flow. Use this skill whenever the user mentions a tweet URL, a Twitter thread, an X bookmark, a Twitter article, reading their X bookmarks, pulling a thread, or gr
| 1 | # /mktg-x — authenticated Twitter/X reader |
| 2 | |
| 3 | Read tweets, threads, bookmarks, articles, and user timelines from X (formerly Twitter) using an authenticated flow. Hits X's web GraphQL API with session cookies so `/cmo`, source-digestion workflows, and any other mktg skill can read auth-walled X content without a browser. |
| 4 | |
| 5 | Firecrawl and WebFetch can't reach this content — Twitter serves a degraded logged-out page to unauthenticated clients. mktg-x is the only path. |
| 6 | |
| 7 | ## On Activation |
| 8 | |
| 9 | 1. **Verify auth credentials are set.** mktg-x needs two env vars — a session token and a CSRF token. If neither is set, fail loud — do NOT fall back to firecrawl or WebFetch: |
| 10 | |
| 11 | | Priority | Session token env var | CSRF token env var | |
| 12 | |---|---|---| |
| 13 | | 1 (preferred) | `MKTG_X_AUTH_TOKEN` | `MKTG_X_CT0` | |
| 14 | | 2 (explicit legacy compat) | `TWITTER_AUTH_TOKEN` or `AUTH_TOKEN` with `MKTG_X_ENABLE_LEGACY_ENV=1` | `TWITTER_CT0` or `CT0` with `MKTG_X_ENABLE_LEGACY_ENV=1` | |
| 15 | |
| 16 | The script resolves credentials in priority order. If no session token or CSRF token is found via any name, it emits the frozen error shape and exits with code 2. See [references/auth.md](references/auth.md) for how to obtain both values from your browser and export them. |
| 17 | |
| 18 | **Headless agent warning:** do NOT rely on browser-cookie extraction in headless environments. On macOS, Chrome's cookie database is Keychain-encrypted — the extraction flow triggers a GUI password prompt that **a headless agent cannot answer and will hang indefinitely**. Always use env vars for agent workflows. |
| 19 | |
| 20 | 2. **Detect the URL shape.** mktg-x handles five distinct shapes. Each dispatches to a different endpoint via [scripts/fetch-x.ts](scripts/fetch-x.ts): |
| 21 | |
| 22 | | URL pattern | Shape | Script invocation | |
| 23 | |---|---|---| |
| 24 | | `x.com/<user>/status/<id>` (no reply siblings) | single tweet | `bun run ./scripts/fetch-x.ts tweet "<url>"` | |
| 25 | | `x.com/<user>/status/<id>` (has reply chain by same author) | thread | `bun run ./scripts/fetch-x.ts thread "<url>"` | |
| 26 | | `x.com/i/bookmarks` or `x.com/<user>/bookmarks` | bookmarks | `bun run ./scripts/fetch-x.ts bookmarks "<url>"` | |
| 27 | | `x.com/<user>/article/<id>` (long-form article) | article | `bun run ./scripts/fetch-x.ts article "<url>"` | |
| 28 | | `x.com/<user>` (no status/bookmarks/article suffix) | timeline | `bun run ./scripts/fetch-x.ts timeline "<user>"` | |
| 29 | |
| 30 | Also accepts `twitter.com`, `mobile.twitter.com`, and `www.x.com` — the script normalizes before routing. |
| 31 | |
| 32 | 3. **Run the fetch.** The script resolves `MKTG_X_AUTH_TOKEN` + `MKTG_X_CT0`, calls the appropriate X GraphQL endpoint, sanitizes tweet text (terminal-escape injection prevention via `sanitize.ts`), normalizes the response, and writes structured JSON to stdout. Write large responses to `.mktg-x/` via shell redirect; stream small ones directly. |
| 33 | |
| 34 | 4. **Honor the frozen output contract.** The script returns one canonical per-item shape that source-digestion workflows consume directly — six fields, nothing more: |
| 35 | ```json |
| 36 | { |
| 37 | "tweet_text": "...", |
| 38 | "thread_unroll": ["first tweet text", "reply by same author", "..."], |
| 39 | "embedded_links": ["https://..."], |
| 40 | "media_urls": ["https://pbs.twimg.com/..."], |
| 41 | "author": "@handle", |
| 42 | "timestamp": "2026-04-11T00:00:00Z" |
| 43 | } |
| 44 | ``` |
| 45 | For `bookmarks` and `timeline` modes, the output is a JSON **array** of this per-item shape — one entry per post. `thread_unroll` is empty `[]` for non-thread posts. **This surface is frozen** — adding or renaming fields breaks source-extractor's contract. Any change requires bumping the contract version. |
| 46 | |
| 47 | **Engagement metrics (likes, reposts, replies, views) are deliberately omitted.** See Anti-Pattern #1 — this is not optional polish, it's a load-bearing decision about what mktg ideates on. |
| 48 | |
| 49 | 5. **Brand integration (progressive enhancement).** |
| 50 | - **L0 (no brand context):** works identically — fetch, return JSON, done. mktg-x is a read-only fetch skill; it writes nothing to `brand/`. |
| 51 | - **L1+ (brand/voice-profile.md exists):** if the user asks for voice analysis after the fetch, hand the output to `brand-voice` or `positioning-angles`. mktg-x does not analyze voice itself — it just grabs the source. |
| 52 | |
| 53 | ## When to Use |
| 54 | |
| 55 | Trigger this skill whenever A |