birdclaw 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/CHANGELOG.md +33 -1
  2. package/README.md +108 -7
  3. package/package.json +24 -23
  4. package/playwright.config.ts +1 -0
  5. package/public/favicon.ico +0 -0
  6. package/public/logo192.png +0 -0
  7. package/public/logo512.png +0 -0
  8. package/public/manifest.json +2 -2
  9. package/src/cli.ts +450 -18
  10. package/src/components/AppNav.tsx +66 -29
  11. package/src/components/AvatarChip.tsx +10 -5
  12. package/src/components/ConversationThread.tsx +125 -0
  13. package/src/components/DmWorkspace.tsx +96 -98
  14. package/src/components/EmbeddedTweetCard.tsx +20 -14
  15. package/src/components/InboxCard.tsx +92 -90
  16. package/src/components/LinkPreviewCard.tsx +270 -0
  17. package/src/components/ProfilePreview.tsx +8 -3
  18. package/src/components/SavedTimelineView.tsx +48 -38
  19. package/src/components/TimelineCard.tsx +228 -84
  20. package/src/components/TweetRichText.tsx +6 -2
  21. package/src/lib/archive-import.ts +1565 -65
  22. package/src/lib/authored-live.ts +1074 -0
  23. package/src/lib/backup.ts +316 -14
  24. package/src/lib/bird-actions.ts +1 -10
  25. package/src/lib/bird-command.ts +57 -0
  26. package/src/lib/bird.ts +89 -5
  27. package/src/lib/config.ts +1 -1
  28. package/src/lib/db.ts +191 -4
  29. package/src/lib/follow-graph.ts +1053 -0
  30. package/src/lib/link-index.ts +11 -98
  31. package/src/lib/link-insights.ts +834 -0
  32. package/src/lib/link-preview-metadata.ts +334 -0
  33. package/src/lib/media-fetch.ts +787 -0
  34. package/src/lib/media-includes.ts +165 -0
  35. package/src/lib/mention-threads-live.ts +535 -43
  36. package/src/lib/mentions-live.ts +623 -19
  37. package/src/lib/moderation-target.ts +6 -0
  38. package/src/lib/profile-hydration.ts +1 -1
  39. package/src/lib/profile-resolver.ts +115 -1
  40. package/src/lib/queries.ts +233 -7
  41. package/src/lib/seed.ts +127 -8
  42. package/src/lib/timeline-collections-live.ts +145 -22
  43. package/src/lib/timeline-live.ts +10 -15
  44. package/src/lib/tweet-account-edges.ts +9 -2
  45. package/src/lib/tweet-render.ts +97 -0
  46. package/src/lib/types.ts +185 -2
  47. package/src/lib/ui.ts +375 -147
  48. package/src/lib/url-expansion-store.ts +110 -0
  49. package/src/lib/url-expansion.ts +20 -3
  50. package/src/lib/x-profile.ts +7 -2
  51. package/src/lib/xurl.ts +296 -12
  52. package/src/routeTree.gen.ts +105 -0
  53. package/src/routes/__root.tsx +7 -3
  54. package/src/routes/api/conversation.tsx +34 -0
  55. package/src/routes/api/link-insights.tsx +79 -0
  56. package/src/routes/api/link-preview.tsx +43 -0
  57. package/src/routes/api/profile-hydrate.tsx +51 -0
  58. package/src/routes/blocks.tsx +111 -86
  59. package/src/routes/dms.tsx +90 -78
  60. package/src/routes/inbox.tsx +98 -86
  61. package/src/routes/index.tsx +63 -50
  62. package/src/routes/links.tsx +883 -0
  63. package/src/routes/mentions.tsx +63 -50
  64. package/src/styles.css +106 -43
package/CHANGELOG.md CHANGED
@@ -1,6 +1,31 @@
1
1
  # CHANGELOG
2
2
 
