birdclaw 0.2.1 → 0.4.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 (39) hide show
  1. package/CHANGELOG.md +44 -1
  2. package/README.md +54 -15
  3. package/package.json +14 -15
  4. package/playwright.config.ts +5 -2
  5. package/src/cli.ts +289 -20
  6. package/src/lib/actions-transport.ts +58 -1
  7. package/src/lib/archive-import.ts +34 -2
  8. package/src/lib/backup.ts +271 -33
  9. package/src/lib/bird-actions.ts +21 -0
  10. package/src/lib/bird.ts +332 -17
  11. package/src/lib/blocks.ts +3 -3
  12. package/src/lib/bookmark-sync-job.ts +3 -3
  13. package/src/lib/config.ts +34 -1
  14. package/src/lib/db.ts +321 -14
  15. package/src/lib/dms-live.ts +3 -3
  16. package/src/lib/identity-search-index.ts +369 -0
  17. package/src/lib/mention-threads-live.ts +267 -0
  18. package/src/lib/mentions-live.ts +18 -7
  19. package/src/lib/moderation-target.ts +7 -5
  20. package/src/lib/moderation-write.ts +3 -3
  21. package/src/lib/profile-affiliation-hydration.ts +189 -0
  22. package/src/lib/profile-affiliations.ts +262 -0
  23. package/src/lib/profile-bio-entities.ts +318 -0
  24. package/src/lib/profile-history.ts +164 -0
  25. package/src/lib/profile-hydration.ts +8 -24
  26. package/src/lib/profile-resolver.ts +428 -0
  27. package/src/lib/queries.ts +522 -68
  28. package/src/lib/research.ts +607 -0
  29. package/src/lib/seed.ts +10 -4
  30. package/src/lib/sqlite.ts +143 -0
  31. package/src/lib/timeline-collections-live.ts +22 -8
  32. package/src/lib/timeline-live.ts +182 -0
  33. package/src/lib/tweet-account-edges.ts +39 -0
  34. package/src/lib/tweet-lookup.ts +35 -0
  35. package/src/lib/types.ts +103 -1
  36. package/src/lib/url-expansion.ts +147 -0
  37. package/src/lib/whois.ts +1060 -0
  38. package/src/lib/x-profile.ts +174 -17
  39. package/src/lib/xurl.ts +41 -6
package/CHANGELOG.md CHANGED
@@ -1,6 +1,49 @@
1
1
  # CHANGELOG
2
2
 
