One click saves an X (Twitter) thread, Article, or post as clean Markdown for LLM context (Claude, ChatGPT) plus a self-contained offline HTML archive - images, video, and quoted posts embedded, with
翻译准备中
插件JavaScript · PowerShellGitHub @wolfgang-aura在线运营者未确认
试用过了吗? 点一下就好,无需登录。
One-click userscript: save X/Twitter threads, Articles & posts as clean Markdown for LLM context (Claude, ChatGPT) + a self-contained offline HTML archive. Images, video & quoted posts inlined, honest completeness reporting. Local-first.
翻译准备中
取自仓库 README
正在加载完整 README。 以下为已保存的摘录。
Save X/Twitter threads & Articles as clean Markdown for LLMs — plus a one-file offline HTML
archive.
One click on a post, thread, or Article produces an agent-ready folder: a clean .llm.md you can
paste straight into Claude or ChatGPT, the real image files it references, and a self-contained
.html that opens fully offline with media and quoted posts inlined. No screenshots, no
copy-paste mangling, no dead links when the thread disappears.
Why this instead of copy-paste or screenshots?查看完整 README ↗English | 简体中文
Save X/Twitter threads & Articles as clean Markdown for LLMs — plus a one-file offline HTML archive.
One click on a post, thread, or Article produces an agent-ready folder: a clean .llm.md you can
paste straight into Claude or ChatGPT, the real image files it references, and a self-contained
.html that opens fully offline with media and quoted posts inlined. No screenshots, no
copy-paste mangling, no dead links when the thread disappears.
Why this instead of copy-paste or screenshots?
The source file is also the shipped artifact: plain JavaScript, no production build step.
quoted_tweet payload to patch source URLs and rebuild the card in one round-trip.Content-Length on GET and HEAD, so
Slack, Discord, and Twitter link previews render the shared images and pages correctly.The product remains local-first. Nothing is uploaded until you click Create AI link (the default button action, which publishes at the 7-day default expiry) or choose the combined save-and-share flow, which asks for the expiry first — and the reply archive never uploads at all. When a save and a share are combined, the local copy is completed first, so an upload failure cannot take it away. Shared capsules exclude raw video, are capped at 25 MB, and retain source links when media cannot be included.
On the X timeline, a status page, or an Article:
The first library save asks you to choose a root folder. Desktop Chromium writes folders directly. Browsers without the File System Access API receive a zip with the same structure.
<library root>/
_sourcecapsule-index.md
2026-07-02/
<handle>-<status-id>/
<handle>-<status-id>.html
<handle>-<status-id>.llm.md
AI_LINK.txt # only after an AI readable link is created
media/
README.txt
Thread capture is deliberately honest: it includes same-author posts visible during a progressive
scroll, marks post boundaries, and records best-effort completeness in the manifest. X can
virtualize or withhold posts, so this is not yet a guarantee of every reply in very long threads.
Everything above archives a post and its thread. This archives the replies to a post — what people said back — as threaded Markdown plus a full-text CSV.
On an opened post, click ... then:
sourcecapsule-replies-<id>.md and .csv.Replies accumulate across runs and never shrink. A populated field is never overwritten by an empty one and the longer text always wins, so a truncated preview cannot clobber full long-form text, and a reply that disappears from a later surface keeps what was already captured. Storage is IndexedDB; if a write fails, it says so rather than losing the run silently.
Reply media is recorded as links only — downloading media for thousands of replies is out of scope. Images, video, and posters are listed as URLs you can follow.
Every archive reports what it captured, lists any reply id it knows about but could not fetch, and refuses to treat X's public reply counter as a denominator. Measured across three real conversations:
| Replies on X (counter) | Archived with content | Known but uncaptured |
|---|---|---|
| 764 | 636 | 0 |
| 670 | 537 | 0 |
| 32 | 36 | 0 |
In every case the archive captured 100% of the replies X actually delivered to the browser. The shortfall against the counter in the first two is replies X never served at all — deleted, blocked, restricted, or simply never returned by any surface — which are undetectable by construction. The third row exceeds its counter because X's number appears to count direct replies only, while the archive also captures nested replies-to-replies.
Coverage is best effort and the receipt in every export says so. Deleted, private, and never-delivered replies remain unknowable.
The userscript ships pointing at the hosted share service
(https://sourcecapsule-share.wolfgang-aura.workers.dev): a Cloudflare Worker + R2 backend with
per-IP rate limiting on link creation, a 25 MB package cap, and 1/7/30-day expiry with daily
cleanup. To try it:
.md to the capsule URL for the clean Markdown endpoint.When the link expires, the archived copy is deleted but the link does not go blank: it serves a
short notice page pointing back to the original post on X, so whoever you sent it to can still
find the source. The .md endpoint serves the same notice as Markdown. That pointer holds only
the post's public permalink, title, and author handle — nothing about who made the link — and is
removed 180 days after expiry, or immediately if you delete the link.
Successful links are remembered in this browser under SourceCapsule: Recent AI readable links.
Expired links remain visible but greyed out. When a link is created from a local-save receipt or the
combined save-and-link flow, SourceCapsule also writes AI_LINK.txt beside the saved Markdown so the
same link can be recovered from the library folder.
cd SourceCapsule
npm.cmd install
npm.cmd run dev:share
Leave that PowerShell window open, then open Tampermonkey while on x.com and set
SourceCapsule: Share service URL to http://127.0.0.1:8787. Other hosts also need an
@connect grant in the userscript header (and a host_permissions entry in the extension
manifest).
The backend is a small Cloudflare Worker with an R2 bucket. Deployment details are in
share-worker/README.md. Accounts, billing, and permanent-link quotas
are intentionally not part of this first share release.
cd SourceCapsule
npm.cmd run build:extension
Then in Chrome:
chrome://extensions.dist\sourcecapsule-extension.This package reuses the tested userscript engine through a thin compatibility layer and has passed the automated package suite plus live-X validation. It remains an unpacked beta until a separate Chrome Web Store publication is prepared.
One command captures a post and returns a ready AI readable link, with no clicks, no prompts, and no clipboard use:
node scripts\sourcecapsule-capture.mjs --url "https://x.com/handle/status/123" --json
{
"ok": true,
"sourceUrl": "https://x.com/handle/status/123",
"viewUrl": "https://sourcecapsule-share.wolfgang-aura.workers.dev/c/...",
"markdownUrl": "https://sourcecapsule-share.wolfgang-aura.workers.dev/c/....md",
"expiresAt": "2026-09-06T12:51:33.824Z",
"complete": true,
"warnings": []
}
Only that JSON goes to stdout. Progress and diagnostics go to stderr, and a failure exits nonzero.
It reuses the normal capture path. The extension opens the post in a background window, waits
for the passive GraphQL recovery layer, runs the same share export a menu click would run, and
keeps strict completeness validation on. If strict mode still finds missing evidence after every
recovery layer and retry, the command fails with needs_owner and lists what is missing, rather
than publishing a capsule with dead ends in it:
{
"ok": false,
"error": "needs_owner",
"message": "Strict capture could not recover missing evidence.",
"counts": { "imageFetchFailed": 2 }
}
npm.cmd run build:extension
npm.cmd run install:native-host
The installer needs no administrator rights. It writes everything under HKCU and installs the
host into %LOCALAPPDATA%\SourceCapsule\native-host, registering it for Chrome, Edge, and Brave.
The registered host is a two-line .cmd that runs your signed node.exe, so Windows Smart App
Control allows it.
Run the installer from an ordinary PowerShell window. A shell opened inside a packaged app, such as the Claude desktop app, redirects the files and registry keys into that app's private copy, where a normally started browser cannot see them. The installer detects this and exits with an error instead of reporting success.
Then load dist\sourcecapsule-extension as an unpacked extension and confirm its ID reads
gaclgcfljpjojddiikddejenlnjaggie. The ID is pinned by a key in the manifest, because the
native messaging host has to name a fixed extension origin.
Verify the bridge with:
node scripts\sourcecapsule-capture.mjs --ping
--load-extension installs at Chromium's COMMAND_LINE location. The browser records it in the
profile but does not load it again on a start that lacks the flag, so an ordinary restart has
no SourceCapsule extension at all: no service worker, no native port, and a CLI that cannot reach
the host. The worker is not asleep; it does not exist.
Install a launcher that always passes the flag:
powershell -ExecutionPolicy Bypass -File scripts\start-sourcecapsule-browser.ps1 -InstallShortcut -InstallStartup
That puts Brave with SourceCapsule on the Desktop, in the Start Menu, and in Startup. Start the browser from it and the bridge is always there. Starting it any other way silently drops the extension.
The launcher also passes --disable-features=CalculateNativeWinOcclusion, and the capture needs
it. The capture window is deliberately unfocused, Windows occlusion tracking treats a fully
covered window like a hidden tab, and a browser without the flag fetches a thread's conversation
and never renders it: the capsule comes back holding the root post alone. If you installed the
shortcut before this flag existed, run -InstallShortcut -InstallStartup again.
To check or repair the current state:
powershell -ExecutionPolicy Bypass -File scripts\start-sourcecapsule-browser.ps1 -Status
powershell -ExecutionPolicy Bypass -File scripts\start-sourcecapsule-browser.ps1 -Restart -Verify
-Status reports whether the running browser has the flag and exits nonzero when it does not.
-Restart closes it and starts it correctly; a browser that is already running without the flag
cannot be fixed by launching it again, because the second launch just hands its arguments to the
existing process. -Verify then polls --ping until the bridge answers, so the launcher cannot
report success while the bridge is still down.
The alternative is a one-time Load unpacked of dist\sourcecapsule-extension from
brave://extensions, which records the extension at the UNPACKED location and survives restarts
without any launcher. It is a manual UI step, and it cannot coexist with the command-line copy:
same key, same extension ID.
Google Chrome needs Load unpacked. Since Chrome 137, branded Google Chrome
ignores --load-extension.
The launcher picks Brave, then Edge, then Chrome. When it ends up on Chrome, it leaves out
--load-extension, says so, and still passes the occlusion flag. Load the extension once from
chrome://extensions (Developer mode, Load unpacked, select dist\sourcecapsule-extension).
Then start Chrome from the shortcut. -Status cannot see an unpacked extension on a command
line, so on Chrome confirm with --ping. Chromium and Chrome for Testing still honor the flag.
The browser spawns native messaging hosts; nothing outside can dial into the browser. So the
service worker holds a long-lived port to the host, the host owns a Windows named pipe, and the
CLI connects to that pipe. One host owns the pipe at a time, which is also what enforces one
capture at a time. Timeouts are bounded at every hop and the URL is validated against a canonical
x.com/<handle>/status/<id> pattern in both the CLI and the extension.
Specified native messaging host not found. That is almost always a start without
--load-extension (or, on Google Chrome, a missing Load unpacked), not a bad registration.requestAnimationFrame
paused and timers throttled, which starves the media force-load pass and manufactures
strict-mode blockers.| Content | Result |
|---|---|
| Text, headings, lists, links | Preserved in reading order |
| Same-author thread continuations | Captured best-effort, with explicit post boundaries |
| Images | Inlined at the highest accessible resolution |
| Quoted posts | Rebuilt as selectable cards using X syndication data |
| Downloadable MP4 | Embedded in a full offline HTML archive |
| HLS-only or blocked video | Poster and source link; clearly marked incomplete |
| Agent bundle | Markdown plus real image/poster files; raw video excluded |
| Replies to a post | Threaded Markdown + CSV with full text; media as links only (experimental) |
| Provenance | Source URL, author, timestamp, warnings, and capture manifest |
An archive never claims more than it actually preserved. Missing media, preview-only long-form quotes, and incomplete video are visible in both the rendered output and manifest. For long-form (note) posts, SourceCapsule recovers the full text when X has already delivered it to your browser while the page was open (no extra requests); otherwise the preview is clearly flagged as truncated.
Open the userscript-manager menu while on x.com:
Requirements: Node.js 18+.
cd SourceCapsule
npm.cmd install
npm.cmd test
npm.cmd run lint
npm.cmd run format:check
npm.cmd run build:extension
The automated suite covers the DOM-free renderer, jsdom extraction/thread behavior, share Worker, and extension packaging. It does not replace manual testing against X's live DOM.
noindex and restrictive security headers.Do not share sensitive posts through a public deployment until authentication and abuse controls exist. “Unguessable” is useful access control for v1 testing, not a replacement for user accounts.
MIT. See LICENSE.