3
- ## Unreleased
3
+ ## 0.5.0 - 2026-05-15
4
+
5
+ ### Added
6
+
7
+ - Add `birdclaw import archive --select` for importing targeted archive slices while preserving unselected local data.
8
+ - Add `birdclaw sync authored` for filling own-tweet gaps from `xurl` after the archive cutoff. Thanks @cavit99.
9
+ - Add live `sync mentions` and `sync mention-threads --mode xurl` ingestion for current mention data and conversation context. Thanks @cavit99.
10
+ - Add `birdclaw media fetch` plus archive bundled-media extraction and live media variant persistence for local originals caching. Thanks @cavit99.
11
+ - Add a `/links` web lane for Hacker News-style top URL and video-provider insights with today, week, month, year, and all-time ranges.
12
+ - Import archive `follower.js`/`following.js` files into the local follow graph and add archive-authored tweet edges so fresh archive imports are immediately queryable without live sync. Thanks @cavit99.
13
+ - Add cache-first followers/following sync, local follow graph queries, and backup/export support for graph snapshots and churn events. Thanks @ma08.
14
+ - Hydrate missing link-discussion profile avatars through `bird`/`xurl` so hover sheets can upgrade archive placeholders into real profile cards.
15
+ - Add inline tweet conversation expansion in the web timeline, preserving the selected reply's parent chain before broad thread context.
16
+
17
+ ### Changed
18
+
19
+ - Update npm dependencies, including React, Vite, Vitest, Playwright, Tailwind, Kysely, TanStack packages, oxlint, and oxfmt.
20
+
21
+ ### Fixed
22
+
23
+ - Seed demo link insight data before direct `/links` route loads, so the lane is populated even when it is the first web route opened.
24
+ - Isolate the default `bird` command config test from the maintainer's local `~/.birdclaw/config.json`.
25
+ - Skip non-numeric archive placeholder IDs such as self-DM conversation IDs when hydrating profiles through X, so one malformed local ID no longer aborts the batch. Thanks @nfarina.
26
+ - Include expanded short URLs and link occurrences in Git-friendly backups so linked-tweet search survives backup restore.
27
+ - Prefer `bird` for follow graph sync in `auto` mode, keeping `xurl` as an explicit fallback for accounts where OAuth2 follow reads work.
28
+ - Update the docs site and app icons to use the Birdclaw crab-bird mark instead of the generic bird logo.
4
29
 
5
30
  ## 0.4.1 - 2026-05-11
6
31
 
@@ -51,6 +76,13 @@
51
76
  - Allow Playwright e2e runs to use an alternate local port when `3000` is already occupied.
52
77
  - Replace maintainer-local documentation links with repo-relative links and align the setup docs with the Node version file. Thanks @stainlu.
53
78
 
79
+ ## Unreleased
80
+
81
+ ### Fixed
82
+
83
+ - Fix live `xurl` status detection when the CLI is installed but not authenticated; thanks @kyupark.
84
+ - Default local `bird` integration to `bird` on PATH and report stale configured command paths with setup guidance.
85
+
54
86
  ## 0.2.1 - 2026-04-27
55
87
 
56
88
  ### Changed
package/README.md CHANGED
@@ -20,20 +20,27 @@ Status: WIP. Real and usable. Not done. Expect schema churn, transport gaps, and
20
20
  - one shared SQLite DB for multiple accounts, with canonical tweets/profiles and account-scoped timeline/collection edges
21
21
  - FTS5 search over tweets and DMs
22
22
  - archive autodiscovery on macOS
23
- - archive import for tweets, likes, profiles, and full DMs
23
+ - archive import for tweets, likes, followers/following, profiles, and full DMs
24
+ - selective archive re-imports for one stale slice without wiping the rest of the local store
24
25
  - archive import for bookmark exports when present
25
- - live likes and bookmarks sync through `xurl` or `bird`
26
+ - archive import streams bundled media files into the local originals cache and extracts `video_info.variants[]` for video and animated-GIF rows
27
+ - live authored sync through `xurl`, plus likes and bookmarks through `xurl` or `bird`
28
+ - cache-first followers/following sync through `bird` or `xurl`
29
+ - local follow graph queries for top followers, unfollows, mutuals, and non-mutual following
26
30
  - Git-friendly text backups with yearly tweet shards and per-conversation DM shards
27
31
  - profile hydration from live Twitter metadata
28
32
  - profile-change history, affiliation badge edges, and extracted bio entities for local identity lookups
29
33
  - local avatar cache
30
34
  - local media cache root under `~/.birdclaw`
