birdclaw 0.4.1 → 0.5.1

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 (85) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +113 -7
  3. package/bin/birdclaw.mjs +50 -11
  4. package/package.json +30 -28
  5. package/playwright.config.ts +1 -0
  6. package/public/birdclaw-mark.png +0 -0
  7. package/public/favicon.ico +0 -0
  8. package/public/logo192.png +0 -0
  9. package/public/logo512.png +0 -0
  10. package/public/manifest.json +2 -2
  11. package/scripts/browser-perf.mjs +399 -0
  12. package/scripts/build-docs-site.mjs +940 -0
  13. package/scripts/docs-site-assets.mjs +311 -0
  14. package/scripts/run-vitest.mjs +21 -0
  15. package/scripts/sanitize-node-options.mjs +23 -0
  16. package/scripts/start-test-server.mjs +29 -0
  17. package/src/cli.ts +496 -19
  18. package/src/components/AppNav.tsx +66 -29
  19. package/src/components/AvatarChip.tsx +10 -5
  20. package/src/components/BrandMark.tsx +67 -0
  21. package/src/components/ConversationThread.tsx +126 -0
  22. package/src/components/DmWorkspace.tsx +118 -105
  23. package/src/components/EmbeddedTweetCard.tsx +20 -14
  24. package/src/components/FeedState.tsx +147 -0
  25. package/src/components/InboxCard.tsx +104 -90
  26. package/src/components/LinkPreviewCard.tsx +270 -0
  27. package/src/components/ProfilePreview.tsx +8 -3
  28. package/src/components/SavedTimelineView.tsx +89 -71
  29. package/src/components/SyncNowButton.tsx +105 -0
  30. package/src/components/ThemeSlider.tsx +10 -59
  31. package/src/components/TimelineCard.tsx +326 -86
  32. package/src/components/TimelineRouteFrame.tsx +156 -0
  33. package/src/components/TweetMediaGrid.tsx +120 -23
  34. package/src/components/TweetRichText.tsx +19 -4
  35. package/src/components/useTimelineRouteData.ts +137 -0
  36. package/src/lib/api-client.ts +229 -0
  37. package/src/lib/archive-finder.ts +24 -20
  38. package/src/lib/archive-import.ts +1582 -67
  39. package/src/lib/authored-live.ts +1074 -0
  40. package/src/lib/backup.ts +316 -14
  41. package/src/lib/bird-actions.ts +1 -10
  42. package/src/lib/bird-command.ts +57 -0
  43. package/src/lib/bird.ts +89 -5
  44. package/src/lib/config.ts +1 -1
  45. package/src/lib/conversation-surface.ts +174 -0
  46. package/src/lib/db.ts +193 -4
  47. package/src/lib/follow-graph.ts +1053 -0
  48. package/src/lib/link-index.ts +11 -98
  49. package/src/lib/link-insights.ts +834 -0
  50. package/src/lib/link-preview-metadata.ts +334 -0
  51. package/src/lib/media-fetch.ts +787 -0
  52. package/src/lib/media-includes.ts +165 -0
  53. package/src/lib/mention-threads-live.ts +535 -43
  54. package/src/lib/mentions-live.ts +623 -19
  55. package/src/lib/moderation-target.ts +6 -0
  56. package/src/lib/profile-hydration.ts +1 -1
  57. package/src/lib/profile-resolver.ts +115 -1
  58. package/src/lib/queries.ts +326 -35
  59. package/src/lib/seed.ts +127 -8
  60. package/src/lib/timeline-collections-live.ts +145 -22
  61. package/src/lib/timeline-live.ts +10 -15
  62. package/src/lib/tweet-account-edges.ts +9 -2
  63. package/src/lib/tweet-render.ts +97 -0
  64. package/src/lib/types.ts +185 -2
  65. package/src/lib/ui.ts +383 -147
  66. package/src/lib/url-expansion-store.ts +110 -0
  67. package/src/lib/url-expansion.ts +20 -3
  68. package/src/lib/web-sync.ts +443 -0
  69. package/src/lib/x-profile.ts +7 -2
  70. package/src/lib/xurl.ts +296 -12
  71. package/src/routeTree.gen.ts +126 -0
  72. package/src/routes/__root.tsx +7 -3
  73. package/src/routes/api/conversation.tsx +34 -0
  74. package/src/routes/api/link-insights.tsx +79 -0
  75. package/src/routes/api/link-preview.tsx +43 -0
  76. package/src/routes/api/profile-hydrate.tsx +51 -0
  77. package/src/routes/api/sync.tsx +59 -0
  78. package/src/routes/blocks.tsx +111 -86
  79. package/src/routes/dms.tsx +172 -87
  80. package/src/routes/inbox.tsx +98 -86
  81. package/src/routes/index.tsx +22 -115
  82. package/src/routes/links.tsx +928 -0
  83. package/src/routes/mentions.tsx +22 -115
  84. package/src/styles.css +169 -43
  85. package/vite.config.ts +8 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,51 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.5.1 - 2026-05-15