3
- All notable changes to this project will be documented in this file.
3
+ ## Unreleased
4
+
5
+ ## 0.4.0 - 2026-05-09
6
+
7
+ ### Added
8
+
9
+ - Preserve richer X profile metadata during `bird`/`xurl` profile hydration, including profile URLs, profile URL entities, locations, verification type, raw profile JSON, and X affiliation/highlighted-label metadata.
10
+ - Add first-class `profile_affiliations` storage, backup/export/import support, and `whois --json` `profileEvidence` so identity lookups can explain whether a match came from bio text, profile URLs, affiliation badges, DM context, or expanded links.
11
+ - Add profile-change snapshots for hydrated profiles, preserving prior bio/profile URL/location/verification/affiliation states so identity searches can surface current and previous affiliation evidence.
12
+ - Add first-class bio entity extraction for profile bios and profile URLs, including `@handle`, domain, and company-phrase evidence used by fuzzy identity searches such as `whois "blacksmith guy"`.
13
+ - Add a derived `identity_search_index` and `whois` filters for affiliation-oriented identity lookups: `--affiliation`, `--current-affiliation`, and `--exclude-domain-only`.
14
+
15
+ ### Changed
16
+
17
+ - Use Node's native `node:sqlite` runtime instead of `better-sqlite3`, removing the native npm dependency while preserving the existing synchronous SQLite API surface.
18
+ - Allow Node 26.x in the package engine range and update install docs for the native SQLite runtime.
19
+ - Improve DM `whois` ranking with Sweetistics-style profile evidence scoring: profile URLs and affiliation badges now boost relevant candidates, while cached profile and URL lookups still avoid repeated API/network work.
20
+ - Resolve synthetic X highlighted-label organization badges into real local organization profile ids when `bird` can hydrate the org handle.
21
+ - Rank current affiliation and bio identity evidence above plain profile domains in `whois`, group human output into ambiguity buckets, and explain "why this person?" with the strongest typed evidence first.
22
+ - Use `bird profiles --json` for batch profile hydration when available, falling back to single-profile `bird user --profile-only --json`.
23
+
24
+ ## 0.3.0 - 2026-05-05
25
+
26
+ ### Added
27
+
28
+ - Add research mode for turning bookmarked Twitter threads into Markdown briefs, with shared `xurl`/`bird` tweet lookup fallback for thread expansion. Thanks @anupamchugh.
29
+ - Add live home timeline and mention-thread sync commands so local triage can pull current `bird` context into the SQLite store.
30
+ - Add search snippets for tweet and DM results, including deterministic DM snippets when multiple messages in a conversation match. Thanks @mvanhorn.
31
+ - Add `--min-likes` and `--quality-reason` controls for tweet search quality filtering. Thanks @mvanhorn.
32
+ - Store Twitter following counts on profiles and include them in JSONL backups.
33
+
34
+ ### Changed
35
+
36
+ - Use the native TypeScript preview compiler for the `typecheck` script.
37
+ - Refresh TypeScript and related development dependencies.
38
+
39
+ ### Fixed
40
+
41
+ - Use the existing Twitter web cookie fallback as the final `auto` transport for block and unblock actions. Thanks @pejmanjohn.
42
+ - Resolve the `bird` transport from `PATH` before falling back to the local development checkout. Thanks @vyctorbrzezowski.
43
+ - Stabilize the presenter timestamp test across local time zones. Thanks @pejmanjohn.
44
+ - Clean up the DMs route render test so CI does not leave React work running after jsdom teardown.
45
+ - Allow Playwright e2e runs to use an alternate local port when `3000` is already occupied.
46
+ - Replace maintainer-local documentation links with repo-relative links and align the setup docs with the Node version file. Thanks @stainlu.
4
47
 
5
48
  ## 0.2.1 - 2026-04-27
6
49
 
package/README.md CHANGED
@@ -17,7 +17,7 @@ Status: WIP. Real and usable. Not done. Expect schema churn, transport gaps, and
17
17
 
18
18
  ### Local data + storage
19
19
 
20
- - one shared SQLite DB for multiple accounts
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
23
  - archive import for tweets, likes, profiles, and full DMs
@@ -25,6 +25,7 @@ Status: WIP. Real and usable. Not done. Expect schema churn, transport gaps, and
25
25
  - live likes and bookmarks sync through `xurl` or `bird`
26
26
  - Git-friendly text backups with yearly tweet shards and per-conversation DM shards
27
27
  - profile hydration from live Twitter metadata
28
+ - profile-change history, affiliation badge edges, and extracted bio entities for local identity lookups
28
29
  - local avatar cache
29
30
  - local media cache root under `~/.birdclaw`
30
31
 
@@ -112,7 +113,7 @@ export BIRDCLAW_HOME=/path/to/custom/root
112
113
 
113
114
  ## Requirements
114
115
 
115
- - Node `25.8.1`
116
+ - Node `25.8.1` or Node 26.x
116
117
  - `pnpm`
117
118
  - macOS recommended for Spotlight archive discovery
118
119
  - `xurl` optional for live reads / writes
@@ -203,7 +204,7 @@ pnpm cli search tweets --liked --limit 20 --json
203
204
  pnpm cli search tweets --bookmarked --limit 20 --json
204
205
  ```
205
206
 
206
- ### Sync likes and bookmarks
207
+ ### Sync likes, bookmarks, and home timeline
207
208
 
208
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.
209
210
 
@@ -211,6 +212,8 @@ pnpm cli search tweets --bookmarked --limit 20 --json
211
212
  pnpm cli sync likes --mode auto --limit 100 --refresh --json
212
213
  pnpm cli sync bookmarks --mode auto --limit 100 --refresh --json
213
214
  pnpm cli sync bookmarks --mode bird --all --max-pages 5 --limit 100 --refresh --json
215
+ pnpm cli sync timeline --limit 100 --refresh --json
216
+ pnpm cli sync mention-threads --limit 30 --delay-ms 1500 --timeout-ms 15000 --json
214
217
  ```
