birdclaw 0.5.0 → 0.6.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 (105) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/README.md +55 -5
  3. package/bin/birdclaw.mjs +50 -11
  4. package/package.json +9 -7
  5. package/public/birdclaw-mark.png +0 -0
  6. package/scripts/browser-perf.mjs +400 -0
  7. package/scripts/build-docs-site.mjs +940 -0
  8. package/scripts/docs-site-assets.mjs +311 -0
  9. package/scripts/run-vitest.mjs +21 -0
  10. package/scripts/sanitize-node-options.mjs +23 -0
  11. package/scripts/start-test-server.mjs +42 -0
  12. package/src/cli.ts +422 -14
  13. package/src/components/AccountSwitcher.tsx +186 -0
  14. package/src/components/AppNav.tsx +29 -9
  15. package/src/components/AvatarChip.tsx +9 -3
  16. package/src/components/BrandMark.tsx +67 -0
  17. package/src/components/ConversationThread.tsx +11 -10
  18. package/src/components/DmWorkspace.tsx +39 -14
  19. package/src/components/FeedState.tsx +147 -0
  20. package/src/components/InboxCard.tsx +14 -2
  21. package/src/components/LinkPreviewCard.tsx +40 -18
  22. package/src/components/MarkdownViewer.tsx +452 -0
  23. package/src/components/SavedTimelineView.tsx +64 -56
  24. package/src/components/SyncNowButton.tsx +137 -0
  25. package/src/components/ThemeSlider.tsx +49 -93
  26. package/src/components/TimelineCard.tsx +364 -136
  27. package/src/components/TimelineRouteFrame.tsx +170 -0
  28. package/src/components/TweetMediaGrid.tsx +170 -24
  29. package/src/components/TweetRichText.tsx +28 -13
  30. package/src/components/account-selection.ts +64 -0
  31. package/src/components/useTimelineRouteData.ts +153 -0
  32. package/src/lib/account-sync-job.ts +654 -0
  33. package/src/lib/actions-transport.ts +216 -146
  34. package/src/lib/api-client.ts +304 -0
  35. package/src/lib/archive-finder.ts +72 -53
  36. package/src/lib/archive-import.ts +1377 -1298
  37. package/src/lib/authored-live.ts +261 -204
  38. package/src/lib/avatar-cache.ts +159 -44
  39. package/src/lib/backup.ts +1532 -951
  40. package/src/lib/bird-actions.ts +139 -57
  41. package/src/lib/bird-command.ts +101 -28
  42. package/src/lib/bird.ts +549 -194
  43. package/src/lib/blocklist.ts +40 -23
  44. package/src/lib/blocks-write.ts +129 -80
  45. package/src/lib/blocks.ts +165 -97
  46. package/src/lib/bookmark-sync-job.ts +250 -160
  47. package/src/lib/conversation-surface.ts +205 -0
  48. package/src/lib/db.ts +35 -3
  49. package/src/lib/dms-live.ts +720 -66
  50. package/src/lib/effect-runtime.ts +45 -0
  51. package/src/lib/follow-graph.ts +224 -180
  52. package/src/lib/http-effect.ts +222 -0
  53. package/src/lib/inbox.ts +74 -43
  54. package/src/lib/link-index.ts +88 -76
  55. package/src/lib/link-insights.ts +24 -0
  56. package/src/lib/link-preview-metadata.ts +472 -52
  57. package/src/lib/media-fetch.ts +286 -213
  58. package/src/lib/mention-threads-live.ts +352 -288
  59. package/src/lib/mentions-live.ts +390 -342
  60. package/src/lib/moderation-target.ts +102 -65
  61. package/src/lib/moderation-write.ts +77 -18
  62. package/src/lib/mutes-write.ts +129 -80
  63. package/src/lib/mutes.ts +8 -1
  64. package/src/lib/openai.ts +84 -53
  65. package/src/lib/period-digest.ts +953 -0
  66. package/src/lib/profile-affiliation-hydration.ts +93 -54
  67. package/src/lib/profile-hydration.ts +124 -72
  68. package/src/lib/profile-replies.ts +60 -43
  69. package/src/lib/profile-resolver.ts +402 -294
  70. package/src/lib/queries.ts +1024 -189
  71. package/src/lib/research.ts +165 -120
  72. package/src/lib/sqlite.ts +1 -0
  73. package/src/lib/timeline-collections-live.ts +204 -167
  74. package/src/lib/timeline-live.ts +60 -39
  75. package/src/lib/tweet-lookup.ts +30 -19
  76. package/src/lib/types.ts +38 -1
  77. package/src/lib/ui.ts +41 -10
  78. package/src/lib/url-expansion.ts +226 -55
  79. package/src/lib/url-safety.ts +220 -0
  80. package/src/lib/web-sync.ts +511 -0
  81. package/src/lib/whois.ts +166 -147
  82. package/src/lib/x-web.ts +102 -71
  83. package/src/lib/xurl.ts +681 -411
  84. package/src/routeTree.gen.ts +63 -0
  85. package/src/routes/__root.tsx +25 -5
  86. package/src/routes/api/action.tsx +127 -78
  87. package/src/routes/api/avatar.tsx +39 -30
  88. package/src/routes/api/blocks.tsx +26 -23
  89. package/src/routes/api/conversation.tsx +25 -14
  90. package/src/routes/api/inbox.tsx +27 -21
  91. package/src/routes/api/link-insights.tsx +31 -25
  92. package/src/routes/api/link-preview.tsx +25 -21
  93. package/src/routes/api/period-digest.tsx +123 -0
  94. package/src/routes/api/profile-hydrate.tsx +31 -28
  95. package/src/routes/api/query.tsx +79 -55
  96. package/src/routes/api/status.tsx +18 -10
  97. package/src/routes/api/sync.tsx +105 -0
  98. package/src/routes/dms.tsx +195 -55
  99. package/src/routes/inbox.tsx +32 -19
  100. package/src/routes/index.tsx +21 -127
  101. package/src/routes/links.tsx +50 -5
  102. package/src/routes/mentions.tsx +21 -127
  103. package/src/routes/today.tsx +441 -0
  104. package/src/styles.css +74 -11
  105. package/vite.config.ts +8 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,68 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 0.6.0 - 2026-05-22