35
+ - live syncers persist tweet media variants so `media fetch` can pull originals from `pbs.twimg.com` and `video.twimg.com` on a separate schedule
36
+ - `media fetch` reuses bytes already extracted by `import archive` before falling back to CDN
31
37
 
32
38
  ### Web UI
33
39
 
34
40
  - `Home` timeline
35
41
  - `Mentions` queue
36
42
  - `Likes` and `Bookmarks` review lanes
43
+ - `Links` for Hacker News-style top URLs, video-provider links, and the comments around them across today/week/month/year/all-time windows
37
44
  - `DMs` workspace with two-column layout
38
45
  - `Inbox` for mixed mention + DM triage
39
46
  - `Blocks` for local blocklist maintenance
@@ -74,7 +81,7 @@ Status: WIP. Real and usable. Not done. Expect schema churn, transport gaps, and
74
81
  ## Still In Progress
75
82
 
76
83
  - broader resumable live sync beyond the targeted paths already wired
77
- - fuller media fetch pipeline
84
+ - thumbnail generation on top of the originals cache
78
85
  - richer multi-account UX
79
86
  - more complete transport coverage
80
87
  - more archive edge-case handling
@@ -102,6 +109,7 @@ Important paths:
102
109
 
103
110
  - DB: `~/.birdclaw/birdclaw.sqlite`
104
111
  - media cache: `~/.birdclaw/media`
112
+ - archive-extracted media: `~/.birdclaw/media/originals/archive/<kind>/<id>/<filename>` where `<kind>` is one of `tweets`, `dms`, `community`, `deleted`, `profile`, `moments`, `dmGroup`
105
113
  - avatar cache: `~/.birdclaw/media/thumbs/avatars`
106
114
  - Playwright test home: `.playwright-home`
107
115
 
@@ -111,6 +119,53 @@ Override the root:
111
119
  export BIRDCLAW_HOME=/path/to/custom/root
112
120
  ```
113
121
 
122
+ ### Media fetch
123
+
124
+ `birdclaw media fetch` fills the local originals cache at
125
+ `~/.birdclaw/media/originals/<media_key>.<ext>` for tweet media URLs already
126
+ stored in `tweets.media_json`. Images come from `pbs.twimg.com`, videos and
127
+ animated GIFs from `video.twimg.com` (highest-bitrate mp4 variant; HLS-only
128
+ media is skipped).
129
+
130
+ Live syncers (`sync mentions`, `sync mention-threads`, `sync likes`, `sync
131
+ bookmarks`, `sync timeline`) persist `media_json` with `variants[]` ride-along
132
+ metadata so `media fetch` has URLs to download from. Archive-imported tweets
133
+ already carry that shape. Before falling back to HTTP, `media fetch` looks for
134
+ bytes already extracted by `import archive` under
135
+ `~/.birdclaw/media/originals/archive/tweets/<tweetId>/` and copies those into
136
+ the canonical path; reuses are counted in the JSON output as
137
+ `reused_from_archive` and never spend CDN bandwidth.
138
+
139
+ Legal posture: this is a respectful client-rendering cache, not a scraper. The
140
+ command never enumerates, crawls, or derives Twitter/X CDN URLs. It only
141
+ fetches URLs that birdclaw already has from an archive or API/live sync
142
+ record, skips files that already exist locally, sends a birdclaw user agent,
143
+ paces image requests sequentially by default, caps optional image parallelism
144
+ at five, runs video downloads serially with their own `--video-pacing-ms`,
145
+ streams response bodies to a `.tmp` file with `Range: bytes=<size>-` resume,
146
+ caps each file at `--max-bytes` (100MB default), backs off on `429`, and
147
+ relies on the local file cache for idempotency.
148
+
149
+ Thumbnail generation and automatic invocation from sync commands are
150
+ intentionally left out. Run it separately, for example from cron or launchd
151
+ every few hours:
152
+
153
+ ```bash
154
+ birdclaw media fetch --json
155
+ birdclaw media fetch --dry-run --limit 20
156
+ birdclaw media fetch --include-video --video-pacing-ms 1500 --max-bytes 209715200 --json
157
+ birdclaw media fetch --no-include-video --parallel 3 --pacing-ms 250 --json
158
+ ```
159
+
160
+ Notes:
161
+
162
+ - `--include-video` is on by default; pass `--no-include-video` for images
163
+ only
164
+ - `--kind`, `--since`, and `--limit` scope which tweet rows are inspected
165
+ - `--parallel` applies to image fetches only; video fetches stay serial
166
+ - JSON output reports `images_fetched`, `videos_fetched`, `gifs_fetched`,
167
+ `reused_from_archive`, and per-kind byte counters
168
+
114
169
  ## Requirements
115
170
 
116
171
  - Node `25.8.1` or Node 26.x
@@ -163,9 +218,26 @@ Find and import an archive:
163
218
  birdclaw archive find --json
164
219
  birdclaw import archive --json
165
220
  birdclaw import archive ~/Downloads/twitter-archive-2025.zip --json
221
+ ```
222
+
223
+ Optional profile hydration can improve bios, follower counts, and avatars, but it performs live X profile reads and can spend API credits on large archives:
224
+
225
+ ```bash
166
226
  birdclaw import hydrate-profiles --json
167
227
  ```
