birdclaw

birdclaw 🪶 — Local Twitter memory in SQLite: archives, DMs, likes, bookmarks

birdclaw is a local-first Twitter workspace: archive import, cached live reads, focused triage, and reply flows in one local web app + CLI. Built by @steipete.

Status: WIP. Real and usable. Not done. Expect schema churn, transport gaps, and rough edges while the core settles.

What It Does

What Works Today

Local data + storage

Web UI

Triage + filtering

Actions

Safety

Still In Progress

If you need polished product-grade sync parity today, this is not there yet.

Screens

Storage

Default root:

~/.birdclaw

Important paths:

Override the root:

export BIRDCLAW_HOME=/path/to/custom/root

Requirements

Install

Homebrew:

brew install steipete/tap/birdclaw

From source:

fnm use
pnpm install

Run

pnpm dev

Open:

http://localhost:3000

Quick Start

Initialize local state:

birdclaw init
birdclaw auth status --json
birdclaw db stats --json

Find and import an archive:

birdclaw archive find --json
birdclaw import archive --json
birdclaw import archive ~/Downloads/twitter-archive-2025.zip --json
birdclaw import hydrate-profiles --json

Back up the local SQLite store as canonical JSONL text:

birdclaw backup sync --repo ~/Projects/backup-birdclaw --remote https://github.com/steipete/backup-birdclaw.git --json

Merge the backup into the current BIRDCLAW_HOME:

birdclaw backup import ~/Projects/backup-birdclaw --json

Start the app:

birdclaw serve

First 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 --json

CLI Highlights

Search local tweets

pnpm cli search tweets "local-first" --json
pnpm cli search tweets "sync engine" --limit 20 --json
pnpm cli search tweets --since 2020-01-01 --until 2021-01-01 --originals-only --hide-low-quality --limit 500 --json
pnpm cli search tweets --liked --limit 20 --json
pnpm cli search tweets --bookmarked --limit 20 --json

Sync likes, bookmarks, and home timeline

auto tries xurl first, then falls back to bird. Use bird directly when the API path is unavailable for the account/token you have locally.

pnpm cli sync likes --mode auto --limit 100 --refresh --json
pnpm cli sync bookmarks --mode auto --limit 100 --refresh --json
pnpm cli sync bookmarks --mode bird --all --max-pages 5 --limit 100 --refresh --json
pnpm cli sync timeline --limit 100 --refresh --json
pnpm cli sync mention-threads --limit 30 --delay-ms 1500 --timeout-ms 15000 --json

Export mentions for agents

Default birdclaw mode returns normalized items with text, plainText, markdown, author metadata, and canonical URLs:

pnpm cli mentions export "agent" --unreplied --limit 10

Cached 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 5

Home config lives in ~/.birdclaw/config.json. Example:

{
	"actions": {
		"transport": "auto"
	},
	"mentions": {
		"dataSource": "bird",
		"birdCommand": "/Users/steipete/Projects/bird/bird"
	}
}

Notes:

Research bookmarks and threads

birdclaw research turns bookmarked tweets into a markdown brief with local thread expansion, live ancestor lookup when needed, and extracted links/handles:

birdclaw research "codex" --limit 20 --thread-depth 10 --json
birdclaw research --account acct_primary --out ~/research/codex.md

Search and triage DMs

pnpm cli search dms "prototype" --json
pnpm cli search dms "layout" --min-followers 1000 --min-influence-score 120 --sort influence --json
pnpm cli dms sync --limit 50 --refresh --json
pnpm cli dms list --refresh --limit 10 --json
pnpm cli dms list --unreplied --min-followers 500 --min-influence-score 90 --sort influence --json

AI inbox

pnpm cli inbox --json
pnpm cli inbox --kind dms --limit 10 --json
pnpm cli inbox --score --hide-low-signal --limit 8 --json

Blocklist

pnpm 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 --json

Notes:

Example blocklist file:

# crypto / AI slop
@jpctan
@SystemDaddyAi
- @Pepe202579 memecoin bait
https://x.com/someone/status/2030857479001960633?s=20

Profile reply scan

pnpm cli profiles replies @jpctan --limit 12 --json

Notes:

Typical tell:

Mutes

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 --json

Notes:

Test env hardening

Compose / reply

pnpm cli compose post "Ship local software."
pnpm cli compose reply tweet_004 "On it."
pnpm cli compose dm dm_003 "Send it over."

Text Backup