6
+
7
+ ### Fixed
8
+
9
+ - Harden the published CLI wrapper and release checks so the packaged `birdclaw` binary avoids `tsx` CLI IPC startup and stays covered by lint, format, and smoke tests.
10
+ - Forward shutdown signals through the published CLI and bundled web server, and include referenced script helpers in npm packages.
11
+ - Keep the selected DM conversation visible while its thread refreshes so the reply composer no longer flashes away mid-action.
12
+ - Send the selected web account through manual sync controls so multi-account timelines sync the intended profile.
13
+ - Run web sync requests as background jobs with status polling so the UI no longer holds one blocking sync request open.
14
+ - Add typed web API fetch handling and explicit DMs loading/error/empty states so failed local reads surface cleanly.
15
+ - Add explicit web app sync controls for home timeline, mentions, likes, bookmarks, and DMs so fresh live data can be pulled without leaving the UI.
16
+ - Refine the web app sidebar tagline and theme selector so the brand chrome reads more clearly in compact layouts.
17
+ - Add shared web feed loading/error/empty states with timeline-shaped skeleton rows and move conversation expansion into a cached single-thread surface with hover prefetch.
18
+ - Use the Birdclaw crab-bird mark in the web app chrome, loading states, and empty states; soften dark-mode contrast and replace text-only reply warnings with conversation/replied indicators.
19
+ - Allow the local web app to respond when Tailscale Serve forwards requests through the `clawmac.sheep-coho.ts.net` hostname.
20
+ - Speed up the default home timeline load on large local databases and keep malformed archived media URL entities from crashing the web timeline.
21
+ - Preserve tweet media aspect ratios, open timeline media in an inline viewer, and suppress duplicate media URL cards.
22
+
23
+ ## 0.5.0 - 2026-05-15
24
+
25
+ ### Added
26
+
27
+ - Add `birdclaw import archive --select` for importing targeted archive slices while preserving unselected local data.
28
+ - Add `birdclaw sync authored` for filling own-tweet gaps from `xurl` after the archive cutoff. Thanks @cavit99.
29
+ - Add live `sync mentions` and `sync mention-threads --mode xurl` ingestion for current mention data and conversation context. Thanks @cavit99.
30
+ - Add `birdclaw media fetch` plus archive bundled-media extraction and live media variant persistence for local originals caching. Thanks @cavit99.
31
+ - Add a `/links` web lane for Hacker News-style top URL and video-provider insights with today, week, month, year, and all-time ranges.
32
+ - 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.
33
+ - Add cache-first followers/following sync, local follow graph queries, and backup/export support for graph snapshots and churn events. Thanks @ma08.
34
+ - Hydrate missing link-discussion profile avatars through `bird`/`xurl` so hover sheets can upgrade archive placeholders into real profile cards.
35
+ - Add inline tweet conversation expansion in the web timeline, preserving the selected reply's parent chain before broad thread context.
36
+
37
+ ### Changed
38
+
39
+ - Update npm dependencies, including React, Vite, Vitest, Playwright, Tailwind, Kysely, TanStack packages, oxlint, and oxfmt.
40
+
41
+ ### Fixed
42
+
43
+ - Seed demo link insight data before direct `/links` route loads, so the lane is populated even when it is the first web route opened.
44
+ - Isolate the default `bird` command config test from the maintainer's local `~/.birdclaw/config.json`.
45
+ - 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.
46
+ - Include expanded short URLs and link occurrences in Git-friendly backups so linked-tweet search survives backup restore.
47
+ - Prefer `bird` for follow graph sync in `auto` mode, keeping `xurl` as an explicit fallback for accounts where OAuth2 follow reads work.
48
+ - Update the docs site and app icons to use the Birdclaw crab-bird mark instead of the generic bird logo.
49
+
5
50
  ## 0.4.1 - 2026-05-11
