birdclaw 0.6.0 → 0.8.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 (62) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/README.md +32 -2
  3. package/package.json +29 -30
  4. package/scripts/build-docs-site.mjs +39 -12
  5. package/src/cli.ts +457 -26
  6. package/src/components/AppNav.tsx +10 -0
  7. package/src/components/MarkdownViewer.tsx +438 -72
  8. package/src/components/ProfileAnalysisStream.tsx +428 -0
  9. package/src/components/ProfilePreview.tsx +120 -9
  10. package/src/components/SavedTimelineView.tsx +30 -8
  11. package/src/components/SyncNowButton.tsx +5 -2
  12. package/src/components/TimelineCard.tsx +20 -4
  13. package/src/components/TimelineRouteFrame.tsx +16 -0
  14. package/src/components/TweetRichText.tsx +36 -12
  15. package/src/components/useTimelineRouteData.ts +74 -6
  16. package/src/lib/account-sync-job.ts +15 -3
  17. package/src/lib/archive-finder.ts +1 -1
  18. package/src/lib/archive-import.ts +245 -7
  19. package/src/lib/authored-live.ts +1 -0
  20. package/src/lib/avatar-cache.ts +50 -0
  21. package/src/lib/backup.ts +4 -3
  22. package/src/lib/bird.ts +33 -0
  23. package/src/lib/config.ts +35 -2
  24. package/src/lib/data-sources.ts +219 -0
  25. package/src/lib/db.ts +62 -1
  26. package/src/lib/geocoding.ts +296 -0
  27. package/src/lib/link-insights.ts +2 -0
  28. package/src/lib/location.ts +137 -0
  29. package/src/lib/mention-threads-live.ts +94 -1
  30. package/src/lib/mentions-live.ts +187 -40
  31. package/src/lib/network-map.ts +382 -0
  32. package/src/lib/period-digest.ts +468 -29
  33. package/src/lib/profile-analysis.ts +1272 -0
  34. package/src/lib/profile-bio-entities.ts +1 -1
  35. package/src/lib/queries.ts +14 -4
  36. package/src/lib/search-discussion.ts +1016 -0
  37. package/src/lib/timeline-live.ts +272 -19
  38. package/src/lib/tweet-account-edges.ts +2 -0
  39. package/src/lib/tweet-render.ts +141 -1
  40. package/src/lib/tweet-search-live.ts +565 -0
  41. package/src/lib/types.ts +37 -2
  42. package/src/lib/ui.ts +1 -1
  43. package/src/lib/web-sync.ts +7 -2
  44. package/src/lib/xurl-rate-limits.ts +267 -0
  45. package/src/lib/xurl.ts +551 -41
  46. package/src/routeTree.gen.ts +231 -0
  47. package/src/routes/__root.tsx +5 -6
  48. package/src/routes/api/data-sources.tsx +24 -0
  49. package/src/routes/api/network-map.tsx +55 -0
  50. package/src/routes/api/period-digest.tsx +29 -3
  51. package/src/routes/api/profile-analysis.tsx +152 -0
  52. package/src/routes/api/query.tsx +1 -0
  53. package/src/routes/api/search-discussion.tsx +169 -0
  54. package/src/routes/api/xurl-rate-limits.tsx +24 -0
  55. package/src/routes/data-sources.tsx +257 -0
  56. package/src/routes/discuss.tsx +419 -0
  57. package/src/routes/network-map.tsx +1035 -0
  58. package/src/routes/profile-analyze.tsx +112 -0
  59. package/src/routes/profiles.$handle.tsx +228 -0
  60. package/src/routes/rate-limits.tsx +309 -0
  61. package/src/routes/today.tsx +22 -8
  62. package/src/styles.css +22 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,69 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 0.8.0 - 2026-06-10
