On this page
01 · Quickstart
Go from an archive to readable branches.
Use a one-shot build when you have a personal export and want an answer now. Nothing is imported into Ariadne's persistent library.
Ariadne follows the parent IDs your sources know about. Missing posts remain visible as unavailable placeholders; use --strict when you would rather fail than keep a partial branch.
1. Install the command
uv tool install ariadne-x
ariadne --help2. Build Markdown from a personal archive
ariadne build \
--archive ~/Downloads/twitter-archive.zip \
--for-user alice \
--since 2024-01-01 \
--format markdown \
--output conversations.mdReplace alice with the archive owner's username. The file contains one retained root-to-target branch per section, including attribution, quote context, warnings, and placeholders where a post could not be resolved.
Prefer prompts?
Run ariadne interactive for a guided conversation build. The local dump explorer is a different command: ariadne dumps interactive.
02 · Choose an input
Start with the data you already have.
Build without importing
--archive PATHUse a personal export or Community Archive folder, ZIP, or data file once.
Import into the library
ariadne dumps import PATHNormalize and index archives you want to search or combine repeatedly.
Load generic records
--tweets-file PATHFields survive when the source provides them; sparse input may not contain reply edges.
Pass IDs or X URLs
ariadne build [ITEM ...]Combine positional items with local sources or an explicitly enabled API fallback.
Try cheap network sources
--target-user USEREnables unofficial RSS and oEmbed by default. It is convenient, not a completeness guarantee.
Use its public thread API
ariadne bluesky HANDLENo authentication is required; output uses the same five renderers.
Install as a library
python3 -m pip install ariadne-x
# Add DuckDB only for Parquet imports
uv tool install 'ariadne-x[parquet]'Ariadne requires Python 3.11 or newer. The distribution is named ariadne-x; both the command and import package are named ariadne. Personal archives, Community CSV/ZIP, and generic files work without the Parquet extra.
03 · How it works
Target to root for lookup. Root to target for output.
- 01LoadNormalize archives, files, dumps, and cache.
- 02SelectChoose IDs or matching user posts as targets.
- 03FollowWalk each known reply-parent chain toward its root.
- 04AttachAdd quote paths when the selected sources provide them.
- 05RenderPrune subset branches and write root-to-target output.
Branches, not whole trees
A target includes its ancestors. Sibling replies are not discovered or appended.
Dates select targets
--since does not discard older ancestors needed by a selected branch.
Partial data stays visible
Unresolved posts produce warnings and placeholders unless --strict is set.
Quotes have two jobs
Quote context is attached separately. A root quote-tweet is spliced onto its quoted post by default.
The default ancestor limit is 50. Use --max-depth, --no-quotes, or --no-quote-as-reply to change the reconstruction policy.
04 · Archive library
Import once, then search and combine locally.
A persistent import is a normalized SQLite database under ~/.ariadne/dumps, or $ARIADNE_HOME/dumps. Later queries no longer need the source. Removing an import never removes or edits that source.
ariadne dumps import ~/Downloads/twitter-archive.zip --name personal
ariadne dumps import ~/DATA/community.zip --name community
ariadne dumps list
ariadne dumps search 'remembered phrase' --user alice
ariadne dumps user alice --since 2024-01-01 --limit 50
ariadne dumps show https://x.com/alice/status/1234567890123456789| Kind | Accepted input | Explicit flag |
|---|---|---|
| Personal X/Twitter archive | folder, ZIP, or archive data file | --kind twitter-archive |
| Community Archive export | CSV directory or ZIP | --kind community-csv |
| Parquet collection | one .parquet file or a directory | --kind parquet |
| Generic tweet dump | CSV, JSON, JSONL, or NDJSON | --kind tweets-file |
- All imported dumps participate in a build automatically. Repeat
--dump NAMEto narrow the set, or use--no-dumpsfor an isolated run. - Duplicate post IDs are merged across selected dumps, preserving complementary metadata.
--no-ftsskips the full-text index; search then falls back to slower substring matching.- More than 10,000 matching local posts requires a narrower
--sinceor explicit--dump-limit.
05 · Network sources
Know which sources have text and which have edges.
A reply branch can continue only when a source knows the parent post ID. Text-only sources can repair content, but they cannot infer the missing edge. Ordinary local builds do not make network requests.
| Source | Finds targets | Reply IDs | Enabled by |
|---|---|---|---|
| Local archives and dumps | yes, when present | yes, when present | Automatic |
| Community Archive | donor accounts | yes | --community-archive |
| oEmbed | no | no; content only | --oembed or --target-user |
| Nitter / XCancel RSS | recent candidates | usually no | --unofficial-rss or --target-user |
| twitterapi.io | yes | yes | API key; potentially paid |
| X API v2 | yes with timeline fetch | yes | Bearer token; potentially billable |
Public-account convenience mode
--target-user alice tries the local store and cache, then the default Nitter/XCancel-style RSS endpoints and oEmbed. Those public services are unsupported, fragile, commonly recent-only, and usually lack parent IDs. Disable them with--no-unofficial-rss --no-oembed.
# Permit official X API reads after cheap sources
ariadne build \
--target-user alice \
--fetch \
--fetch-user-timeline \
--max-user-pages 2 \
--format raft \
--output alice.jsonl--fetch resolves selected posts and missing parents; --fetch-user-timelineenumerates the user timeline. Both require an X bearer token and may consume billable API reads. Community Archive is donor-scoped; twitterapi.io is a separate potentially paid gateway.
06 · Output formats
Choose the representation your next step needs.
| Format | Encoding | Best for |
|---|---|---|
messages | JSON | Enriched chat-like messages with tweet metadata. This is the CLI default. |
openai | JSON | The same conversation envelope, with each nested message reduced to role, name, and content. |
json | JSON | Normalized tweets, root-to-target branches, quote paths, warnings, and provenance. |
markdown | Text | A readable view with attribution, unavailable placeholders, quotes, and warnings. |
raft | JSONL | One retrieval document per retained branch, ready for chunking or embedding. |
# Read it
ariadne build --archive archive.zip --for-user alice --format markdown
# Feed retrieval
ariadne build --archive archive.zip --for-user alice --format raft -o branches.jsonl
# Keep the normalized graph
ariadne build --archive archive.zip --for-user alice --format json -o graph.jsonIn messages and openai, the root is assistant and later posts are user. In raft, later posts are participant.
07 · Python API
Run the same pipeline without a file round-trip.
The public API is typed and synchronous. Pass keywords for compact calls, or use BuildOptions when configuration should be reusable and inspectable.
from pathlib import Path
import ariadne
options = ariadne.BuildOptions(
archive=Path("twitter-archive.zip"),
for_user="alice",
since="2024-01-01",
no_dumps=True, # keep this run isolated from persistent imports
)
result = ariadne.build(options)
for conversation in result:
print(conversation.target_id)
documents = result.raft_documents()
result.save("out/branches.jsonl", "raft")result.conversationsReconstructed branch objectsresult.tweetsNormalized tweets used by the resultresult.warningsProvider and data-quality diagnosticsresult.render(format)Any renderer as textresult.messages()Parsed enriched or strict messagesresult.json_payload()Parsed graph payloadHandle stable API errors
try:
result = ariadne.build(for_user="alice")
except ariadne.NoTargetsError:
print("No posts matched the target selection")
except ariadne.AriadneError as exc:
print(f"Ariadne could not build: {exc}")Imported dumps are used automatically unless no_dumps=True. Multi-value options accept one value or an iterable, filesystem inputs accept PathLike, and credentials are excluded from option representations. In async applications, run build in a worker thread.
08 · Troubleshooting
Common problems have explicit fixes.
No posts matched
Supply a tweet ID/URL or a target selector such as --for-user, --author-id, or --all-loaded. Check that --since is not too narrow.
A parent is unavailable
None of the selected sources resolved that ID. Add a richer archive or explicitly enable a structured network source. Use --strict only if partial branches should fail.
A local timeline exceeds 10,000 posts
Add a narrower --since date or an explicit --dump-limit. Explicit limits keep the newest matching posts.
A Parquet import asks for DuckDB
Install the optional dependency with uv tool install 'ariadne-x[parquet]'.
Search is unexpectedly slow
The import may have been created with --no-fts. Re-import it without that flag to build the full-text index.
Unrelated imported posts appear in a build
Persistent dumps join builds by default. Restrict them with repeatable --dump NAME, or disable them with --no-dumps.
Reference and development
uv sync --extra dev --extra parquet
uv run pytest
uv run ruff check .
uv run mypy src