168
228
 
229
+ `import archive` is idempotent. Re-running parses follower/following edges into the local follow graph, streams bundled media files under `data/tweets_media/`, `data/direct_messages_media/`, and the other archive media folders into `~/.birdclaw/media/originals/archive/<kind>/<id>/`, and pulls `video_info.variants[]` so archive video and animated-GIF rows carry mp4 URLs for the live media fetcher. Already-extracted files are skipped when size matches.
230
+
231
+ Re-import only one part of a newer archive when you already have live or local data you want to keep:
232
+
233
+ ```bash
234
+ birdclaw import archive ~/Downloads/twitter-archive-2026.zip --select tweets --json
235
+ birdclaw import archive ~/Downloads/twitter-archive-2026.zip --select likes,bookmarks --json
236
+ birdclaw import archive ~/Downloads/twitter-archive-2026.zip --select directMessages --json
237
+ ```
238
+
239
+ Valid `--select` slices are `tweets`, `likes`, `bookmarks`, `profiles`, `directMessages`, `followers`, and `following`. `dms` and `direct-messages` are accepted aliases for `directMessages`.
240
+
169
241
  Back up the local SQLite store as canonical JSONL text:
170
242
 
171
243
  ```bash
@@ -204,21 +276,47 @@ pnpm cli search tweets --liked --limit 20 --json
204
276
  pnpm cli search tweets --bookmarked --limit 20 --json
205
277
  ```
206
278
 
207
- ### Sync likes, bookmarks, and home timeline
279
+ ### Sync authored tweets, likes, bookmarks, home timeline, and mentions
208
280
 
209
- `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.
281
+ `auto` tries `xurl` first for likes/bookmarks, then falls back to `bird`. Use `bird` directly when the API path is unavailable for the account/token you have locally. For repeated xurl collection syncs, add `--early-stop` to stop paging once a whole page already exists locally; without `--all` or `--max-pages`, it caps at 10 pages.
210
282
 
211
283
  ```bash
284
+ pnpm cli sync authored --mode xurl --limit 100 --json
212
285
  pnpm cli sync likes --mode auto --limit 100 --refresh --json
213
286
  pnpm cli sync bookmarks --mode auto --limit 100 --refresh --json
287
+ pnpm cli sync likes --mode auto --limit 100 --max-pages 5 --early-stop --refresh --json
288
+ pnpm cli sync bookmarks --mode auto --limit 100 --max-pages 5 --early-stop --refresh --json
214
289
  pnpm cli sync bookmarks --mode bird --all --max-pages 5 --limit 100 --refresh --json
215
290
  pnpm cli sync timeline --limit 100 --refresh --json
291
+ pnpm cli sync mentions --mode xurl --limit 100 --max-pages 3 --refresh --json
216
292
  pnpm cli sync mention-threads --limit 30 --delay-ms 1500 --timeout-ms 15000 --json