4
+
5
+ ### Added
6
+
7
+ - Add `birdclaw dms sync/list --mode xurl|auto` for recent OAuth2 DM event imports through `xurl`, with bird fallback in auto mode.
8
+ - Add an explicit Messages sort toggle for newest conversations or sender follower count.
9
+ - Add web DM inbox controls for switching between all, accepted, and message-request conversations.
10
+ - Render the `What happened` AI digest as a structured day overview with summary cards, signal topics, highlight tweets, links, people, and hover previews for cited tweet ids and `@handle` mentions.
11
+ - Add a streaming `What happened` AI digest in the web UI and CLI (`birdclaw today`, `birdclaw digest`) backed by OpenAI Responses API, GPT-5.5 by default, medium reasoning, priority service tier, local context hashing, and cached final structured results.
12
+ - Add a global web account switcher plus `jobs sync-account`/`install-account-launchd` so multi-account Birdclaw installs can refresh home, mentions, likes, bookmarks, and DMs from launchd.
13
+
14
+ ### Changed
15
+
16
+ - Stream the Today report as one longer Markdown brief with inline hoverable tweet citations instead of adding separate overview and action cards after the model finishes.
17
+ - Increase the default Today digest tweet context and accept larger web digest requests so 24-hour reports can see deeper into the local archive.
18
+ - Start the Effect rewrite by making `effect` a first-class runtime dependency and moving web API fetches, web sync orchestration, live command helpers, action transport, `bird`/`xurl` JSON/action transports and public adapters, backup export/import/validation and Git orchestration, moderation target resolution, blocks/mutes write helpers, remote block sync, batch blocklist imports, x-web mutations, authored/mentions/mention-thread sync including xurl recent-search and parent-walk fallback internals, conversation loading, home timeline, saved collection, DM live sync, profile hydration/resolution/affiliation/reply inspection, shared tweet lookup, research and whois report generation, follow graph live sync, link preview/index fetches, archive discovery/import subprocesses, avatar/URL caches, OpenAI/inbox scoring, scheduled bookmark sync locking/audit/launchd install, and media-fetch archive reuse/download concurrency onto Effect programs with Promise-compatible public wrappers.
19
+
20
+ ### Fixed
21
+
22
+ - Deduplicate unscoped timeline rows across accounts so Home does not show the same tweet twice when multiple accounts saw it.
23
+ - Render model-emitted Markdown links even when the model inserts a space or line break between the link label and URL.
24
+ - Close Today tweet hover previews when opening their source links so command-clicked citations cannot leave stale preview cards stacked on later hovers.
25
+ - Keep the Messages shell aligned with the rest of the app while collapsing the sidebar labels, and label optional follower/score DM filters instead of showing default `0` fields.
26
+ - Link grouped AI digest tweet citations to nearby readable text instead of showing raw `tweet_...` IDs.
27
+ - Expand the Messages web layout into an icon-rail workspace so the DM list and thread panes no longer squeeze into the standard feed width.
28
+ - Show DM message requests across accounts instead of filtering them by the active sidebar account.
29
+ - Verify the live `bird` account before DM sync, preserve stable account IDs for sparse DM payloads, and pace request-page imports.
30
+ - Align DM profile stat labels and values consistently in the web detail panel.
31
+ - Keep outbound DM bubble text readable by separating inbound and outbound bubble color classes in the web UI.
32
+ - Link AI digest tweet citations on readable text instead of leaking raw `tweet_...` ids when the model cites a local tweet by prefixed id.
33
+ - Hydrate profile metadata for Today highlight tweets so real avatar images replace fallback initials after cached digest results render.
34
+ - Allow trusted private-proxy web deployments to stream the AI digest remotely without a token, while keeping app-level token enforcement when configured and surfacing API error details in the Today view.
35
+ - Harden web write/quota endpoints, URL/avatar fetching, backup imports, archive replacement imports, block sync pruning, and GitHub workflows based on a deepsec security pass.
36
+ - Validate compose, tweet-reply, and DM-reply writes before live transport, reject failed xurl sends without leaving local ghost entries, and keep failed web reply drafts visible with the transport error.
37
+ - Keep account-scoped manual sync buttons disabled until account metadata loads so saved timelines do not submit accountless collection syncs.
38
+ - Cancel failed link preview response bodies promptly so repeated broken preview fetches do not leave sockets open until timeout.
39
+ - Harden link preview metadata fetching against private-network redirects, DNS rebinding, oversized or compressed responses, and slow/broken multi-address hosts.
40
+ - Link raw `@handle` mentions in archived timeline text and render retweets as embedded original tweets with compact repost attribution.
41
+ - Remove the duplicate inline sync account picker now that the global web account switcher controls manual sync account state, and move the theme toggle out of the sidebar footer so the account switcher stays anchored at the bottom.
42
+ - Move the one-button theme toggle above the sidebar account picker so the bottom controls align with the active-account avatar.
43
+ - Hide unresolved `t.co` placeholders and duplicate preview cards on media tweets, and let single-image media render in a natural image-sized frame.
44
+ - Render reposts as native timeline rows with the original author avatar and a single compact repost attribution.
45
+ - Hide empty bookmark, media, and account metadata from timeline action rows so the footer only shows useful state.
46
+ - Move the theme toggle into the sidebar account footer row so it sits with the active-account controls.
47
+
48
+ ## 0.5.1 - 2026-05-15
49
+
50
+ ### Fixed
51
+
52
+ - 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.
53
+ - Forward shutdown signals through the published CLI and bundled web server, and include referenced script helpers in npm packages.
54
+ - Keep the selected DM conversation visible while its thread refreshes so the reply composer no longer flashes away mid-action.
55
+ - Send the selected web account through manual sync controls so multi-account timelines sync the intended profile.
56
+ - Run web sync requests as background jobs with status polling so the UI no longer holds one blocking sync request open.
57
+ - Add typed web API fetch handling and explicit DMs loading/error/empty states so failed local reads surface cleanly.
58
+ - 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.
59
+ - Refine the web app sidebar tagline and theme selector so the brand chrome reads more clearly in compact layouts.
60
+ - 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.
61
+ - 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.
62
+ - Allow the local web app to respond when Tailscale Serve forwards requests through the `clawmac.sheep-coho.ts.net` hostname.
63
+ - Speed up the default home timeline load on large local databases and keep malformed archived media URL entities from crashing the web timeline.
64
+ - Preserve tweet media aspect ratios, open timeline media in an inline viewer, and suppress duplicate media URL cards.
65
+
3
66
  ## 0.5.0 - 2026-05-15