215
218
 
216
219
  ### Export mentions for agents
@@ -257,18 +260,49 @@ Notes:
257
260
  - `actions.transport` accepts `auto`, `bird`, or `xurl`
258
261
  - `bird` mode uses your local `bird` CLI and caches its mentions output into birdclaw's canonical store
259
262
  - filters still work in `xurl` mode; filtered payloads are rebuilt from the local canonical store after sync
260
- - `sync likes` and `sync bookmarks` store live results in the same local timeline table, so `search tweets --liked` and `search tweets --bookmarked` work across archive and live data
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
264
+
265
+ ### Research bookmarks and threads
266
+
267
+ `birdclaw research` turns bookmarked tweets into a markdown brief with local thread expansion, live ancestor lookup when needed, and extracted links/handles:
268
+
269
+ ```bash
270
+ birdclaw research "codex" --limit 20 --thread-depth 10 --json
271
+ birdclaw research --account acct_primary --out ~/research/codex.md
272
+ ```
261
273
 
262
274
  ### Search and triage DMs
263
275
 
264
276
  ```bash
265
277
  pnpm cli search dms "prototype" --json
266
278
  pnpm cli search dms "layout" --min-followers 1000 --min-influence-score 120 --sort influence --json
279
+ pnpm cli search dms "blacksmith" --context 4 --resolve-profiles --expand-urls --no-xurl-fallback --json
280
+ pnpm cli whois "blacksmith guy" --context 4 --no-xurl-fallback --json
281
+ pnpm cli whois "github guy" --current-affiliation github --exclude-domain-only --no-xurl-fallback
282
+ pnpm cli whois "blacksmith" --tweets --context 4 --no-xurl-fallback --json
267
283
  pnpm cli dms sync --limit 50 --refresh --json
268
284
  pnpm cli dms list --refresh --limit 10 --json
269
285
  pnpm cli dms list --unreplied --min-followers 500 --min-influence-score 90 --sort influence --json
270
286
  ```
271
287
 
288
+ `--resolve-profiles` fills archive-imported numeric DM profiles through the local
289
+ cache first, then `bird`, then `xurl` unless `--no-xurl-fallback` is set.
290
+ Resolved profiles keep bio, location, profile URL, verification type, structured
291
+ URL entities, raw profile JSON, and any X affiliation badge metadata Birdclaw can
292
+ see. When a highlighted-label badge only gives a synthetic label plus handle,
293
+ Birdclaw tries to hydrate that org handle through `bird` and rewrites the edge to
294
+ the real local organization profile id. Profile changes are snapshotted over
295
+ time, and bios are indexed for `@handles`, domains, and company phrases.
296
+ `whois` uses that profile context plus DM context and cached URL expansion to
297
+ return typed evidence such as `profile_bio`, `profile_url`, `profile_bio_url`,
298
+ `affiliation`, `bio_handle`, `bio_domain`, `bio_company`, `profile_history`,
299
+ `dm_context`, and `expanded_url`. It keeps a derived `identity_search_index`
300
+ for fast local profile-evidence lookups, ranks current affiliation and bio
301
+ identity evidence above plain domains, and groups human output into likely
302
+ affiliated, ecosystem, link-only, and DM-context buckets. Use
303
+ `--affiliation`, `--current-affiliation`, and `--exclude-domain-only` when you
304
+ want "GitHub people" rather than anyone with a `github.com` link.
305
+
272
306
  ### AI inbox
273
307
 
