birdclaw 0.1.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 +32 -0
- package/LICENSE +21 -0
- package/README.md +389 -0
- package/bin/birdclaw.mjs +28 -0
- package/package.json +100 -0
- package/playwright.config.ts +31 -0
- package/public/favicon.ico +0 -0
- package/public/logo192.png +0 -0
- package/public/logo512.png +0 -0
- package/public/manifest.json +25 -0
- package/public/robots.txt +3 -0
- package/src/cli-moderation.ts +210 -0
- package/src/cli.ts +450 -0
- package/src/components/AppNav.tsx +49 -0
- package/src/components/AvatarChip.tsx +47 -0
- package/src/components/DmWorkspace.tsx +246 -0
- package/src/components/EmbeddedTweetCard.tsx +44 -0
- package/src/components/InboxCard.tsx +136 -0
- package/src/components/ProfilePreview.tsx +55 -0
- package/src/components/ThemeSlider.tsx +124 -0
- package/src/components/TimelineCard.tsx +118 -0
- package/src/components/TweetMediaGrid.tsx +39 -0
- package/src/components/TweetRichText.tsx +85 -0
- package/src/lib/actions-transport.ts +173 -0
- package/src/lib/archive-finder.ts +128 -0
- package/src/lib/archive-import.ts +736 -0
- package/src/lib/avatar-cache.ts +184 -0
- package/src/lib/bird-actions.ts +200 -0
- package/src/lib/bird.ts +183 -0
- package/src/lib/blocklist.ts +119 -0
- package/src/lib/blocks-write.ts +118 -0
- package/src/lib/blocks.ts +326 -0
- package/src/lib/config.ts +152 -0
- package/src/lib/db.ts +326 -0
- package/src/lib/dms-live.ts +279 -0
- package/src/lib/inbox.ts +210 -0
- package/src/lib/mentions-export.ts +147 -0
- package/src/lib/mentions-live.ts +475 -0
- package/src/lib/moderation-target.ts +171 -0
- package/src/lib/moderation-write.ts +72 -0
- package/src/lib/mutes-write.ts +118 -0
- package/src/lib/mutes.ts +77 -0
- package/src/lib/openai.ts +86 -0
- package/src/lib/present.ts +20 -0
- package/src/lib/profile-hydration.ts +144 -0
- package/src/lib/profile-replies.ts +60 -0
- package/src/lib/queries.ts +725 -0
- package/src/lib/seed.ts +486 -0
- package/src/lib/sync-cache.ts +65 -0
- package/src/lib/theme-transition.ts +117 -0
- package/src/lib/theme.tsx +157 -0
- package/src/lib/tweet-render.ts +115 -0
- package/src/lib/types.ts +316 -0
- package/src/lib/ui.ts +270 -0
- package/src/lib/x-profile.ts +203 -0
- package/src/lib/x-web.ts +168 -0
- package/src/lib/xurl.ts +492 -0
- package/src/routeTree.gen.ts +282 -0
- package/src/router.tsx +20 -0
- package/src/routes/__root.tsx +64 -0
- package/src/routes/api/action.tsx +88 -0
- package/src/routes/api/avatar.tsx +41 -0
- package/src/routes/api/blocks.tsx +32 -0
- package/src/routes/api/inbox.tsx +35 -0
- package/src/routes/api/query.tsx +72 -0
- package/src/routes/api/status.tsx +15 -0
- package/src/routes/blocks.tsx +352 -0
- package/src/routes/dms.tsx +262 -0
- package/src/routes/inbox.tsx +201 -0
- package/src/routes/index.tsx +125 -0
- package/src/routes/mentions.tsx +125 -0
- package/src/styles.css +109 -0
- package/tsconfig.json +30 -0
- package/vite.config.ts +23 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# CHANGELOG
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## 0.1.0 - 2026-04-27
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Add X web cookie fallback for block and unblock actions when the X API rejects OAuth2 block writes.
|
|
10
|
+
- Add `profiles replies` so moderation triage can inspect a user's recent reply pattern before blocking.
|
|
11
|
+
- Add `blocks import <path>` for one-shot blocklist application from a file.
|
|
12
|
+
- Add paged `mentions export --mode xurl --all --max-pages <n>` so moderation loops can scan the full retrievable mentions window.
|
|
13
|
+
- Add `actions.transport` config plus shared action transport routing for `bird`, `xurl`, and `auto`.
|
|
14
|
+
- Add transport-aware mute/unmute support to the API action route.
|
|
15
|
+
- Add the first packaged `birdclaw` CLI release.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- Capture `xurl` mutation error bodies so transport fallbacks can key off the real API failure.
|
|
20
|
+
- Make `birdclaw` block and unblock flows succeed remotely again on Peter's current auth setup.
|
|
21
|
+
- Verify forced `xurl` mute/block writes through `bird status` before mutating local sqlite.
|
|
22
|
+
- Cache authenticated `xurl whoami` lookups so repeated moderation writes do less redundant auth work.
|
|
23
|
+
- Strip inherited `--localstorage-file` from the Playwright web-server env to avoid noisy cross-repo test warnings.
|
|
24
|
+
- Override Node 25 native web storage in jsdom test setup so Vitest runs stop emitting `--localstorage-file` warnings.
|
|
25
|
+
|
|
26
|
+
### Docs
|
|
27
|
+
|
|
28
|
+
- Document block transport behavior and fallback path in the CLI/docs.
|
|
29
|
+
- Document the reply-pattern inspection flow for borderline AI/slop accounts.
|
|
30
|
+
- Document blocklist import file format and usage.
|
|
31
|
+
- Document paged xurl mention export for agent moderation runs.
|
|
32
|
+
- Document that mention reads and moderation writes use separate config knobs.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Peter Steinberger
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
# birdclaw
|
|
2
|
+
|
|
3
|
+
`birdclaw` is a local-first X workspace: archive import, cached live reads, focused triage, and reply flows in one local web app + CLI.
|
|
4
|
+
|
|
5
|
+
Status: WIP. Real and usable. Not done. Expect schema churn, transport gaps, and rough edges while the core settles.
|
|
6
|
+
|
|
7
|
+
## What It Does
|
|
8
|
+
|
|
9
|
+
- keeps your X data in local SQLite
|
|
10
|
+
- stores media and avatar cache under `~/.birdclaw`
|
|
11
|
+
- imports archives when you have them
|
|
12
|
+
- still works when you do not
|
|
13
|
+
- gives you a clean local UI for home, mentions, DMs, inbox, and blocks
|
|
14
|
+
- exposes scriptable JSON for agents and automation
|
|
15
|
+
|
|
16
|
+
## What Works Today
|
|
17
|
+
|
|
18
|
+
### Local data + storage
|
|
19
|
+
|
|
20
|
+
- one shared SQLite DB for multiple accounts
|
|
21
|
+
- FTS5 search over tweets and DMs
|
|
22
|
+
- archive autodiscovery on macOS
|
|
23
|
+
- archive import for tweets, likes, profiles, and full DMs
|
|
24
|
+
- profile hydration from live X metadata
|
|
25
|
+
- local avatar cache
|
|
26
|
+
- local media cache root under `~/.birdclaw`
|
|
27
|
+
|
|
28
|
+
### Web UI
|
|
29
|
+
|
|
30
|
+
- `Home` timeline
|
|
31
|
+
- `Mentions` queue
|
|
32
|
+
- `DMs` workspace with two-column layout
|
|
33
|
+
- `Inbox` for mixed mention + DM triage
|
|
34
|
+
- `Blocks` for local blocklist maintenance
|
|
35
|
+
- constrained timeline lane instead of full-width dashboard UI
|
|
36
|
+
- tweet expansion with URLs, inline images, quoted tweets, replies, and profile hover cards
|
|
37
|
+
- sender bio and influence context in the DM detail header
|
|
38
|
+
- system / light / dark theme switcher with animated transition
|
|
39
|
+
|
|
40
|
+
### Triage + filtering
|
|
41
|
+
|
|
42
|
+
- replied / unreplied filters for timelines
|
|
43
|
+
- DM filters by participant, followers, and derived influence score
|
|
44
|
+
- AI-ranked inbox for mentions + DMs
|
|
45
|
+
- OpenAI scoring hook for low-signal filtering
|
|
46
|
+
- cached live mentions export in `xurl`-compatible JSON
|
|
47
|
+
- live profile-reply inspection for borderline AI/slop triage
|
|
48
|
+
- one-shot blocklist import from a file for batch moderation passes
|
|
49
|
+
|
|
50
|
+
### Actions
|
|
51
|
+
|
|
52
|
+
- post tweets
|
|
53
|
+
- reply to tweets
|
|
54
|
+
- reply to DMs
|
|
55
|
+
- add / remove local blocks
|
|
56
|
+
- import batch blocklists in one call
|
|
57
|
+
- add / remove local mutes
|
|
58
|
+
- sync remote blocks through `xurl` when available
|
|
59
|
+
- fall back to the X web cookie session when OAuth2 block writes are rejected
|
|
60
|
+
|
|
61
|
+
### Safety
|
|
62
|
+
|
|
63
|
+
- local-first by default
|
|
64
|
+
- tests disable live writes
|
|
65
|
+
- CI disables live writes
|
|
66
|
+
- app has no auth layer because it is a local-only tool
|
|
67
|
+
|
|
68
|
+
## Still In Progress
|
|
69
|
+
|
|
70
|
+
- broader resumable live sync beyond the targeted paths already wired
|
|
71
|
+
- fuller media fetch pipeline
|
|
72
|
+
- richer multi-account UX
|
|
73
|
+
- more complete transport coverage
|
|
74
|
+
- more archive edge-case handling
|
|
75
|
+
|
|
76
|
+
If you need polished product-grade sync parity today, this is not there yet.
|
|
77
|
+
|
|
78
|
+
## Screens
|
|
79
|
+
|
|
80
|
+
- `Home`: read and reply without fighting the main X timeline
|
|
81
|
+
- `Mentions`: work the reply queue with clean filters
|
|
82
|
+
- `DMs`: triage by sender context, follower count, and influence
|
|
83
|
+
- `Inbox`: let heuristics / OpenAI float likely-important items
|
|
84
|
+
- `Blocks`: maintain a local-first account-scoped blocklist
|
|
85
|
+
|
|
86
|
+
## Storage
|
|
87
|
+
|
|
88
|
+
Default root:
|
|
89
|
+
|
|
90
|
+
```text
|
|
91
|
+
~/.birdclaw
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Important paths:
|
|
95
|
+
|
|
96
|
+
- DB: `~/.birdclaw/birdclaw.sqlite`
|
|
97
|
+
- media cache: `~/.birdclaw/media`
|
|
98
|
+
- avatar cache: `~/.birdclaw/media/thumbs/avatars`
|
|
99
|
+
- Playwright test home: `.playwright-home`
|
|
100
|
+
|
|
101
|
+
Override the root:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
export BIRDCLAW_HOME=/path/to/custom/root
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Requirements
|
|
108
|
+
|
|
109
|
+
- Node `25.8.1`
|
|
110
|
+
- `pnpm`
|
|
111
|
+
- macOS recommended for Spotlight archive discovery
|
|
112
|
+
- `xurl` optional for live reads / writes
|
|
113
|
+
- OpenAI API key optional for inbox scoring
|
|
114
|
+
|
|
115
|
+
## Install
|
|
116
|
+
|
|
117
|
+
Homebrew:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
brew install steipete/tap/birdclaw
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
From source:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
fnm use
|
|
127
|
+
pnpm install
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Run
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
pnpm dev
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Open:
|
|
137
|
+
|
|
138
|
+
```text
|
|
139
|
+
http://localhost:3000
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Quick Start
|
|
143
|
+
|
|
144
|
+
Initialize local state:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
birdclaw init
|
|
148
|
+
birdclaw auth status --json
|
|
149
|
+
birdclaw db stats --json
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Find and import an archive:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
birdclaw archive find --json
|
|
156
|
+
birdclaw import archive --json
|
|
157
|
+
birdclaw import archive ~/Downloads/twitter-archive-2025.zip --json
|
|
158
|
+
birdclaw import hydrate-profiles --json
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Start the app:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
birdclaw serve
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
First moderation pass:
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
pnpm cli mentions export --mode xurl --refresh --all --max-pages 9 --limit 100
|
|
171
|
+
pnpm cli profiles replies @borderline_handle --limit 12 --json
|
|
172
|
+
pnpm cli blocks import ~/triage/blocklist.txt --account acct_primary --json
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## CLI Highlights
|
|
176
|
+
|
|
177
|
+
### Search local tweets
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
pnpm cli search tweets "local-first" --json
|
|
181
|
+
pnpm cli search tweets "sync engine" --limit 20 --json
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Export mentions for agents
|
|
185
|
+
|
|
186
|
+
Default `birdclaw` mode returns normalized items with `text`, `plainText`, `markdown`, author metadata, and canonical URLs:
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
pnpm cli mentions export "agent" --unreplied --limit 10
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Cached live modes return `xurl`-compatible `data/includes/meta`, but stay in the local SQLite cache so repeat reads do not keep spending live calls:
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
pnpm cli mentions export --mode bird --limit 20
|
|
196
|
+
pnpm cli mentions export --mode bird --refresh --limit 20
|
|
197
|
+
pnpm cli mentions export --mode xurl --limit 5
|
|
198
|
+
pnpm cli mentions export --mode xurl --refresh --limit 5
|
|
199
|
+
pnpm cli mentions export --mode xurl --refresh --all --max-pages 9 --limit 100
|
|
200
|
+
pnpm cli mentions export "courtesy" --mode xurl --limit 5
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Home config lives in `~/.birdclaw/config.json`. Example:
|
|
204
|
+
|
|
205
|
+
```json
|
|
206
|
+
{
|
|
207
|
+
"actions": {
|
|
208
|
+
"transport": "auto"
|
|
209
|
+
},
|
|
210
|
+
"mentions": {
|
|
211
|
+
"dataSource": "bird",
|
|
212
|
+
"birdCommand": "/Users/steipete/Projects/bird/bird"
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Notes:
|
|
218
|
+
|
|
219
|
+
- `--refresh` forces a live fetch
|
|
220
|
+
- `--cache-ttl <seconds>` tunes freshness
|
|
221
|
+
- `--all` walks every retrievable mentions page; `--max-pages` caps that scan
|
|
222
|
+
- in paged `xurl` mode, `--limit` is the per-page size
|
|
223
|
+
- `mentions.dataSource` controls live mention reads only
|
|
224
|
+
- `actions.transport` controls live block/mute writes only
|
|
225
|
+
- `actions.transport` accepts `auto`, `bird`, or `xurl`
|
|
226
|
+
- `bird` mode uses your local `bird` CLI and caches its mentions output into birdclaw's canonical store
|
|
227
|
+
- filters still work in `xurl` mode; filtered payloads are rebuilt from the local canonical store after sync
|
|
228
|
+
|
|
229
|
+
### Search and triage DMs
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
pnpm cli search dms "prototype" --json
|
|
233
|
+
pnpm cli search dms "layout" --min-followers 1000 --min-influence-score 120 --sort influence --json
|
|
234
|
+
pnpm cli dms sync --limit 50 --refresh --json
|
|
235
|
+
pnpm cli dms list --refresh --limit 10 --json
|
|
236
|
+
pnpm cli dms list --unreplied --min-followers 500 --min-influence-score 90 --sort influence --json
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### AI inbox
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
pnpm cli inbox --json
|
|
243
|
+
pnpm cli inbox --kind dms --limit 10 --json
|
|
244
|
+
pnpm cli inbox --score --hide-low-signal --limit 8 --json
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Blocklist
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
pnpm cli blocks list --account acct_primary --json
|
|
251
|
+
pnpm cli blocks sync --account acct_primary --json
|
|
252
|
+
pnpm cli blocks import ~/triage/blocklist.txt --account acct_primary --json
|
|
253
|
+
pnpm cli blocks add @amelia --account acct_primary --json
|
|
254
|
+
pnpm cli blocks record @amelia --account acct_primary --json
|
|
255
|
+
pnpm cli blocks remove @amelia --account acct_primary --json
|
|
256
|
+
pnpm cli ban @amelia --account acct_primary --transport auto --json
|
|
257
|
+
pnpm cli unban @amelia --account acct_primary --transport bird --json
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Notes:
|
|
261
|
+
|
|
262
|
+
- `ban` / `unban` accept `--transport auto|bird|xurl`
|
|
263
|
+
- `auto` tries `bird` first, then falls back to `xurl` when bird fails
|
|
264
|
+
- forced `xurl` writes still verify through `bird status` before sqlite changes
|
|
265
|
+
- X still rejects pure OAuth2 block writes, so `auto` is the safe default for block/unblock
|
|
266
|
+
- `blocks import` accepts newline-delimited blocklists with comments and markdown bullets
|
|
267
|
+
- `blocks sync` is for slow/manual remote reconciliation; not for a hot cron loop
|
|
268
|
+
- `blocks record` stores a known-good remote block locally without issuing another live write
|
|
269
|
+
|
|
270
|
+
Example blocklist file:
|
|
271
|
+
|
|
272
|
+
```text
|
|
273
|
+
# crypto / AI slop
|
|
274
|
+
@jpctan
|
|
275
|
+
@SystemDaddyAi
|
|
276
|
+
- @Pepe202579 memecoin bait
|
|
277
|
+
https://x.com/someone/status/2030857479001960633?s=20
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Profile reply scan
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
pnpm cli profiles replies @jpctan --limit 12 --json
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
Notes:
|
|
287
|
+
|
|
288
|
+
- for the "unsure if AI" case
|
|
289
|
+
- scans recent authored tweets, excludes retweets, keeps replies
|
|
290
|
+
- useful for spotting repeated generic praise, abstraction soup, or cross-thread templated cadence
|
|
291
|
+
|
|
292
|
+
Typical tell:
|
|
293
|
+
|
|
294
|
+
- same upbeat, generic reply shape across unrelated threads in a short time window
|
|
295
|
+
|
|
296
|
+
### Mutes
|
|
297
|
+
|
|
298
|
+
```bash
|
|
299
|
+
pnpm cli mutes list --account acct_primary --json
|
|
300
|
+
pnpm cli mute @amelia --account acct_primary --transport xurl --json
|
|
301
|
+
pnpm cli mutes record @amelia --account acct_primary --json
|
|
302
|
+
pnpm cli unmute @amelia --account acct_primary --transport auto --json
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
Notes:
|
|
306
|
+
|
|
307
|
+
- `mute` / `unmute` accept `--transport auto|bird|xurl`
|
|
308
|
+
- target profile resolution prefers `bird user --json` before any `xurl /2/users` lookup
|
|
309
|
+
- `auto` tries `bird` first, then falls back to `xurl` when bird fails
|
|
310
|
+
- forced `xurl` writes still verify through `bird status` before sqlite changes
|
|
311
|
+
- `mutes record` stores a known-good remote mute locally without issuing another live write
|
|
312
|
+
|
|
313
|
+
### Test env hardening
|
|
314
|
+
|
|
315
|
+
- Playwright strips inherited `--localstorage-file` from `NODE_OPTIONS` before starting Vite
|
|
316
|
+
- this avoids cross-repo test warnings when another repo injected that flag
|
|
317
|
+
|
|
318
|
+
### Compose / reply
|
|
319
|
+
|
|
320
|
+
```bash
|
|
321
|
+
pnpm cli compose post "Ship local software."
|
|
322
|
+
pnpm cli compose reply tweet_004 "On it."
|
|
323
|
+
pnpm cli compose dm dm_003 "Send it over."
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
## Typical Workflow
|
|
327
|
+
|
|
328
|
+
1. import your archive if you have one
|
|
329
|
+
2. hydrate imported profiles from live X metadata
|
|
330
|
+
3. use `Home` for reading
|
|
331
|
+
4. use `Mentions` for reply triage
|
|
332
|
+
5. when one account feels borderline, inspect `profiles replies`
|
|
333
|
+
6. collect keepers into a blocklist file and run `blocks import`
|
|
334
|
+
7. use `DMs` for high-context conversation work
|
|
335
|
+
8. use `Inbox` when you want AI help cutting noise
|
|
336
|
+
9. use CLI exports when agents need stable JSON
|
|
337
|
+
|
|
338
|
+
## Live Transport
|
|
339
|
+
|
|
340
|
+
Current preference:
|
|
341
|
+
|
|
342
|
+
- `xurl` first
|
|
343
|
+
|
|
344
|
+
Without `xurl`, `birdclaw` still works in local/archive mode.
|
|
345
|
+
|
|
346
|
+
Check transport:
|
|
347
|
+
|
|
348
|
+
```bash
|
|
349
|
+
pnpm cli auth status --json
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
## Architecture
|
|
353
|
+
|
|
354
|
+
- SQLite is the canonical local truth
|
|
355
|
+
- archive import and live transport should converge on the same model
|
|
356
|
+
- CLI and web UI share the same normalized core
|
|
357
|
+
- AI ranking is layered on top of local data, not the source of truth
|
|
358
|
+
|
|
359
|
+
## Testing
|
|
360
|
+
|
|
361
|
+
```bash
|
|
362
|
+
fnm exec --using 25.8.1 pnpm check
|
|
363
|
+
fnm exec --using 25.8.1 pnpm test
|
|
364
|
+
fnm exec --using 25.8.1 pnpm coverage
|
|
365
|
+
fnm exec --using 25.8.1 pnpm build
|
|
366
|
+
fnm exec --using 25.8.1 pnpm e2e
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
Current bar:
|
|
370
|
+
|
|
371
|
+
- branch coverage above `80%`
|
|
372
|
+
- Playwright coverage for core UI flows
|
|
373
|
+
|
|
374
|
+
## CI
|
|
375
|
+
|
|
376
|
+
GitHub Actions runs:
|
|
377
|
+
|
|
378
|
+
- `pnpm check`
|
|
379
|
+
- `pnpm coverage`
|
|
380
|
+
- `pnpm build`
|
|
381
|
+
- `pnpm e2e`
|
|
382
|
+
|
|
383
|
+
Workflow: [ci.yml](/Users/steipete/Projects/birdclaw/.github/workflows/ci.yml)
|
|
384
|
+
|
|
385
|
+
## Docs
|
|
386
|
+
|
|
387
|
+
- [spec.md](/Users/steipete/Projects/birdclaw/docs/spec.md)
|
|
388
|
+
- [cli.md](/Users/steipete/Projects/birdclaw/docs/cli.md)
|
|
389
|
+
- [data-architecture.md](/Users/steipete/Projects/birdclaw/docs/data-architecture.md)
|
package/bin/birdclaw.mjs
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
7
|
+
const tsxCli = join(packageRoot, "node_modules", "tsx", "dist", "cli.mjs");
|
|
8
|
+
const birdclawCli = join(packageRoot, "src", "cli.ts");
|
|
9
|
+
|
|
10
|
+
const result = spawnSync(
|
|
11
|
+
process.execPath,
|
|
12
|
+
[tsxCli, birdclawCli, ...process.argv.slice(2)],
|
|
13
|
+
{
|
|
14
|
+
stdio: "inherit",
|
|
15
|
+
env: process.env,
|
|
16
|
+
},
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
if (result.error) {
|
|
20
|
+
console.error(result.error.message);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (result.signal) {
|
|
25
|
+
process.kill(process.pid, result.signal);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
process.exit(result.status ?? 0);
|
package/package.json
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "birdclaw",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Local-first X workspace for archives, DMs, mentions, and moderation",
|
|
5
|
+
"homepage": "https://github.com/steipete/birdclaw#readme",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/steipete/birdclaw.git"
|
|
10
|
+
},
|
|
11
|
+
"bin": {
|
|
12
|
+
"birdclaw": "bin/birdclaw.mjs"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"bin/",
|
|
16
|
+
"src/",
|
|
17
|
+
"!src/**/*.test.ts",
|
|
18
|
+
"!src/**/*.test.tsx",
|
|
19
|
+
"!src/test/",
|
|
20
|
+
"public/",
|
|
21
|
+
"CHANGELOG.md",
|
|
22
|
+
"LICENSE",
|
|
23
|
+
"README.md",
|
|
24
|
+
"package.json",
|
|
25
|
+
"playwright.config.ts",
|
|
26
|
+
"pnpm-lock.yaml",
|
|
27
|
+
"tsconfig.json",
|
|
28
|
+
"vite.config.ts"
|
|
29
|
+
],
|
|
30
|
+
"type": "module",
|
|
31
|
+
"imports": {
|
|
32
|
+
"#/*": "./src/*"
|
|
33
|
+
},
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"dev": "vite dev --port 3000",
|
|
39
|
+
"build": "vite build",
|
|
40
|
+
"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",
|
|
43
|
+
"e2e": "playwright test",
|
|
44
|
+
"format": "oxfmt --write src scripts playwright vite.config.ts vitest.config.ts playwright.config.ts",
|
|
45
|
+
"format:check": "oxfmt --check src scripts playwright vite.config.ts vitest.config.ts playwright.config.ts",
|
|
46
|
+
"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",
|
|
47
|
+
"check": "pnpm run format:check && pnpm run lint",
|
|
48
|
+
"cli": "tsx src/cli.ts"
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"@steipete/sweet-cookie": "^0.2.0",
|
|
52
|
+
"@tailwindcss/vite": "^4.2.4",
|
|
53
|
+
"@tanstack/devtools-vite": "0.6.0",
|
|
54
|
+
"@tanstack/react-devtools": "0.10.2",
|
|
55
|
+
"@tanstack/react-router": "1.168.24",
|
|
56
|
+
"@tanstack/react-router-devtools": "1.166.13",
|
|
57
|
+
"@tanstack/react-router-ssr-query": "1.166.12",
|
|
58
|
+
"@tanstack/react-start": "1.167.49",
|
|
59
|
+
"@tanstack/router-plugin": "^1.167.27",
|
|
60
|
+
"@vitejs/plugin-react": "^6.0.1",
|
|
61
|
+
"better-sqlite3": "^12.9.0",
|
|
62
|
+
"commander": "^14.0.3",
|
|
63
|
+
"kysely": "^0.28.16",
|
|
64
|
+
"lucide-react": "^1.11.0",
|
|
65
|
+
"react": "^19.2.5",
|
|
66
|
+
"react-dom": "^19.2.5",
|
|
67
|
+
"tailwindcss": "^4.2.4",
|
|
68
|
+
"tsx": "^4.21.0",
|
|
69
|
+
"vite": "^8.0.10",
|
|
70
|
+
"zod": "^4.3.6"
|
|
71
|
+
},
|
|
72
|
+
"devDependencies": {
|
|
73
|
+
"@playwright/test": "^1.59.1",
|
|
74
|
+
"@tailwindcss/typography": "^0.5.19",
|
|
75
|
+
"@testing-library/dom": "^10.4.1",
|
|
76
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
77
|
+
"@testing-library/react": "^16.3.2",
|
|
78
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
79
|
+
"@types/node": "^25.6.0",
|
|
80
|
+
"@types/react": "^19.2.14",
|
|
81
|
+
"@types/react-dom": "^19.2.3",
|
|
82
|
+
"@vitest/coverage-v8": "^4.1.5",
|
|
83
|
+
"jsdom": "^29.0.2",
|
|
84
|
+
"oxfmt": "^0.46.0",
|
|
85
|
+
"oxlint": "^1.61.0",
|
|
86
|
+
"typescript": "^6.0.3",
|
|
87
|
+
"vitest": "^4.1.5"
|
|
88
|
+
},
|
|
89
|
+
"engines": {
|
|
90
|
+
"node": ">=25.8.1 <26"
|
|
91
|
+
},
|
|
92
|
+
"packageManager": "pnpm@10.31.0",
|
|
93
|
+
"pnpm": {
|
|
94
|
+
"onlyBuiltDependencies": [
|
|
95
|
+
"better-sqlite3",
|
|
96
|
+
"esbuild",
|
|
97
|
+
"lightningcss"
|
|
98
|
+
]
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { defineConfig, devices } from "@playwright/test";
|
|
3
|
+
|
|
4
|
+
const testHome = path.join(process.cwd(), ".playwright-home");
|
|
5
|
+
|
|
6
|
+
export default defineConfig({
|
|
7
|
+
testDir: "./playwright",
|
|
8
|
+
fullyParallel: false,
|
|
9
|
+
retries: 0,
|
|
10
|
+
workers: 1,
|
|
11
|
+
use: {
|
|
12
|
+
baseURL: "http://127.0.0.1:3000",
|
|
13
|
+
trace: "on-first-retry",
|
|
14
|
+
},
|
|
15
|
+
projects: [
|
|
16
|
+
{
|
|
17
|
+
name: "chromium",
|
|
18
|
+
use: { ...devices["Desktop Chrome"] },
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
webServer: {
|
|
22
|
+
command: "node ./scripts/start-test-server.mjs",
|
|
23
|
+
url: "http://127.0.0.1:3000",
|
|
24
|
+
reuseExistingServer: false,
|
|
25
|
+
timeout: 120000,
|
|
26
|
+
env: {
|
|
27
|
+
BIRDCLAW_HOME: testHome,
|
|
28
|
+
BIRDCLAW_DISABLE_LIVE_WRITES: "1",
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
});
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"short_name": "TanStack App",
|
|
3
|
+
"name": "Create TanStack App Sample",
|
|
4
|
+
"icons": [
|
|
5
|
+
{
|
|
6
|
+
"src": "favicon.ico",
|
|
7
|
+
"sizes": "64x64 32x32 24x24 16x16",
|
|
8
|
+
"type": "image/x-icon"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"src": "logo192.png",
|
|
12
|
+
"type": "image/png",
|
|
13
|
+
"sizes": "192x192"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"src": "logo512.png",
|
|
17
|
+
"type": "image/png",
|
|
18
|
+
"sizes": "512x512"
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"start_url": ".",
|
|
22
|
+
"display": "standalone",
|
|
23
|
+
"theme_color": "#000000",
|
|
24
|
+
"background_color": "#ffffff"
|
|
25
|
+
}
|