4
+
5
+ ### Added
6
+
7
+ - Add localized Today/digest reports through `--language <locale-id>`, `BIRDCLAW_DIGEST_LANGUAGE`, and the period-digest API, with canonical locale validation and separate caches. (#47 - thanks @yujiawei)
8
+
9
+ ### Changed
10
+
11
+ - Update runtime and development dependencies, align TanStack Start packages, and move pnpm's native build allowlist into workspace configuration.
12
+
13
+ ### Fixed
14
+
15
+ - Add accurate archive-first Sign in and archive-request onboarding, including account-binding requirements, current xurl/bird setup, scoped transport selection, and clean autolink rendering. (#46 - thanks @peetzweg)
16
+ - Respect `OPENAI_BASE_URL` when sending Today/digest requests to OpenAI-compatible API endpoints.
17
+ - Anchor Link Insights' Today range to UTC midnight so it matches stored `created_at` timestamps across local time zones.
18
+
19
+ ## 0.7.0 - 2026-06-01
20
+
21
+ ### Added
22
+
23
+ - Stream live `birdclaw import archive` progress to stderr: per-slice parsing ticks (tweets, DMs, likes, bookmarks, follows, media) and chunked write-phase progress every 1,000 rows for profiles, tweets, likes+bookmarks, and DM messages. `--json` still keeps stdout clean for scripting.
24
+ - Add `birdclaw discuss <query>` and a Discuss web view for live keyword search via `bird`/`xurl`, persisted search-result tweets, and streaming OpenAI summaries with optional private DM context.
25
+ - Add `birdclaw profile-analyze <handle>` plus a Profile Analyse web view that backfills profile timelines and conversation context through `xurl`, caches the fetched context and AI result in SQLite, and exposes Analyse actions on tweet cards.
26
+ - Add canonical `/profiles/:handle` pages with profile headers and cached Profile Analyse output.
27
+ - Add a Rate Limits web view for observed `xurl` profile-analysis calls, 429s, local throttle settings, and documented X API recent-search windows.
28
+ - Add a Network Map web view for current followers/following, with SQLite geocode caching, OpenCage refreshes, Mapbox rendering, and a local fallback map.
29
+ - Render Network Map clusters as avatar stacks with relationship-weighted rings and avatar-rich profile/cluster overlays.
30
+ - Make the Network Map people list follow the current viewport with an in-view search panel.
31
+ - Add a Data Sources web view showing Birdclaw, bird, and xurl health, authenticated accounts, and automatic fallback order.
32
+ - Prefetch cached avatars for Discuss hover citations so source previews avoid fallback initials once profile metadata includes an avatar URL.
33
+ - Refresh Today digests from live `xurl` home timelines, mentions, and mention conversations before AI analysis so reports see more current context and reply parents.
34
+
35
+ ### Changed
36
+
37
+ - Let Today and Discuss fetch much deeper live `xurl` data for the selected time window while keeping the AI prompt constrained to a large model-context budget.
38
+
39
+ ### Fixed
40
+
41
+ - Implement `birdclaw auth use <auto|bird|xurl>` so the documented command persists the preferred moderation action transport. (#45 - thanks @peetzweg)
42
+ - Keep `birdclaw init` alive when the macOS Downloads scan is blocked, falling back to the other archive discovery paths. (#44 - thanks @peetzweg)
43
+ - Show live Today fetch progress while Birdclaw pulls X home timeline, mentions, and reply context before the first AI tokens arrive.
44
+ - Include live fetch counts and page/thread progress in Today status messages before AI summary streaming begins.
45
+ - Recover live `xurl` sync when the valid OAuth token is stored under a different local xurl username label than the Birdclaw account handle.
46
+ - Keep Profile Analyse citation hover cards linked to real tweet/avatar sources, throttle `xurl` conversation searches, and retry 429s before continuing AI summaries with partial context.
47
+ - Open `/profiles/:handle` analysis streams immediately, use same-origin profile fetches, and let `BIRDCLAW_PROFILE_ANALYSIS_ACCOUNT` select the xurl account used for profile backfills.
48
+ - Keep Profile Analyse headers from slicing through loaded avatars/names and turn unresolved numeric tweet citations into safe X source links without leaking raw IDs.
49
+ - Group adjacent Profile Analyse tweet citations so cached AI reports show numbered source links instead of repeated generic `source` labels.
50
+ - Highlight hydrated Profile Analyse `@handle` mentions with profile previews and link multi-source citations to readable clauses when possible.
51
+ - Hydrate Profile Analyse header bio `@handle` mentions as soon as the profile context loads, so affiliation-style bios show profile hover previews.
52
+ - Flip tweet and profile hover previews above their trigger when there is not enough room below.
53
+ - Show expanded URLs instead of `t.co` shortlinks in tweet citation hover previews whenever tweet URL entities are available.
54
+ - Show expanded URLs instead of `t.co` shortlinks in Profile Analyse account bios when X description URL entities are available.
55
+ - Keep emoji-bearing profile bios and media tweets aligned with X entity ranges, and route `@handle` profile-preview links to internal `/profiles/:handle` analysis pages.
56
+ - Make Discuss search source/mode controls look like dropdowns in one row, raise live tweet search depth to 20,000 results / 200 pages, combine bird plus xurl in auto mode, and include matching local timeline/saved tweets in Live search discussions.
57
+ - Default Discuss live mode to xurl now that OAuth2 search is authorized.
58
+ - Use the default authorized xurl OAuth2 user for Discuss/Profile Analyse recent-search reads instead of the selected Birdclaw account handle.
59
+ - Keep Discuss/Profile Analyse recent-search reads from inheriting `BIRDCLAW_XURL_OAUTH2_*` overrides, so account-scoped xurl settings do not force stale app/user auth into global search calls.
60
+ - Let normal Discuss web searches reuse cached AI discussions while keeping the Refresh button as the explicit forced-refresh path.
61
+ - Keep Discuss Live search scoped to live/search-result tweets instead of sweeping every local timeline bucket before AI streaming starts.
62
+ - Link unresolved model-emitted `tweet_<id>` citations in AI reports to X source URLs instead of showing raw citation tokens.
63
+ - Tighten AI report line height and first-block spacing in Today and Discuss.
64
+ - Keep Network Map profile positions anchored to exact geocoded locations and render dense areas through smarter avatar clusters instead of random scatter.
65
+ - Ignore stale configured OAuth2 xurl account overrides for Profile Analyse user lookup and profile timeline reads.
66
+
3
67
  ## 0.6.0 - 2026-05-22
4
68
 
5
69
  ### Added
package/README.md CHANGED
@@ -219,6 +219,8 @@ birdclaw auth status --json
219
219
  birdclaw db stats --json
220
220
  ```
221
221
 
222
+ `auth status` reports Birdclaw's coarse xurl status. Verify xurl with `xurl whoami` and bird with `bird whoami`. For setup and transport selection, see [Sign in](docs/auth.md).
223
+
222
224
  Find and import an archive:
223
225
 
224
226
  ```bash
@@ -227,6 +229,8 @@ birdclaw import archive --json
227
229
  birdclaw import archive ~/Downloads/twitter-archive-2025.zip --json
228
230
  ```
229
231
 
232
+ Don't have an archive yet? Request it from <https://x.com/settings/download_your_data>; X emails a download link when it is ready, which may take a few days. A fresh Birdclaw database needs the archive import to establish account identity before live sync. See [Archive Import → Get an archive](docs/archive.md#get-an-archive).
233
+
230
234
  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:
231
235
 
232
236
  ```bash
@@ -387,19 +391,45 @@ birdclaw research "codex" --limit 20 --thread-depth 10 --json
387
391
  birdclaw research --account acct_primary --out ~/research/codex.md
388
392
  ```
389
393
 
394
+ ### Discuss keyword searches
395
+
396
+ `birdclaw discuss` fetches live keyword matches through `bird` or `xurl`, stores them as local `search` tweets, then streams an OpenAI Markdown summary and discussion. DMs are excluded unless explicitly included.
397
+
398
+ ```bash
399
+ birdclaw discuss "local-first" --mode bird
400
+ birdclaw discuss "sync engine" --question "what changed over time?"
401
+ birdclaw discuss "prototype" --include-dms --limit 500 --max-pages 5 --json
402
+ ```
403
+
404
+ ### Profile analysis
405
+
406
+ `birdclaw profile-analyze` resolves a profile through `xurl`, walks as much of the retrievable timeline as the API allows, backfills high-signal conversations, caches both the fetched context and AI result in SQLite, and writes a Markdown profile brief.
407
+
408
+ Conversation backfill uses X recent search, so Birdclaw paces those calls by default (`BIRDCLAW_PROFILE_ANALYSIS_CONVERSATION_DELAY_MS`, default `3100`) and retries a 429 once after `BIRDCLAW_PROFILE_ANALYSIS_RATE_LIMIT_RETRY_MS` (default `60000`) before continuing with partial context. Set `BIRDCLAW_PROFILE_ANALYSIS_RATE_LIMIT_MAX_RETRIES` or the matching CLI flags when you want different behavior.
409
+
410
+ When `xurl` has multiple OAuth2 labels, set `BIRDCLAW_XURL_OAUTH2_APP` and `BIRDCLAW_XURL_OAUTH2_USERNAME` to force the known-good token. Set `BIRDCLAW_PROFILE_ANALYSIS_ACCOUNT` to an account id or handle when profile backfills should use a non-default Birdclaw account.
411
+
412
+ The web UI uses `/profiles/<handle>` for the canonical profile page, `/profile-analyze` for the analysis/search utility page, and `/rate-limits` for observed `xurl` pressure, recent 429s, and the active Profile Analyse throttle settings.
413
+
414
+ ```bash
415
+ birdclaw profile-analyze steipete
416
+ birdclaw profile-analyse openai --max-pages 20 --max-conversations 40 --conversation-delay-ms 3100 --rate-limit-retries 2 --json
417
+ ```
418
+
390
419
  ### What happened today
391
420
 
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.
421
+ `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. Use `--language <locale-id>` or `BIRDCLAW_DIGEST_LANGUAGE` for localized reports.
393
422
 
394
423
  ```bash
395
424
  birdclaw today
425
+ birdclaw today --language zh-CN
396
426
  birdclaw digest 24h --refresh
397
427
  birdclaw digest week --json
398
428
  birdclaw digest --since 2026-05-16T00:00:00Z --until 2026-05-17T00:00:00Z
399
429
  birdclaw digest today --include-dms
400
430
  ```
401
431
 
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.
432
+ 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, service tier, and report language.
403
433
 
404
434
  ### Search and triage DMs
405
435
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "birdclaw",
3
- "version": "0.6.0",
3
+ "version": "0.8.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",
@@ -56,48 +56,47 @@
56
56
  "@tailwindcss/vite": "^4.3.0",
57
57
  "@tanstack/devtools-vite": "0.7.0",
58
58
  "@tanstack/react-devtools": "0.10.5",
59
- "@tanstack/react-router": "1.169.2",
60
- "@tanstack/react-router-devtools": "1.166.13",
61
- "@tanstack/react-router-ssr-query": "1.166.12",
62
- "@tanstack/react-start": "1.167.65",
63
- "@tanstack/router-plugin": "^1.167.35",
64
- "@vitejs/plugin-react": "^6.0.1",
65
- "commander": "^14.0.3",
66
- "effect": "^3.21.2",
67
- "kysely": "^0.29.0",
68
- "lucide-react": "^1.16.0",
69
- "react": "^19.2.6",
70
- "react-dom": "^19.2.6",
59
+ "@tanstack/react-router": "1.170.15",
60
+ "@tanstack/react-router-devtools": "1.167.0",
61
+ "@tanstack/react-router-ssr-query": "1.167.1",
62
+ "@tanstack/react-start": "1.168.25",
63
+ "@tanstack/router-plugin": "^1.168.18",
64
+ "@vitejs/plugin-react": "^6.0.2",
65
+ "commander": "^15.0.0",
66
+ "effect": "^3.21.3",
67
+ "kysely": "^0.29.2",
68
+ "lucide-react": "^1.17.0",
69
+ "mapbox-gl": "^3.24.0",
70
+ "react": "^19.2.7",
71
+ "react-dom": "^19.2.7",
72
+ "react-map-gl": "^8.1.1",
73
+ "supercluster": "^8.0.1",
71
74
  "tailwindcss": "^4.3.0",
72
- "tsx": "^4.22.0",
73
- "vite": "^8.0.13",
75
+ "tsx": "^4.22.4",
76
+ "vite": "^8.0.16",
74
77
  "zod": "^4.4.3"
75
78
  },
76
79
  "devDependencies": {
77
80
  "@playwright/test": "^1.60.0",
78
- "@tailwindcss/typography": "^0.5.19",
81
+ "@tailwindcss/typography": "^0.5.20",
79
82
  "@testing-library/dom": "^10.4.1",
80
83
  "@testing-library/jest-dom": "^6.9.1",
81
84
  "@testing-library/react": "^16.3.2",
82
- "@types/node": "^25.8.0",
83
- "@types/react": "^19.2.14",
85
+ "@types/geojson": "^7946.0.16",
86
+ "@types/node": "^25.9.2",
87
+ "@types/react": "^19.2.17",
84
88
  "@types/react-dom": "^19.2.3",
85
- "@typescript/native-preview": "7.0.0-dev.20260514.1",
86
- "@vitest/coverage-v8": "^4.1.6",
89
+ "@types/supercluster": "^7.1.3",
90
+ "@typescript/native-preview": "7.0.0-dev.20260609.1",
91
+ "@vitest/coverage-v8": "^4.1.8",
87
92
  "jsdom": "^29.1.1",
88
- "oxfmt": "^0.49.0",
89
- "oxlint": "^1.64.0",
93
+ "oxfmt": "^0.54.0",
94
+ "oxlint": "^1.69.0",
90
95
  "typescript": "^6.0.3",
91
- "vitest": "^4.1.6"
96
+ "vitest": "^4.1.8"
92
97
  },
93
98
  "engines": {
94
99
  "node": ">=25.8.1 <27"
95
100
  },
96
- "packageManager": "pnpm@10.31.0",
97
- "pnpm": {
98
- "onlyBuiltDependencies": [
99
- "esbuild",
100
- "lightningcss"
101
- ]
102
- }
101
+ "packageManager": "pnpm@10.31.0"
103
102
  }
@@ -24,7 +24,10 @@ const productDescription =
24
24
  const brewInstall = "brew install steipete/tap/birdclaw";
25
25
 
26
26
  const sections = [
27
- ["Start", ["index.md", "install.md", "quickstart.md", "configuration.md"]],
27
+ [
28
+ "Start",
29
+ ["index.md", "install.md", "auth.md", "quickstart.md", "configuration.md"],
30
+ ],
28
31
  [
29
32
  "Archive & Sync",
30
33
  ["archive.md", "sync.md", "media.md", "backup.md", "jobs.md"],
@@ -449,19 +452,41 @@ function inline(text, currentRel) {
449
452
  stash.push(`<code>${escapeHtml(code)}</code>`);
450
453
  return `\u0000${stash.length - 1}\u0000`;
451
454
  });
452
- out = escapeHtml(out)
453
- .replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>")
454
- .replace(/(^|[^*])\*([^*\s][^*]*?)\*(?!\*)/g, "$1<em>$2</em>")
455
- .replace(/(^|[^_])_([^_\s][^_]*?)_(?!_)/g, "$1<em>$2</em>")
456
- .replace(
457
- /\[([^\]]+)\]\(([^)]+)\)/g,
458
- (_, label, href) =>
459
- `<a href="${escapeAttr(rewriteHref(href, currentRel))}">${label}</a>`,
460
- )
461
- .replace(/&lt;(https?:\/\/[^\s<>]+)&gt;/g, '<a href="$1">$1</a>');
455
+ const stashLink = (label, href) => {
456
+ stash.push(
457
+ `<a href="${escapeAttr(rewriteHref(href, currentRel))}">${renderInlineText(label, stash)}</a>`,
458
+ );
459
+ return `\u0000${stash.length - 1}\u0000`;
460
+ };
461
+ out = out.replace(/\[([^\]]+)\]\(<([^<>]+)>\)/g, (_, label, href) =>
462
+ stashLink(label, href),
463
+ );
464
+ out = out.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, label, href) =>
465
+ stashLink(label, href),
466
+ );
467
+ out = out.replace(/<(https?:\/\/[^\s<>]+)>/g, (_, url) => {
468
+ stash.push(`<a href="${escapeAttr(url)}">${escapeHtml(url)}</a>`);
469
+ return `\u0000${stash.length - 1}\u0000`;
470
+ });
471
+ return renderInlineText(out, stash);
472
+ }
473
+
474
+ function renderInlineText(text, stash) {
475
+ let out = formatInlineText(text);
462
476
  out = out.replace(/\\\|/g, "|");
463
477
  out = out.replace(/&lt;br&gt;/g, "<br>");
464
- return out.replace(/\u0000(\d+)\u0000/g, (_, i) => stash[Number(i)]);
478
+ return restoreInlineStash(out, stash);
479
+ }
480
+
481
+ function formatInlineText(text) {
482
+ return escapeHtml(text)
483
+ .replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>")
484
+ .replace(/(^|[^*])\*([^*\s][^*]*?)\*(?!\*)/g, "$1<em>$2</em>")
485
+ .replace(/(^|[^_])_([^_\s][^_]*?)_(?!_)/g, "$1<em>$2</em>");
486
+ }
487
+
488
+ function restoreInlineStash(text, stash) {
489
+ return text.replace(/\u0000(\d+)\u0000/g, (_, i) => stash[Number(i)]);
465
490
  }
466
491
 
467
492
  function rewriteHref(href, currentRel) {
@@ -886,6 +911,8 @@ function highlightYamlValue(rest) {
886
911
  return escapeHtml(rest);
887
912
  }
888
913
 
914
+ export const __test__ = { inline };
915
+
889
916
  function validateLinks(outputDir) {
890
917
  const failures = [];
891
918
  const placeholderHrefs =