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.
- package/CHANGELOG.md +44 -1
- package/README.md +54 -15
- package/package.json +14 -15
- package/playwright.config.ts +5 -2
- package/src/cli.ts +289 -20
- package/src/lib/actions-transport.ts +58 -1
- package/src/lib/archive-import.ts +34 -2
- package/src/lib/backup.ts +271 -33
- package/src/lib/bird-actions.ts +21 -0
- package/src/lib/bird.ts +332 -17
- package/src/lib/blocks.ts +3 -3
- package/src/lib/bookmark-sync-job.ts +3 -3
- package/src/lib/config.ts +34 -1
- package/src/lib/db.ts +321 -14
- package/src/lib/dms-live.ts +3 -3
- package/src/lib/identity-search-index.ts +369 -0
- package/src/lib/mention-threads-live.ts +267 -0
- package/src/lib/mentions-live.ts +18 -7
- package/src/lib/moderation-target.ts +7 -5
- package/src/lib/moderation-write.ts +3 -3
- package/src/lib/profile-affiliation-hydration.ts +189 -0
- package/src/lib/profile-affiliations.ts +262 -0
- package/src/lib/profile-bio-entities.ts +318 -0
- package/src/lib/profile-history.ts +164 -0
- package/src/lib/profile-hydration.ts +8 -24
- package/src/lib/profile-resolver.ts +428 -0
- package/src/lib/queries.ts +522 -68
- package/src/lib/research.ts +607 -0
- package/src/lib/seed.ts +10 -4
- package/src/lib/sqlite.ts +143 -0
- package/src/lib/timeline-collections-live.ts +22 -8
- package/src/lib/timeline-live.ts +182 -0
- package/src/lib/tweet-account-edges.ts +39 -0
- package/src/lib/tweet-lookup.ts +35 -0
- package/src/lib/types.ts +103 -1
- package/src/lib/url-expansion.ts +147 -0
- package/src/lib/whois.ts +1060 -0
- package/src/lib/x-profile.ts +174 -17
- package/src/lib/xurl.ts +41 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,49 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
-
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
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](
|
|
529
|
+
Workflow: [ci.yml](.github/workflows/ci.yml)
|
|
491
530
|
|
|
492
531
|
## Docs
|
|
493
532
|
|
|
494
|
-
- [spec.md](
|
|
495
|
-
- [cli.md](
|
|
496
|
-
- [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.
|
|
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.
|
|
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.
|
|
60
|
-
"@tanstack/router-plugin": "^1.167.
|
|
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.
|
|
65
|
-
"lucide-react": "^1.
|
|
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.
|
|
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.
|
|
85
|
-
"oxfmt": "^0.
|
|
86
|
-
"oxlint": "^1.
|
|
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 <
|
|
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
|
]
|
package/playwright.config.ts
CHANGED
|
@@ -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
|
|
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:
|
|
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
|
},
|