4
67
 
5
68
  ### Added
package/README.md CHANGED
@@ -78,6 +78,12 @@ Status: WIP. Real and usable. Not done. Expect schema churn, transport gaps, and
78
78
  - CI disables live writes
79
79
  - app has no auth layer because it is a local-only tool
80
80
 
81
+ ### Runtime Architecture
82
+
83
+ Birdclaw uses [Effect](https://effect.website/) for new and migrated I/O-heavy internals. The current Effect boundary covers browser API fetches, web sync orchestration, sync-job polling, `bird`/`xurl` subprocess helpers and public adapters, backup export/import/validation and Git orchestration, moderation action transport and target resolution, `bird` action/profile adapters, blocks/mutes write helpers, remote block sync, batch blocklist imports, x-web mutations, authored/mentions/mention-thread sync including xurl recent-search and parent-walk fallback internals, conversation loading, home timeline, saved collection, DM live sync, profile hydration/resolution/affiliation/reply inspection, shared tweet lookup, research and whois report generation, follow graph live sync, link preview/index fetches, archive discovery/import subprocesses, avatar/URL caches, OpenAI/inbox scoring, scheduled bookmark sync locking/audit/launchd install, and the paced/concurrent `media fetch` archive-reuse and HTTP download pipeline.
84
+
85
+ Public CLI and React call sites still expose plain `Promise` wrappers where that keeps the surrounding framework code simple. New core code should prefer `Effect` programs with typed error values, then add a Promise wrapper only at the outer CLI, route, or component boundary.
86
+
81
87
  ## Still In Progress
82
88
 
83
89
  - broader resumable live sync beyond the targeted paths already wired
@@ -91,6 +97,7 @@ If you need polished product-grade sync parity today, this is not there yet.
91
97
  ## Screens
92
98
 
93
99
  - `Home`: read and reply without fighting the main Twitter timeline
100
+ - `What happened`: stream an AI digest for today, 24h, yesterday, or week
94
101
  - `Mentions`: work the reply queue with clean filters
95
102
  - `Likes` / `Bookmarks`: revisit saved posts from archive or live sync
96
103
  - `DMs`: triage by sender context, follower count, and influence
@@ -256,6 +263,16 @@ Start the app:
256
263
  birdclaw serve
257
264
  ```
258
265
 
266
+ `birdclaw serve` binds the dev server to `127.0.0.1` and enables local
267
+ loopback web APIs without a token. Remote access through a trusted private proxy
268
+ requires `BIRDCLAW_ALLOW_REMOTE_WEB=1`. To require an app-level token too, set
269
+ `BIRDCLAW_WEB_TOKEN` and send it as `x-birdclaw-token` or a `birdclaw_token`
270
+ cookie.
271
+
272
+ 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.
273
+
274
+ 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.
275
+
259
276
  First moderation pass:
260
277
 
261
278
  ```bash
@@ -359,6 +376,7 @@ Notes:
359
376
  - `bird` mode uses your local `bird` CLI and caches its mentions output into birdclaw's canonical store
360
377
  - filters still work in `xurl` mode; filtered payloads are rebuilt from the local canonical store after sync
361
378
  - `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
379
+ - 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
362
380
 
363
381
  ### Research bookmarks and threads
364
382
 
@@ -369,20 +387,37 @@ birdclaw research "codex" --limit 20 --thread-depth 10 --json
369
387
  birdclaw research --account acct_primary --out ~/research/codex.md
370
388
  ```
371
389
 
390
+ ### What happened today
391
+
392
+ `birdclaw today` streams a local "what happened" digest from the SQLite store. It uses the OpenAI Responses API with `gpt-5.5`, medium reasoning, and priority service tier by default. Set `OPENAI_API_KEY`; override with `BIRDCLAW_AI_MODEL`, `BIRDCLAW_OPENAI_REASONING_EFFORT`, or `BIRDCLAW_OPENAI_SERVICE_TIER` when needed.
393
+
394
+ ```bash
395
+ birdclaw today
396
+ birdclaw digest 24h --refresh
397
+ birdclaw digest week --json
398
+ birdclaw digest --since 2026-05-16T00:00:00Z --until 2026-05-17T00:00:00Z
399
+ birdclaw digest today --include-dms
400
+ ```
401
+
402
+ The web UI exposes the same stream under `What happened`. DMs are excluded unless explicitly enabled. Final structured results are cached by the exact local context hash, model, reasoning effort, and service tier.
403
+
372
404
  ### Search and triage DMs
373
405
 
374
406
  ```bash
375
407
  pnpm cli search dms "prototype" --json
376
- pnpm cli search dms "layout" --min-followers 1000 --min-influence-score 120 --sort influence --json
408
+ pnpm cli search dms "layout" --min-followers 1000 --min-influence-score 120 --sort followers --json
377
409
  pnpm cli search dms "blacksmith" --context 4 --resolve-profiles --expand-urls --no-xurl-fallback --json
378
410
  pnpm cli whois "blacksmith guy" --context 4 --no-xurl-fallback --json
379
411
  pnpm cli whois "github guy" --current-affiliation github --exclude-domain-only --no-xurl-fallback
380
412
  pnpm cli whois "blacksmith" --tweets --context 4 --no-xurl-fallback --json
381
413
  pnpm cli dms sync --limit 50 --refresh --json
414
+ pnpm cli dms sync --mode auto --limit 50 --refresh --json
382
415
  pnpm cli dms list --refresh --limit 10 --json
383
- pnpm cli dms list --unreplied --min-followers 500 --min-influence-score 90 --sort influence --json
416
+ pnpm cli dms list --unreplied --min-followers 500 --min-influence-score 90 --sort followers --json
384
417
  ```
385
418
 
419
+ `dms sync/list --refresh` supports `--mode bird|xurl|auto`. `bird` is the default and required for message-request state; `xurl` imports recent OAuth2 DM events as accepted conversations, and `auto` falls back to bird when xurl cannot read them.
420
+
386
421
  `--resolve-profiles` fills archive-imported numeric DM profiles through the local
387
422
  cache first, then `bird`, then `xurl` unless `--no-xurl-fallback` is set.
388
423
  Resolved profiles keep bio, location, profile URL, verification type, structured
@@ -425,7 +460,7 @@ pnpm cli unban @amelia --account acct_primary --transport bird --json
425
460
  Notes:
426
461
 
427
462
  - `ban` / `unban` accept `--transport auto|bird|xurl`
428
- - `auto` tries `bird` first, then falls back to `xurl`, then `x-web` cookie-backed block/unblock when both fail
463
+ - `auto` tries `bird` first, then falls back to `xurl`; unverified x-web writes are not used by moderation actions
429
464
  - forced `xurl` writes still verify through `bird status` before sqlite changes
430
465
  - Twitter still rejects pure OAuth2 block writes, so `auto` is the safe default for block/unblock
431
466
  - `blocks import` accepts newline-delimited blocklists with comments and markdown bullets
@@ -537,7 +572,22 @@ Configure stale-aware backup reads in `~/.birdclaw/config.json`:
537
572
 
538
573
  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.
539
574
 
540
- ### Scheduled Bookmark Sync
575
+ ### Scheduled Account and Bookmark Sync
576
+
577
+ `birdclaw jobs sync-account` refreshes home timeline, mentions, mention threads, likes, bookmarks, and DMs for a selected account, then appends a per-step audit entry.
578
+
579
+ ```bash
580
+ birdclaw --json jobs sync-account --account acct_openclaw --limit 100 --max-pages 3 --refresh --allow-bird-account
581
+ tail -n 5 ~/.birdclaw/audit/account-sync.jsonl | jq .
582
+ ```
583
+
584
+ On macOS, install the 30-minute LaunchAgent:
585
+
586
+ ```bash
587
+ birdclaw --json jobs install-account-launchd --account acct_openclaw --program /opt/homebrew/bin/birdclaw --env-path ~/.config/bird/openclaw.env --allow-bird-account
588
+ ```
589
+
590
+ Use `--env-path ~/.config/bird/openclaw.env` when launchd needs account-specific `bird` cookies. Pass `--allow-bird-account` only when those cookies match `--account`; otherwise Bird-backed timeline, mentions, and DM steps refuse non-default account writes to avoid misattribution. Use `--steps timeline,mentions,dms` to narrow the scheduled surfaces.
541
591
 
542
592
  `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.
543
593
 
@@ -554,7 +604,7 @@ On macOS, install the 3-hour LaunchAgent after choosing the Birdclaw executable
554
604
  birdclaw --json jobs install-bookmarks-launchd --program /opt/homebrew/bin/birdclaw
555
605
  ```
556
606
 
557
- 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.
607
+ 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-path ~/.config/bird/env.sh`. Birdclaw sources that file inside the scheduled process without storing the secrets in the plist.
558
608
 
559
609
  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.
560
610
 
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.5.0",
3
+ "version": "0.6.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",
@@ -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",
@@ -35,16 +36,16 @@
35
36
  "access": "public"
36
37
  },
37
38
  "scripts": {
38
- "dev": "vite dev --port 3000",
39
+ "dev": "BIRDCLAW_LOCAL_WEB=1 vite dev --host 127.0.0.1 --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",
49
50
  "perf:browser": "node ./scripts/browser-perf.mjs",
50
51
  "cli": "tsx src/cli.ts",
@@ -62,6 +63,7 @@
62
63
  "@tanstack/router-plugin": "^1.167.35",
63
64
  "@vitejs/plugin-react": "^6.0.1",
64
65
  "commander": "^14.0.3",
66
+ "effect": "^3.21.2",
65
67
  "kysely": "^0.29.0",
66
68
  "lucide-react": "^1.16.0",
67
69
  "react": "^19.2.6",
Binary file