274
308
  ```bash
@@ -293,7 +327,7 @@ pnpm cli unban @amelia --account acct_primary --transport bird --json
293
327
  Notes:
294
328
 
295
329
  - `ban` / `unban` accept `--transport auto|bird|xurl`
296
- - `auto` tries `bird` first, then falls back to `xurl` when bird fails
330
+ - `auto` tries `bird` first, then falls back to `xurl`, then `x-web` cookie-backed block/unblock when both fail
297
331
  - forced `xurl` writes still verify through `bird status` before sqlite changes
298
332
  - Twitter still rejects pure OAuth2 block writes, so `auto` is the safe default for block/unblock
299
333
  - `blocks import` accepts newline-delimited blocklists with comments and markdown bullets
@@ -366,8 +400,13 @@ Layout:
366
400
  manifest.json
367
401
  data/accounts.jsonl
368
402
  data/profiles.jsonl
403
+ data/profile_affiliations.jsonl
404
+ data/profile_snapshots.jsonl
405
+ data/profile_bio_entities.jsonl
369
406
  data/tweets/YYYY.jsonl
370
407
  data/tweets/unknown.jsonl
408
+ data/timeline_edges/home.jsonl
409
+ data/timeline_edges/mention.jsonl
371
410
  data/collections/likes.jsonl
372
411
  data/collections/bookmarks.jsonl
373
412
  data/dms/conversations.jsonl
@@ -376,7 +415,7 @@ data/moderation/blocks.jsonl
376
415
  data/moderation/mutes.jsonl
377
416
  ```
378
417
 
379
- Tweets are sharded by year for human browsing and yearly analysis. Collection-only tweets whose real creation date is unknown go into `data/tweets/unknown.jsonl` instead of pretending they belong to 1970. DMs are sharded by year with `conversation_id` in each row; this keeps Git fast while preserving conversation membership. Likes and bookmarks are stored as collection edges in `data/collections` and mirrored into the timeline rows for current query compatibility.
418
+ Tweets are sharded by year for human browsing and yearly analysis. Collection-only tweets whose real creation date is unknown go into `data/tweets/unknown.jsonl` instead of pretending they belong to 1970. Timeline membership is stored in `data/timeline_edges`; likes and bookmarks are stored as account-scoped collection edges in `data/collections`. DMs are sharded by year with `conversation_id` in each row; this keeps Git fast while preserving conversation membership.
380
419
 
381
420
  Use `backup sync` when the target is a private Git repo. It pulls first, merge-imports the remote backup into local SQLite, exports the local union back into text shards, commits, and pushes.
382
421
 
@@ -466,11 +505,11 @@ pnpm cli auth status --json
466
505
  ## Testing
467
506
 
468
507
  ```bash
469
- fnm exec --using 25.8.1 pnpm check
470
- fnm exec --using 25.8.1 pnpm test
471
- fnm exec --using 25.8.1 pnpm coverage
472
- fnm exec --using 25.8.1 pnpm build
473
- fnm exec --using 25.8.1 pnpm e2e
508
+ pnpm check
509
+ pnpm test
510
+ pnpm coverage
511
+ pnpm build
512
+ pnpm e2e
474
513
  ```
475
514
 
476
515
  Current bar:
@@ -487,10 +526,10 @@ GitHub Actions runs:
487
526
  - `pnpm build`
488
527
  - `pnpm e2e`
489
528
 
490
- Workflow: [ci.yml](/Users/steipete/Projects/birdclaw/.github/workflows/ci.yml)
529
+ Workflow: [ci.yml](.github/workflows/ci.yml)
491
530
 
492
531
  ## Docs
493
532
 
494
- - [spec.md](/Users/steipete/Projects/birdclaw/docs/spec.md)
495
- - [cli.md](/Users/steipete/Projects/birdclaw/docs/cli.md)
496
- - [data-architecture.md](/Users/steipete/Projects/birdclaw/docs/data-architecture.md)
533
+ - [spec.md](docs/spec.md)
534
+ - [cli.md](docs/cli.md)
535
+ - [data-architecture.md](docs/data-architecture.md)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "birdclaw",
3
- "version": "0.2.1",
3
+ "version": "0.4.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,29 +46,29 @@
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
- "cli": "tsx src/cli.ts"
49
+ "cli": "tsx src/cli.ts",
50
+ "docs:site": "node scripts/build-docs-site.mjs"
50
51
  },