birdclaw backup export writes deterministic JSONL shards that can rebuild the local SQLite index without committing SQLite WAL/SHM files, FTS shadow tables, or transient live caches.

Layout:

manifest.json
data/accounts.jsonl
data/profiles.jsonl
data/tweets/YYYY.jsonl
data/tweets/unknown.jsonl
data/collections/likes.jsonl
data/collections/bookmarks.jsonl
data/dms/conversations.jsonl
data/dms/YYYY.jsonl
data/moderation/blocks.jsonl
data/moderation/mutes.jsonl

Tweets are sharded by year for human browsing and yearly analysis. Collection-only tweets whose real creation date is unknown go into data/tweets/unknown.jsonl instead of pretending they belong to 1970. DMs are sharded by year with conversation_id in each row; this keeps Git fast while preserving conversation membership. Likes and bookmarks are stored as collection edges in data/collections and mirrored into the timeline rows for current query compatibility.

Use backup sync when the target is a private Git repo. It pulls first, merge-imports the remote backup into local SQLite, exports the local union back into text shards, commits, and pushes.

pnpm cli backup sync --repo ~/Projects/backup-birdclaw --remote https://github.com/steipete/backup-birdclaw.git --json
pnpm cli backup validate ~/Projects/backup-birdclaw --json

Configure stale-aware backup reads in ~/.birdclaw/config.json:

{
	"backup": {
		"repoPath": "/Users/steipete/Projects/backup-birdclaw",
		"remote": "https://github.com/steipete/backup-birdclaw.git",
		"autoSync": true,
		"staleAfterSeconds": 900
	}
}

Read paths such as CLI search, inbox, API status/query, and web startup pull + merge from Git only when the last backup check is stale. Data-changing commands run a full backup sync afterward when this config is enabled. Set BIRDCLAW_BACKUP_AUTO_SYNC=0 to disable that behavior for one process.

Scheduled Bookmark Sync

birdclaw jobs sync-bookmarks refreshes live bookmarks and appends one JSONL audit entry per run. Each entry includes host, timestamps, duration, before/after bookmark counts, source transport, fetched count, backup sync result, and any error.

birdclaw --json jobs sync-bookmarks --mode auto --limit 100 --max-pages 5 --refresh
tail -n 5 ~/.birdclaw/audit/bookmarks-sync.jsonl | jq .

After a successful bookmark refresh, the job runs the normal backup auto-sync path. If ~/.birdclaw/config.json has backup.autoSync enabled, the changed local data is merged into the configured Git backup repo, committed, and pushed. The audit entry records that backup result so scheduled runs are inspectable later.

On macOS, install the 3-hour LaunchAgent after choosing the Birdclaw executable path for that machine:

birdclaw --json jobs install-bookmarks-launchd --program /opt/homebrew/bin/birdclaw

If the machine uses bird with browser cookies that are not available to launchd, write an export-only env file with mode 0600 and install with --env-file ~/.config/bird/env.sh. Birdclaw sources that file inside the scheduled process without storing the secrets in the plist.

The LaunchAgent writes ~/Library/LaunchAgents/com.steipete.birdclaw.bookmarks-sync.plist, runs at load, then every 10,800 seconds. It writes the audit log to ~/.birdclaw/audit/bookmarks-sync.jsonl and stdout/stderr to ~/.birdclaw/logs/bookmarks-sync.*.log. A lock file prevents overlapping runs and records an already-running skip when needed. The default job fetches up to 5 pages every 3 hours; pass --all if you want every retrievable page each run.

Useful checks:

launchctl print gui/$(id -u)/com.steipete.birdclaw.bookmarks-sync
launchctl kickstart -k gui/$(id -u)/com.steipete.birdclaw.bookmarks-sync
tail -n 1 ~/.birdclaw/audit/bookmarks-sync.jsonl | jq .

Typical Workflow

  1. import your archive if you have one
  2. hydrate imported profiles from live Twitter metadata
  3. use Home for reading
  4. use Mentions for reply triage
  5. when one account feels borderline, inspect profiles replies
  6. collect keepers into a blocklist file and run blocks import
  7. use DMs for high-context conversation work
  8. use Inbox when you want AI help cutting noise
  9. use CLI exports when agents need stable JSON

Live Transport

Current preference:

Without xurl or bird, birdclaw still works in local/archive mode.

Check transport:

pnpm cli auth status --json

Architecture

Testing

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 e2e

Current bar:

CI

GitHub Actions runs:

Workflow: ci.yml

Docs