217
293
  ```
218
294
 
295
+ Mention context is a two-step sync pipeline: run `sync mentions` to ingest recent mention rows with `kind='mention'`, then run `sync mention-threads --mode xurl` to fill parent/root conversation context.
296
+
297
+ ### Follow graph queries
298
+
299
+ Follow graph sync is cache-first and defaults to dry-run so repeated agent queries do not keep spending live reads. `auto` prefers `bird` for this path and falls back to `xurl`.
300
+
301
+ ```bash
302
+ pnpm cli sync followers --json
303
+ pnpm cli sync following --json
304
+ pnpm cli sync followers --yes --json
305
+ pnpm cli sync following --yes --json
306
+ pnpm cli sync followers --mode bird --yes --json
307
+ pnpm cli graph summary --json
308
+ pnpm cli graph events --since 2026-05-01 --json
309
+ pnpm cli graph top-followers --limit 20 --json
310
+ pnpm cli graph unfollowed --date 2026-05-01 --json
311
+ pnpm cli graph non-mutual-following --sort followers --limit 100 --json
312
+ pnpm cli graph mutuals --json
313
+ ```
314
+
315
+ Use `--refresh` only when you intentionally want a new live fetch. The `graph` commands are local SQLite reads and never call X. See [follow-graph.md](docs/follow-graph.md) for long-term agent usage notes.
316
+
219
317
  ### Export mentions for agents
220
318
 
221
- Default `birdclaw` mode returns normalized items with `text`, `plainText`, `markdown`, author metadata, and canonical URLs:
319
+ Default `birdclaw` mode exports DB-backed mention items with `text`, `plainText`, `markdown`, author metadata, and canonical URLs:
222
320
 
223
321
  ```bash
224
322
  pnpm cli mentions export "agent" --unreplied --limit 10
@@ -260,7 +358,7 @@ Notes:
260
358
  - `actions.transport` accepts `auto`, `bird`, or `xurl`
261
359
  - `bird` mode uses your local `bird` CLI and caches its mentions output into birdclaw's canonical store
262
360
  - filters still work in `xurl` mode; filtered payloads are rebuilt from the local canonical store after sync
263
- - `sync likes`, `sync bookmarks`, `sync timeline`, and `sync mention-threads` store live results in the canonical local store; per-account home/mention/like/bookmark membership is kept as edges so shared tweets do not clobber account ownership
361
+ - `sync authored`, `sync mentions`, `sync mention-threads`, `sync likes`, `sync bookmarks`, and `sync timeline` store live results in the canonical local store; per-account authored/home/mention/like/bookmark membership is kept as edges so shared tweets do not clobber account ownership
264
362
 
265
363
  ### Research bookmarks and threads
266
364
 
@@ -510,12 +608,14 @@ pnpm test
510
608
  pnpm coverage
511
609
  pnpm build
512
610
  pnpm e2e