6
51
 
7
52
  ### Added
@@ -51,6 +96,13 @@
51
96
  - Allow Playwright e2e runs to use an alternate local port when `3000` is already occupied.
52
97
  - Replace maintainer-local documentation links with repo-relative links and align the setup docs with the Node version file. Thanks @stainlu.
53
98
 
99
+ ## Unreleased
100
+
101
+ ### Fixed
102
+
103
+ - Fix live `xurl` status detection when the CLI is installed but not authenticated; thanks @kyupark.
104
+ - Default local `bird` integration to `bird` on PATH and report stale configured command paths with setup guidance.
105
+
54
106
  ## 0.2.1 - 2026-04-27
55
107
 
56
108
  ### 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
@@ -184,6 +256,10 @@ Start the app:
184
256
  birdclaw serve
185
257
  ```
186
258
 
259
+ Use the Sync button in Home, Mentions, Likes, Bookmarks, or DMs to run the matching live sync from the web UI and then reload the local view. These controls are explicit because live reads can be slow, auth-dependent, or rate-limited.
260
+
261
+ When running behind a trusted reverse proxy such as Tailscale Serve, add any extra proxy hostnames to `BIRDCLAW_ALLOWED_HOSTS`. The clawmac Tailscale hostname is allowed by default.
262
+
187
263
  First moderation pass:
188
264
 
189
265
  ```bash
@@ -204,21 +280,47 @@ pnpm cli search tweets --liked --limit 20 --json
204
280
  pnpm cli search tweets --bookmarked --limit 20 --json
205
281
  ```
206
282
 
207
- ### Sync likes, bookmarks, and home timeline
283
+ ### Sync authored tweets, likes, bookmarks, home timeline, and mentions
208
284
 
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.
285
+ `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
286
 
211
287
  ```bash
288
+ pnpm cli sync authored --mode xurl --limit 100 --json
212
289
  pnpm cli sync likes --mode auto --limit 100 --refresh --json
213
290
  pnpm cli sync bookmarks --mode auto --limit 100 --refresh --json
291
+ pnpm cli sync likes --mode auto --limit 100 --max-pages 5 --early-stop --refresh --json
292
+ pnpm cli sync bookmarks --mode auto --limit 100 --max-pages 5 --early-stop --refresh --json
214
293
  pnpm cli sync bookmarks --mode bird --all --max-pages 5 --limit 100 --refresh --json
215
294
  pnpm cli sync timeline --limit 100 --refresh --json
295
+ pnpm cli sync mentions --mode xurl --limit 100 --max-pages 3 --refresh --json
216
296
  pnpm cli sync mention-threads --limit 30 --delay-ms 1500 --timeout-ms 15000 --json
217
297
  ```
218
298
 
299
+ 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.
300
+
301
+ ### Follow graph queries
302
+
303
+ 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`.
304
+
305
+ ```bash
306
+ pnpm cli sync followers --json
307
+ pnpm cli sync following --json
308
+ pnpm cli sync followers --yes --json
309
+ pnpm cli sync following --yes --json
310
+ pnpm cli sync followers --mode bird --yes --json
311
+ pnpm cli graph summary --json
312
+ pnpm cli graph events --since 2026-05-01 --json
313
+ pnpm cli graph top-followers --limit 20 --json
314
+ pnpm cli graph unfollowed --date 2026-05-01 --json
315
+ pnpm cli graph non-mutual-following --sort followers --limit 100 --json
316
+ pnpm cli graph mutuals --json
317
+ ```
318
+
319
+ 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.
320
+
219
321
  ### Export mentions for agents
220
322
 
221
- Default `birdclaw` mode returns normalized items with `text`, `plainText`, `markdown`, author metadata, and canonical URLs:
323
+ Default `birdclaw` mode exports DB-backed mention items with `text`, `plainText`, `markdown`, author metadata, and canonical URLs:
222
324
 
223
325
  ```bash
224
326
  pnpm cli mentions export "agent" --unreplied --limit 10
@@ -260,7 +362,8 @@ Notes:
260
362
  - `actions.transport` accepts `auto`, `bird`, or `xurl`
261
363
  - `bird` mode uses your local `bird` CLI and caches its mentions output into birdclaw's canonical store
