Skip to content
Back to skills

X Bookmarks

ASecurity

Read the user's latest X (Twitter) bookmarks as typed data (text, author, metrics, media, links, quoted posts, articles) with a TypeScript SDK that calls X's Bookmarks GraphQL API via fetch inside the signed-in x.com tab through playwriter. Use when the user asks for their bookmarks, saved tweets, or wants to export, summarize, or process X bookmarks.

  • 44 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added November 7, 2026
content-marketingtypescriptgobashnodegitapi

Works with

  • cursor
  • cli
  • api

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned November 7, 2026

npx -y skills add remorses/opencode-config --skill x-bookmarks --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of X Bookmarks?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for X Bookmarks
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/remorses-x-bookmarks/badge)](https://www.skillsdirectory.com/skills/remorses-x-bookmarks)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
$schema: https://gist.githubusercontent.com/remorses/9f3737a6f516c01f4bdb612045c64072/raw/agent-skill.schema.json
name: x-bookmarks
description: >
  Read the user's latest X (Twitter) bookmarks as typed data (text, author,
  metrics, media, links, quoted posts, articles) with a TypeScript SDK that
  calls X's Bookmarks GraphQL API via fetch inside the signed-in x.com tab
  through playwriter. Use when the user asks for their bookmarks, saved
  tweets, or wants to export, summarize, or process X bookmarks.
---

# X bookmarks

`sdk.ts` in this folder reads bookmarks with **network calls only**. It runs
`fetch()` inside an x.com page via `page.evaluate`, so the user's cookies
apply. No clicks, no scrolling, no DOM scraping. Do not copy cookies or
tokens out of the browser.

Preconditions: Chrome has the playwriter extension, and the user is logged
into x.com. Read `playwriter skill` in full first if you have not used it.
If `playwriter session new` fails with `extension_not_connected`, show the
user the printed install steps and stop. Do not try other workarounds.

## Get the latest bookmarks

```bash
playwriter session new --tab-group x
playwriter -s <id> --timeout 60000 -e "$(cat <<'EOF'
const { getLatestBookmarks } = await import(require('node:os').homedir() + '/.config/opencode/skills/x-bookmarks/sdk.ts')
const bookmarks = await getLatestBookmarks({ context, limit: 50 })
require('node:fs').writeFileSync('/tmp/x-bookmarks.json', JSON.stringify(bookmarks, null, 2))
console.log(bookmarks.length, bookmarks.map((b) => `@${b.author.screenName}: ${b.text.slice(0, 80)}`).join('\n'))
EOF
)"
```

The playwriter sandbox runs Node 24, so `import()` of a `.ts` file works
(type stripping). Write big results to `/tmp` instead of printing them;
console output gets truncated.

`getLatestBookmarks` returns `XTweet[]`, newest bookmark first. See the
interfaces at the top of `sdk.ts` for all fields. It opens its own tab and
**always closes it** (also on error), so no x.com tab is left open.

## Manual pagination

Only use this when you need cursors. You own the tab, so close it in
`finally`. Never leave the page open after the export.

```js
const sdk = await import(require('node:os').homedir() + '/.config/opencode/skills/x-bookmarks/sdk.ts')
const page = await context.newPage()
try {
  const query = await sdk.discoverBookmarksQuery({ page })
  const first = await sdk.fetchBookmarksPage({ page, query, count: 100 })
  const second = await sdk.fetchBookmarksPage({ page, query, cursor: first.nextCursor })
} finally {
  await page.close()
}
```

Only `discoverBookmarksQuery` navigates.

## How it works

1. `discoverBookmarksQuery` opens `https://x.com/i/bookmarks` and waits for
   the web app's own `GET /i/api/graphql/<queryId>/Bookmarks` request. It
   copies `queryId`, the `features` param and the bearer header from it.
2. `fetchBookmarksPage` replays that GraphQL call with `fetch()` in the page,
   adding `x-csrf-token` from the `ct0` cookie.
3. The response is parsed into `XTweet` objects. The `cursor-bottom-*` entry
   gives `nextCursor`.

## Gotchas

- **Never hardcode the queryId or features.** They change with X deploys.
  Always discover them from the live request.
- The queryId is **not** in `main.js`. It is in a lazy chunk. Do not waste
  time scanning bundles.
- `x-client-transaction-id` is **not** required for this read endpoint.
- The recording started from **History** (`/i/history`), which shows the same
  Bookmarks tab. The `flow/timeline.json`, `live_pipeline` and
  `viewer_context` calls in the recording are noise.
- `UsersByRestIds HTTP-403` page errors in playwriter output come from x.com
  itself. Ignore them.
- Long posts: full text is in `note_tweet`; the SDK handles this.
- On `401`/`403` from Bookmarks, or `missing ct0 cookie`, ask the user to log
  into x.com in Chrome.

Files in this skill

  • SKILL.md3.8 KB
  • sdk.ts9.8 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.