51
52
  "dependencies": {
52
53
  "@steipete/sweet-cookie": "^0.2.0",
53
54
  "@tailwindcss/vite": "^4.2.4",
54
55
  "@tanstack/devtools-vite": "0.6.0",
55
56
  "@tanstack/react-devtools": "0.10.2",
56
- "@tanstack/react-router": "1.168.24",
57
+ "@tanstack/react-router": "1.169.1",
57
58
  "@tanstack/react-router-devtools": "1.166.13",
58
59
  "@tanstack/react-router-ssr-query": "1.166.12",
59
- "@tanstack/react-start": "1.167.49",
60
- "@tanstack/router-plugin": "^1.167.27",
60
+ "@tanstack/react-start": "1.167.61",
61
+ "@tanstack/router-plugin": "^1.167.32",
61
62
  "@vitejs/plugin-react": "^6.0.1",
62
- "better-sqlite3": "^12.9.0",
63
63
  "commander": "^14.0.3",
64
- "kysely": "^0.28.16",
65
- "lucide-react": "^1.11.0",
64
+ "kysely": "^0.28.17",
65
+ "lucide-react": "^1.14.0",
66
66
  "react": "^19.2.5",
67
67
  "react-dom": "^19.2.5",
68
68
  "tailwindcss": "^4.2.4",
69
69
  "tsx": "^4.21.0",
70
70
  "vite": "^8.0.10",
71
- "zod": "^4.3.6"
71
+ "zod": "^4.4.2"
72
72
  },
73
73
  "devDependencies": {
74
74
  "@playwright/test": "^1.59.1",
@@ -76,24 +76,23 @@
76
76
  "@testing-library/dom": "^10.4.1",
77
77
  "@testing-library/jest-dom": "^6.9.1",
78
78
  "@testing-library/react": "^16.3.2",
79
- "@types/better-sqlite3": "^7.6.13",
80
79
  "@types/node": "^25.6.0",
81
80
  "@types/react": "^19.2.14",
82
81
  "@types/react-dom": "^19.2.3",
82
+ "@typescript/native-preview": "7.0.0-dev.20260505.1",
83
83
  "@vitest/coverage-v8": "^4.1.5",
84
- "jsdom": "^29.1.0",
85
- "oxfmt": "^0.46.0",
86
- "oxlint": "^1.61.0",
84
+ "jsdom": "^29.1.1",
85
+ "oxfmt": "^0.47.0",
86
+ "oxlint": "^1.62.0",
87
87
  "typescript": "^6.0.3",
88
88
  "vitest": "^4.1.5"
89
89
  },
90
90
  "engines": {
91
- "node": ">=25.8.1 <26"
91
+ "node": ">=25.8.1 <27"
92
92
  },
93
93
  "packageManager": "pnpm@10.31.0",
94
94
  "pnpm": {
95
95
  "onlyBuiltDependencies": [
96
- "better-sqlite3",
97
96
  "esbuild",
98
97
  "lightningcss"
99
98
  ]
@@ -2,6 +2,8 @@ import path from "node:path";
2
2
  import { defineConfig, devices } from "@playwright/test";
3
3
 
4
4
  const testHome = path.join(process.cwd(), ".playwright-home");
5
+ const port = process.env.BIRDCLAW_PLAYWRIGHT_PORT ?? "3000";
6
+ const baseURL = `http://127.0.0.1:${port}`;
5
7
 
6
8
  export default defineConfig({
7
9
  testDir: "./playwright",
@@ -9,7 +11,7 @@ export default defineConfig({
9
11
  retries: 0,
10
12
  workers: 1,
11
13
  use: {
12
- baseURL: "http://127.0.0.1:3000",
14
+ baseURL,
13
15
  trace: "on-first-retry",
14
16
  },
15
17
  projects: [
@@ -20,10 +22,11 @@ export default defineConfig({
20
22
  ],
21
23
  webServer: {
22
24
  command: "node ./scripts/start-test-server.mjs",
23
- url: "http://127.0.0.1:3000",
25
+ url: baseURL,
24
26
  reuseExistingServer: false,
25
27
  timeout: 120000,
26
28
  env: {
29
+ BIRDCLAW_PLAYWRIGHT_PORT: port,
27
30
  BIRDCLAW_HOME: testHome,
28
31
  BIRDCLAW_DISABLE_LIVE_WRITES: "1",
29
32
  },