262
364
  - 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
365
+ - `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
366
+ - the web UI has explicit Sync buttons for home timeline, mentions, likes, bookmarks, and DMs; they call the same sync paths and then reload the local DB-backed view
264
367
 
265
368
  ### Research bookmarks and threads
266
369
 
@@ -510,12 +613,14 @@ pnpm test
510
613
  pnpm coverage
511
614
  pnpm build
512
615
  pnpm e2e
616
+ pnpm perf:browser -- --scenario=links,links-toggle --iterations=5
513
617
  ```
514
618
 
515
619
  Current bar:
516
620
 
517
621
  - branch coverage above `80%`
518
622
  - Playwright coverage for core UI flows
623
+ - browser perf smoke reports ready/action timings plus API endpoint fan-out
519
624
 
520
625
  ## CI
521
626
 
@@ -533,3 +638,4 @@ Workflow: [ci.yml](.github/workflows/ci.yml)
533
638
  - [spec.md](docs/spec.md)
534
639
  - [cli.md](docs/cli.md)
535
640
  - [data-architecture.md](docs/data-architecture.md)
641
+ - [follow-graph.md](docs/follow-graph.md)
package/bin/birdclaw.mjs CHANGED
@@ -1,30 +1,69 @@
1
1
  #!/usr/bin/env node
2
- import { spawnSync } from "node:child_process";
2
+ import { spawn } from "node:child_process";
3
3
  import { createRequire } from "node:module";
4
4
  import { dirname, join } from "node:path";
5
- import { fileURLToPath } from "node:url";
5
+ import { fileURLToPath, pathToFileURL } from "node:url";
6
6
 
7
7
  const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
8
8
  const require = createRequire(import.meta.url);
9
- const tsxCli = require.resolve("tsx/cli");
9
+ const tsxLoader = pathToFileURL(require.resolve("tsx")).href;
10
10
  const birdclawCli = join(packageRoot, "src", "cli.ts");
11
11
 