611
+ pnpm perf:browser -- --scenario=links,links-toggle --iterations=5
513
612
  ```
514
613
 
515
614
  Current bar:
516
615
 
517
616
  - branch coverage above `80%`
518
617
  - Playwright coverage for core UI flows
618
+ - browser perf smoke reports ready/action timings plus API endpoint fan-out
519
619
 
520
620
  ## CI
521
621
 
@@ -533,3 +633,4 @@ Workflow: [ci.yml](.github/workflows/ci.yml)
533
633
  - [spec.md](docs/spec.md)
534
634
  - [cli.md](docs/cli.md)
535
635
  - [data-architecture.md](docs/data-architecture.md)
636
+ - [follow-graph.md](docs/follow-graph.md)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "birdclaw",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "Local Twitter memory in SQLite for archives, DMs, likes, bookmarks, and moderation",
5
5
  "homepage": "https://github.com/steipete/birdclaw#readme",
6
6
  "license": "MIT",
@@ -46,46 +46,47 @@
46
46
  "format:check": "oxfmt --check src scripts playwright vite.config.ts vitest.config.ts playwright.config.ts",
47
47
  "lint": "oxlint --import-plugin --node-plugin --vitest-plugin --deny-warnings -A require-mock-type-parameters -A no-control-regex src scripts playwright vite.config.ts vitest.config.ts playwright.config.ts",
48
48
  "check": "pnpm run format:check && pnpm run lint",
49
+ "perf:browser": "node ./scripts/browser-perf.mjs",
49
50
  "cli": "tsx src/cli.ts",
50
51
  "docs:site": "node scripts/build-docs-site.mjs"
51
52
  },
52
53
  "dependencies": {
53
- "@steipete/sweet-cookie": "^0.2.0",
54
- "@tailwindcss/vite": "^4.2.4",
55
- "@tanstack/devtools-vite": "0.6.0",
56
- "@tanstack/react-devtools": "0.10.2",
57
- "@tanstack/react-router": "1.169.1",
54
+ "@steipete/sweet-cookie": "^0.3.0",
55
+ "@tailwindcss/vite": "^4.3.0",
56
+ "@tanstack/devtools-vite": "0.7.0",
57
+ "@tanstack/react-devtools": "0.10.5",
58
+ "@tanstack/react-router": "1.169.2",
58
59
  "@tanstack/react-router-devtools": "1.166.13",
59
60
  "@tanstack/react-router-ssr-query": "1.166.12",
60
- "@tanstack/react-start": "1.167.61",
61
- "@tanstack/router-plugin": "^1.167.32",
61
+ "@tanstack/react-start": "1.167.65",
62
+ "@tanstack/router-plugin": "^1.167.35",
62
63
  "@vitejs/plugin-react": "^6.0.1",
63
64
  "commander": "^14.0.3",
64
- "kysely": "^0.28.17",
65
- "lucide-react": "^1.14.0",
66
- "react": "^19.2.5",
67
- "react-dom": "^19.2.5",
68
- "tailwindcss": "^4.2.4",
69
- "tsx": "^4.21.0",
70
- "vite": "^8.0.10",
71
- "zod": "^4.4.2"
65
+ "kysely": "^0.29.0",
66
+ "lucide-react": "^1.16.0",
67
+ "react": "^19.2.6",
68
+ "react-dom": "^19.2.6",
69
+ "tailwindcss": "^4.3.0",
70
+ "tsx": "^4.22.0",
71
+ "vite": "^8.0.13",
72
+ "zod": "^4.4.3"
72
73
  },
73
74
  "devDependencies": {
74
- "@playwright/test": "^1.59.1",
75
+ "@playwright/test": "^1.60.0",
75
76
  "@tailwindcss/typography": "^0.5.19",
76
77
  "@testing-library/dom": "^10.4.1",
77
78
  "@testing-library/jest-dom": "^6.9.1",
78
79
  "@testing-library/react": "^16.3.2",
79
- "@types/node": "^25.6.0",
80
+ "@types/node": "^25.8.0",
80
81
  "@types/react": "^19.2.14",
81
82
  "@types/react-dom": "^19.2.3",
82
- "@typescript/native-preview": "7.0.0-dev.20260505.1",
83
- "@vitest/coverage-v8": "^4.1.5",
83
+ "@typescript/native-preview": "7.0.0-dev.20260514.1",
84
+ "@vitest/coverage-v8": "^4.1.6",
84
85
  "jsdom": "^29.1.1",
85
- "oxfmt": "^0.47.0",
86
- "oxlint": "^1.62.0",
86
+ "oxfmt": "^0.49.0",
87
+ "oxlint": "^1.64.0",
87
88
  "typescript": "^6.0.3",
88
- "vitest": "^4.1.5"
89
+ "vitest": "^4.1.6"
89
90
  },
90
91
  "engines": {
91
92
  "node": ">=25.8.1 <27"
@@ -28,6 +28,7 @@ export default defineConfig({
28
28
  env: {
29
29
  BIRDCLAW_PLAYWRIGHT_PORT: port,
30
30
  BIRDCLAW_HOME: testHome,
31
+ BIRDCLAW_DISABLE_LIVE_PROFILE_LOOKUP: "1",
31
32
  BIRDCLAW_DISABLE_LIVE_WRITES: "1",
32
33
  },
33
34
  },
Binary file
Binary file
Binary file
@@ -1,6 +1,6 @@
1
1
  {
2
- "short_name": "TanStack App",
3
- "name": "Create TanStack App Sample",
2
+ "short_name": "birdclaw",
3
+ "name": "birdclaw",
4
4
  "icons": [
5
5
  {
6
6
  "src": "favicon.ico",