birdclaw is a local-first X workspace: archive import, cached live reads, focused triage, and reply flows in one local web app + CLI.
Status: WIP. Real and usable. Not done. Expect schema churn, transport gaps, and rough edges while the core settles.
- keeps your X data in local SQLite
- stores media and avatar cache under
~/.birdclaw - imports archives when you have them
- still works when you do not
- gives you a clean local UI for home, mentions, DMs, inbox, and blocks
- exposes scriptable JSON for agents and automation
- one shared SQLite DB for multiple accounts
- FTS5 search over tweets and DMs
- archive autodiscovery on macOS
- archive import for tweets, likes, profiles, and full DMs
- profile hydration from live X metadata
- local avatar cache
- local media cache root under
~/.birdclaw
HometimelineMentionsqueueDMsworkspace with two-column layoutInboxfor mixed mention + DM triageBlocksfor local blocklist maintenance- constrained timeline lane instead of full-width dashboard UI
- tweet expansion with URLs, inline images, quoted tweets, replies, and profile hover cards
- sender bio and influence context in the DM detail header
- system / light / dark theme switcher with animated transition
- replied / unreplied filters for timelines
- DM filters by participant, followers, and derived influence score
- AI-ranked inbox for mentions + DMs
- OpenAI scoring hook for low-signal filtering
- cached live mentions export in
xurl-compatible JSON - live profile-reply inspection for borderline AI/slop triage
- one-shot blocklist import from a file for batch moderation passes
- post tweets
- reply to tweets
- reply to DMs
- add / remove local blocks
- import batch blocklists in one call
- add / remove local mutes
- sync remote blocks through
xurlwhen available - fall back to the X web cookie session when OAuth2 block writes are rejected
- local-first by default
- tests disable live writes
- CI disables live writes
- app has no auth layer because it is a local-only tool
- broader resumable live sync beyond the targeted paths already wired
- fuller media fetch pipeline
- richer multi-account UX
- more complete transport coverage
- more archive edge-case handling
If you need polished product-grade sync parity today, this is not there yet.
Home: read and reply without fighting the main X timelineMentions: work the reply queue with clean filtersDMs: triage by sender context, follower count, and influenceInbox: let heuristics / OpenAI float likely-important itemsBlocks: maintain a local-first account-scoped blocklist
Default root:
~/.birdclaw Important paths:
- DB:
~/.birdclaw/birdclaw.sqlite - media cache:
~/.birdclaw/media - avatar cache:
~/.birdclaw/media/thumbs/avatars - Playwright test home:
.playwright-home
Override the root:
export BIRDCLAW_HOME=/path/to/custom/root- Node
25.8.1 pnpm- macOS recommended for Spotlight archive discovery
xurloptional for live reads / writes- OpenAI API key optional for inbox scoring
fnm use pnpm installpnpm devOpen:
http://localhost:3000 Initialize local state:
pnpm cli init pnpm cli auth status --json pnpm cli db stats --jsonFind and import an archive:
pnpm cli archive find --json pnpm cli import archive --json pnpm cli import archive ~/Downloads/twitter-archive-2025.zip --json pnpm cli import hydrate-profiles --jsonStart the app:
pnpm devFirst moderation pass:
pnpm cli mentions export --mode xurl --refresh --all --max-pages 9 --limit 100 pnpm cli profiles replies @borderline_handle --limit 12 --json pnpm cli blocks import ~/triage/blocklist.txt --account acct_primary --jsonpnpm cli search tweets "local-first" --json pnpm cli search tweets "sync engine" --limit 20 --jsonDefault birdclaw mode returns normalized items with text, plainText, markdown, author metadata, and canonical URLs:
pnpm cli mentions export "agent" --unreplied --limit 10Cached live modes return xurl-compatible data/includes/meta, but stay in the local SQLite cache so repeat reads do not keep spending live calls:
pnpm cli mentions export --mode bird --limit 20 pnpm cli mentions export --mode bird --refresh --limit 20 pnpm cli mentions export --mode xurl --limit 5 pnpm cli mentions export --mode xurl --refresh --limit 5 pnpm cli mentions export --mode xurl --refresh --all --max-pages 9 --limit 100 pnpm cli mentions export "courtesy" --mode xurl --limit 5Home config lives in ~/.birdclaw/config.json. Example:
{ "actions": { "transport": "auto" }, "mentions": { "dataSource": "bird", "birdCommand": "/Users/steipete/Projects/bird/bird" } }Notes:
--refreshforces a live fetch--cache-ttl <seconds>tunes freshness--allwalks every retrievable mentions page;--max-pagescaps that scan- in paged
xurlmode,--limitis the per-page size mentions.dataSourcecontrols live mention reads onlyactions.transportcontrols live block/mute writes onlyactions.transportacceptsauto,bird, orxurlbirdmode uses your localbirdCLI and caches its mentions output into birdclaw's canonical store- filters still work in
xurlmode; filtered payloads are rebuilt from the local canonical store after sync
pnpm cli search dms "prototype" --json pnpm cli search dms "layout" --min-followers 1000 --min-influence-score 120 --sort influence --json pnpm cli dms list --unreplied --min-followers 500 --min-influence-score 90 --sort influence --jsonpnpm cli inbox --json pnpm cli inbox --kind dms --limit 10 --json pnpm cli inbox --score --hide-low-signal --limit 8 --jsonpnpm cli blocks list --account acct_primary --json pnpm cli blocks sync --account acct_primary --json pnpm cli blocks import ~/triage/blocklist.txt --account acct_primary --json pnpm cli blocks add @amelia --account acct_primary --json pnpm cli blocks record @amelia --account acct_primary --json pnpm cli blocks remove @amelia --account acct_primary --json pnpm cli ban @amelia --account acct_primary --transport auto --json pnpm cli unban @amelia --account acct_primary --transport bird --jsonNotes:
ban/unbanaccept--transport auto|bird|xurlautotriesbirdfirst, then falls back toxurlwhen bird fails- forced
xurlwrites still verify throughbird statusbefore sqlite changes - X still rejects pure OAuth2 block writes, so
autois the safe default for block/unblock blocks importaccepts newline-delimited blocklists with comments and markdown bulletsblocks syncis for slow/manual remote reconciliation; not for a hot cron loopblocks recordstores a known-good remote block locally without issuing another live write
Example blocklist file:
# crypto / AI slop @jpctan @SystemDaddyAi - @Pepe202579 memecoin bait https://x.com/someone/status/2030857479001960633?s=20 pnpm cli profiles replies @jpctan --limit 12 --jsonNotes:
- for the "unsure if AI" case
- scans recent authored tweets, excludes retweets, keeps replies
- useful for spotting repeated generic praise, abstraction soup, or cross-thread templated cadence
Typical tell:
- same upbeat, generic reply shape across unrelated threads in a short time window
pnpm cli mutes list --account acct_primary --json pnpm cli mute @amelia --account acct_primary --transport xurl --json pnpm cli mutes record @amelia --account acct_primary --json pnpm cli unmute @amelia --account acct_primary --transport auto --jsonNotes:
mute/unmuteaccept--transport auto|bird|xurlautotriesbirdfirst, then falls back toxurlwhen bird fails- forced
xurlwrites still verify throughbird statusbefore sqlite changes mutes recordstores a known-good remote mute locally without issuing another live write
- Playwright strips inherited
--localstorage-filefromNODE_OPTIONSbefore starting Vite - this avoids cross-repo test warnings when another repo injected that flag
pnpm cli compose post "Ship local software." pnpm cli compose reply tweet_004 "On it." pnpm cli compose dm dm_003 "Send it over."- import your archive if you have one
- hydrate imported profiles from live X metadata
- use
Homefor reading - use
Mentionsfor reply triage - when one account feels borderline, inspect
profiles replies - collect keepers into a blocklist file and run
blocks import - use
DMsfor high-context conversation work - use
Inboxwhen you want AI help cutting noise - use CLI exports when agents need stable JSON
Current preference:
xurlfirst
Without xurl, birdclaw still works in local/archive mode.
Check transport:
pnpm cli auth status --json- SQLite is the canonical local truth
- archive import and live transport should converge on the same model
- CLI and web UI share the same normalized core
- AI ranking is layered on top of local data, not the source of truth
fnm exec --using 25.8.1 pnpm check fnm exec --using 25.8.1 pnpm test fnm exec --using 25.8.1 pnpm coverage fnm exec --using 25.8.1 pnpm build fnm exec --using 25.8.1 pnpm e2eCurrent bar:
- branch coverage above
80% - Playwright coverage for core UI flows
GitHub Actions runs:
pnpm checkpnpm coveragepnpm buildpnpm e2e
Workflow: ci.yml