12
- const result = spawnSync(
12
+ const child = spawn(
13
13
  process.execPath,
14
- [tsxCli, birdclawCli, ...process.argv.slice(2)],
14
+ ["--import", tsxLoader, birdclawCli, ...process.argv.slice(2)],
15
15
  {
16
16
  stdio: "inherit",
17
17
  env: process.env,
18
+ detached: process.platform !== "win32",
18
19
  },
19
20
  );
20
21
 
21
- if (result.error) {
22
- console.error(result.error.message);
23
- process.exit(1);
22
+ const forwardedSignals = ["SIGINT", "SIGTERM", "SIGHUP", "SIGQUIT"];
23
+
24
+ function removeSignalHandlers() {
25
+ for (const signal of forwardedSignals) {
26
+ process.removeListener(signal, forwardSignal);
27
+ }
28
+ }
29
+
30
+ function forwardSignal(signal) {
31
+ if (child.exitCode === null && child.signalCode === null) {
32
+ signalChild(signal);
33
+ }
34
+ }
35
+
36
+ function signalChild(signal) {
37
+ if (child.pid === undefined) {
38
+ return;
39
+ }
40
+ const targetPid = process.platform === "win32" ? child.pid : -child.pid;
41
+ try {
42
+ process.kill(targetPid, signal);
43
+ } catch (error) {
44
+ if (error?.code !== "ESRCH") {
45
+ throw error;
46
+ }
47
+ }
24
48
  }
25
49
 
26
- if (result.signal) {
27
- process.kill(process.pid, result.signal);
50
+ for (const signal of forwardedSignals) {
51
+ process.on(signal, forwardSignal);
28
52
  }
29
53
 
30
- process.exit(result.status ?? 0);
54
+ child.on("error", (error) => {
55
+ removeSignalHandlers();
56
+ console.error(error.message);
57
+ process.exit(1);
58
+ });
59
+
60
+ child.on("exit", (code, signal) => {
61
+ removeSignalHandlers();
62
+
63
+ if (signal) {
64
+ process.kill(process.pid, signal);
65
+ return;
66
+ }
67
+
68
+ process.exit(code ?? 0);
69
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "birdclaw",
3
- "version": "0.4.1",
3
+ "version": "0.5.1",
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",
@@ -13,6 +13,7 @@
13
13
  },
14
14
  "files": [
15
15
  "bin/",
16
+ "scripts/",
16
17
  "src/",
17
18
  "!src/**/*.test.ts",
18
19
  "!src/**/*.test.tsx",
@@ -38,54 +39,55 @@
38
39
  "dev": "vite dev --port 3000",
39
40
  "build": "vite build",
40
41
  "preview": "vite preview",
41
- "test": "env -u NODE_OPTIONS node ./scripts/run-vitest.mjs run",
42
- "coverage": "env -u NODE_OPTIONS node ./scripts/run-vitest.mjs run --coverage",
42
+ "test": "node ./scripts/run-vitest.mjs run",
43
+ "coverage": "node ./scripts/run-vitest.mjs run --coverage",
43
44
  "e2e": "playwright test",
44
45
  "typecheck": "tsgo --noEmit",
45
- "format": "oxfmt --write src scripts playwright vite.config.ts vitest.config.ts playwright.config.ts",
46
- "format:check": "oxfmt --check src scripts playwright vite.config.ts vitest.config.ts playwright.config.ts",
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",
46
+ "format": "oxfmt --write bin src scripts playwright vite.config.ts vitest.config.ts playwright.config.ts",
47
+ "format:check": "oxfmt --check bin src scripts playwright vite.config.ts vitest.config.ts playwright.config.ts",
48
+ "lint": "oxlint --import-plugin --node-plugin --vitest-plugin --deny-warnings -A require-mock-type-parameters -A no-control-regex bin src scripts playwright vite.config.ts vitest.config.ts playwright.config.ts",
48
49
  "check": "pnpm run format:check && pnpm run lint",
50
+ "perf:browser": "node ./scripts/browser-perf.mjs",
49
51
  "cli": "tsx src/cli.ts",
50
52
  "docs:site": "node scripts/build-docs-site.mjs"
51
53
  },
52
54
  "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",
55
+ "@steipete/sweet-cookie": "^0.3.0",
56
+ "@tailwindcss/vite": "^4.3.0",
57
+ "@tanstack/devtools-vite": "0.7.0",
58
+ "@tanstack/react-devtools": "0.10.5",
59
+ "@tanstack/react-router": "1.169.2",
58
60
  "@tanstack/react-router-devtools": "1.166.13",
59
61
  "@tanstack/react-router-ssr-query": "1.166.12",
60
- "@tanstack/react-start": "1.167.61",
61
- "@tanstack/router-plugin": "^1.167.32",
62
+ "@tanstack/react-start": "1.167.65",
63
+ "@tanstack/router-plugin": "^1.167.35",
62
64
  "@vitejs/plugin-react": "^6.0.1",
63
65
  "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"
66
+ "kysely": "^0.29.0",
67
+ "lucide-react": "^1.16.0",
68
+ "react": "^19.2.6",
69
+ "react-dom": "^19.2.6",
70
+ "tailwindcss": "^4.3.0",
71
+ "tsx": "^4.22.0",
72
+ "vite": "^8.0.13",
73
+ "zod": "^4.4.3"
72
74
  },
73
75
  "devDependencies": {
74
- "@playwright/test": "^1.59.1",
76
+ "@playwright/test": "^1.60.0",
75
77
  "@tailwindcss/typography": "^0.5.19",
76
78
  "@testing-library/dom": "^10.4.1",
77
79
  "@testing-library/jest-dom": "^6.9.1",
78
80
  "@testing-library/react": "^16.3.2",
79
- "@types/node": "^25.6.0",
81
+ "@types/node": "^25.8.0",
80
82
  "@types/react": "^19.2.14",
81
83
  "@types/react-dom": "^19.2.3",
82
- "@typescript/native-preview": "7.0.0-dev.20260505.1",
83
- "@vitest/coverage-v8": "^4.1.5",
84
+ "@typescript/native-preview": "7.0.0-dev.20260514.1",
85
+ "@vitest/coverage-v8": "^4.1.6",
84
86
  "jsdom": "^29.1.1",
85
- "oxfmt": "^0.47.0",
86
- "oxlint": "^1.62.0",
87
+ "oxfmt": "^0.49.0",
88
+ "oxlint": "^1.64.0",
87
89
  "typescript": "^6.0.3",
88
- "vitest": "^4.1.5"
90
+ "vitest": "^4.1.6"
89
91
  },
90
92
  "engines": {
91
93
  "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
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",