birdclaw 0.5.1 → 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.
- package/CHANGELOG.md +44 -1
- package/README.md +50 -5
- package/package.json +3 -2
- package/scripts/browser-perf.mjs +1 -0
- package/scripts/start-test-server.mjs +16 -3
- package/src/cli.ts +376 -13
- package/src/components/AccountSwitcher.tsx +186 -0
- package/src/components/AppNav.tsx +27 -7
- package/src/components/AvatarChip.tsx +9 -3
- package/src/components/DmWorkspace.tsx +18 -8
- package/src/components/LinkPreviewCard.tsx +40 -18
- package/src/components/MarkdownViewer.tsx +452 -0
- package/src/components/SyncNowButton.tsx +57 -25
- package/src/components/ThemeSlider.tsx +55 -50
- package/src/components/TimelineCard.tsx +225 -93
- package/src/components/TimelineRouteFrame.tsx +22 -8
- package/src/components/TweetMediaGrid.tsx +87 -38
- package/src/components/TweetRichText.tsx +15 -11
- package/src/components/account-selection.ts +64 -0
- package/src/components/useTimelineRouteData.ts +23 -7
- package/src/lib/account-sync-job.ts +654 -0
- package/src/lib/actions-transport.ts +216 -146
- package/src/lib/api-client.ts +128 -53
- package/src/lib/archive-finder.ts +78 -63
- package/src/lib/archive-import.ts +1364 -1300
- package/src/lib/authored-live.ts +261 -204
- package/src/lib/avatar-cache.ts +159 -44
- package/src/lib/backup.ts +1532 -951
- package/src/lib/bird-actions.ts +139 -57
- package/src/lib/bird-command.ts +101 -28
- package/src/lib/bird.ts +549 -194
- package/src/lib/blocklist.ts +40 -23
- package/src/lib/blocks-write.ts +129 -80
- package/src/lib/blocks.ts +165 -97
- package/src/lib/bookmark-sync-job.ts +250 -160
- package/src/lib/conversation-surface.ts +79 -48
- package/src/lib/db.ts +33 -3
- package/src/lib/dms-live.ts +720 -66
- package/src/lib/effect-runtime.ts +45 -0
- package/src/lib/follow-graph.ts +224 -180
- package/src/lib/http-effect.ts +222 -0
- package/src/lib/inbox.ts +74 -43
- package/src/lib/link-index.ts +88 -76
- package/src/lib/link-insights.ts +24 -0
- package/src/lib/link-preview-metadata.ts +472 -52
- package/src/lib/media-fetch.ts +286 -213
- package/src/lib/mention-threads-live.ts +352 -288
- package/src/lib/mentions-live.ts +390 -342
- package/src/lib/moderation-target.ts +102 -65
- package/src/lib/moderation-write.ts +77 -18
- package/src/lib/mutes-write.ts +129 -80
- package/src/lib/mutes.ts +8 -1
- package/src/lib/openai.ts +84 -53
- package/src/lib/period-digest.ts +953 -0
- package/src/lib/profile-affiliation-hydration.ts +93 -54
- package/src/lib/profile-hydration.ts +124 -72
- package/src/lib/profile-replies.ts +60 -43
- package/src/lib/profile-resolver.ts +402 -294
- package/src/lib/queries.ts +969 -199
- package/src/lib/research.ts +165 -120
- package/src/lib/sqlite.ts +1 -0
- package/src/lib/timeline-collections-live.ts +204 -167
- package/src/lib/timeline-live.ts +60 -39
- package/src/lib/tweet-lookup.ts +30 -19
- package/src/lib/types.ts +38 -1
- package/src/lib/ui.ts +30 -7
- package/src/lib/url-expansion.ts +226 -55
- package/src/lib/url-safety.ts +220 -0
- package/src/lib/web-sync.ts +216 -148
- package/src/lib/whois.ts +166 -147
- package/src/lib/x-web.ts +102 -71
- package/src/lib/xurl.ts +681 -411
- package/src/routeTree.gen.ts +42 -0
- package/src/routes/__root.tsx +25 -5
- package/src/routes/api/action.tsx +127 -78
- package/src/routes/api/avatar.tsx +39 -30
- package/src/routes/api/blocks.tsx +26 -23
- package/src/routes/api/conversation.tsx +25 -14
- package/src/routes/api/inbox.tsx +27 -21
- package/src/routes/api/link-insights.tsx +31 -25
- package/src/routes/api/link-preview.tsx +25 -21
- package/src/routes/api/period-digest.tsx +123 -0
- package/src/routes/api/profile-hydrate.tsx +31 -28
- package/src/routes/api/query.tsx +79 -55
- package/src/routes/api/status.tsx +18 -10
- package/src/routes/api/sync.tsx +75 -29
- package/src/routes/dms.tsx +95 -28
- package/src/routes/inbox.tsx +32 -19
- package/src/routes/today.tsx +441 -0
package/src/cli.ts
CHANGED
|
@@ -17,6 +17,11 @@ import {
|
|
|
17
17
|
syncAuthoredTweets,
|
|
18
18
|
type AuthoredSyncMode,
|
|
19
19
|
} from "#/lib/authored-live";
|
|
20
|
+
import {
|
|
21
|
+
installAccountSyncLaunchAgent,
|
|
22
|
+
parseAccountSyncSteps,
|
|
23
|
+
runAccountSyncJob,
|
|
24
|
+
} from "#/lib/account-sync-job";
|
|
20
25
|
import {
|
|
21
26
|
exportBackup,
|
|
22
27
|
importBackup,
|
|
@@ -29,6 +34,7 @@ import {
|
|
|
29
34
|
installBookmarkSyncLaunchAgent,
|
|
30
35
|
runBookmarkSyncJob,
|
|
31
36
|
} from "#/lib/bookmark-sync-job";
|
|
37
|
+
import { runDirectMessageRequestMutationViaBird } from "#/lib/bird";
|
|
32
38
|
import { importBlocklist } from "#/lib/blocklist";
|
|
33
39
|
import {
|
|
34
40
|
type ActionsTransport,
|
|
@@ -36,7 +42,11 @@ import {
|
|
|
36
42
|
getBirdclawPaths,
|
|
37
43
|
resolveMentionsDataSource,
|
|
38
44
|
} from "#/lib/config";
|
|
39
|
-
import {
|
|
45
|
+
import { closeDatabase } from "#/lib/db";
|
|
46
|
+
import {
|
|
47
|
+
type DirectMessagesSyncMode,
|
|
48
|
+
syncDirectMessagesViaCachedBird,
|
|
49
|
+
} from "#/lib/dms-live";
|
|
40
50
|
import { listInboxItems, scoreInbox } from "#/lib/inbox";
|
|
41
51
|
import { backfillLinkIndex, searchLinks } from "#/lib/link-index";
|
|
42
52
|
import { fetchTweetMedia, formatMediaFetchResult } from "#/lib/media-fetch";
|
|
@@ -47,6 +57,11 @@ import {
|
|
|
47
57
|
exportMentionsViaCachedXurl,
|
|
48
58
|
syncMentions,
|
|
49
59
|
} from "#/lib/mentions-live";
|
|
60
|
+
import {
|
|
61
|
+
streamPeriodDigest,
|
|
62
|
+
type PeriodDigestOptions,
|
|
63
|
+
type PeriodDigestPreset,
|
|
64
|
+
} from "#/lib/period-digest";
|
|
50
65
|
import {
|
|
51
66
|
getFollowGraphSummary,
|
|
52
67
|
listFollowEvents,
|
|
@@ -61,6 +76,7 @@ import { resolveProfilesForIds } from "#/lib/profile-resolver";
|
|
|
61
76
|
import { inspectProfileReplies } from "#/lib/profile-replies";
|
|
62
77
|
import { runResearchMode } from "#/lib/research";
|
|
63
78
|
import {
|
|
79
|
+
applyDmRequestMutationToLocalStore,
|
|
64
80
|
createDmReply,
|
|
65
81
|
createPost,
|
|
66
82
|
createTweetReply,
|
|
@@ -154,6 +170,37 @@ function parsePositiveIntegerOption(value: string | undefined, option: string) {
|
|
|
154
170
|
return parsed;
|
|
155
171
|
}
|
|
156
172
|
|
|
173
|
+
function parseDmInboxOption(
|
|
174
|
+
value: string | undefined,
|
|
175
|
+
): "all" | "accepted" | "requests" | undefined {
|
|
176
|
+
const normalized = (value ?? "all").trim().toLowerCase();
|
|
177
|
+
if (
|
|
178
|
+
normalized === "all" ||
|
|
179
|
+
normalized === "accepted" ||
|
|
180
|
+
normalized === "requests"
|
|
181
|
+
) {
|
|
182
|
+
return normalized;
|
|
183
|
+
}
|
|
184
|
+
if (normalized === "request") {
|
|
185
|
+
return "requests";
|
|
186
|
+
}
|
|
187
|
+
printError("--inbox must be all, accepted, or requests");
|
|
188
|
+
process.exitCode = 1;
|
|
189
|
+
return undefined;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function parseDmSyncModeOption(
|
|
193
|
+
value: string | undefined,
|
|
194
|
+
): DirectMessagesSyncMode | undefined {
|
|
195
|
+
const normalized = (value ?? "bird").trim().toLowerCase();
|
|
196
|
+
if (normalized === "auto" || normalized === "bird" || normalized === "xurl") {
|
|
197
|
+
return normalized;
|
|
198
|
+
}
|
|
199
|
+
printError("--mode must be auto, bird, or xurl");
|
|
200
|
+
process.exitCode = 1;
|
|
201
|
+
return undefined;
|
|
202
|
+
}
|
|
203
|
+
|
|
157
204
|
function parseArchiveImportSelect(value: string | undefined) {
|
|
158
205
|
if (value === undefined) {
|
|
159
206
|
return undefined;
|
|
@@ -209,6 +256,73 @@ function resolveActionOptions(options: { transport?: string }) {
|
|
|
209
256
|
};
|
|
210
257
|
}
|
|
211
258
|
|
|
259
|
+
function parseDigestPeriod(value: string | undefined): PeriodDigestPreset {
|
|
260
|
+
const normalized = value?.trim().toLowerCase();
|
|
261
|
+
if (normalized === "yesterday") return "yesterday";
|
|
262
|
+
if (normalized === "24h" || normalized === "day") return "24h";
|
|
263
|
+
if (normalized === "week" || normalized === "7d") return "week";
|
|
264
|
+
return "today";
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function buildDigestOptions(
|
|
268
|
+
period: string | undefined,
|
|
269
|
+
options: {
|
|
270
|
+
account?: string;
|
|
271
|
+
includeDms?: boolean;
|
|
272
|
+
model?: string;
|
|
273
|
+
refresh?: boolean;
|
|
274
|
+
since?: string;
|
|
275
|
+
until?: string;
|
|
276
|
+
maxTweets?: string;
|
|
277
|
+
maxLinks?: string;
|
|
278
|
+
},
|
|
279
|
+
): PeriodDigestOptions | null {
|
|
280
|
+
const maxTweets = parseNonNegativeIntegerOption(
|
|
281
|
+
options.maxTweets,
|
|
282
|
+
"--max-tweets",
|
|
283
|
+
);
|
|
284
|
+
if (options.maxTweets !== undefined && maxTweets === undefined) {
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
const maxLinks = parseNonNegativeIntegerOption(
|
|
288
|
+
options.maxLinks,
|
|
289
|
+
"--max-links",
|
|
290
|
+
);
|
|
291
|
+
if (options.maxLinks !== undefined && maxLinks === undefined) {
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
return {
|
|
295
|
+
period: parseDigestPeriod(period),
|
|
296
|
+
since: options.since,
|
|
297
|
+
until: options.until,
|
|
298
|
+
account: options.account,
|
|
299
|
+
includeDms: Boolean(options.includeDms),
|
|
300
|
+
refresh: Boolean(options.refresh),
|
|
301
|
+
model: options.model,
|
|
302
|
+
maxTweets,
|
|
303
|
+
maxLinks,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function runDigestCli(options: PeriodDigestOptions) {
|
|
308
|
+
const asJson = Boolean(program.opts().json);
|
|
309
|
+
return streamPeriodDigest(options, {
|
|
310
|
+
onDelta: asJson
|
|
311
|
+
? undefined
|
|
312
|
+
: (delta) => {
|
|
313
|
+
process.stdout.write(delta);
|
|
314
|
+
},
|
|
315
|
+
}).then((result) => {
|
|
316
|
+
if (asJson) {
|
|
317
|
+
print(result, true);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
if (!result.markdown.endsWith("\n")) {
|
|
321
|
+
process.stdout.write("\n");
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
212
326
|
async function enrichDmItems(
|
|
213
327
|
query: Parameters<typeof listDmConversations>[0],
|
|
214
328
|
options: {
|
|
@@ -260,14 +374,28 @@ async function enrichDmItems(
|
|
|
260
374
|
}
|
|
261
375
|
|
|
262
376
|
async function autoUpdateBeforeRead() {
|
|
263
|
-
|
|
377
|
+
let result: Awaited<ReturnType<typeof maybeAutoUpdateBackup>>;
|
|
378
|
+
try {
|
|
379
|
+
result = await maybeAutoUpdateBackup();
|
|
380
|
+
} catch (error) {
|
|
381
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
382
|
+
console.error(`birdclaw backup auto-sync failed: ${message}`);
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
264
385
|
if (!result.ok) {
|
|
265
386
|
console.error(`birdclaw backup auto-sync failed: ${result.error}`);
|
|
266
387
|
}
|
|
267
388
|
}
|
|
268
389
|
|
|
269
390
|
async function autoSyncAfterWrite() {
|
|
270
|
-
|
|
391
|
+
let result: Awaited<ReturnType<typeof maybeAutoSyncBackup>>;
|
|
392
|
+
try {
|
|
393
|
+
result = await maybeAutoSyncBackup();
|
|
394
|
+
} catch (error) {
|
|
395
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
396
|
+
console.error(`birdclaw backup sync failed: ${message}`);
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
271
399
|
if (!result.ok) {
|
|
272
400
|
console.error(`birdclaw backup sync failed: ${result.error}`);
|
|
273
401
|
}
|
|
@@ -416,12 +544,13 @@ searchCommand
|
|
|
416
544
|
|
|
417
545
|
searchCommand
|
|
418
546
|
.command("dms <query>")
|
|
547
|
+
.option("--inbox <kind>", "all, accepted, or requests", "all")
|
|
419
548
|
.option("--participant <value>")
|
|
420
549
|
.option("--min-followers <n>", "Minimum sender follower count")
|
|
421
550
|
.option("--max-followers <n>", "Maximum sender follower count")
|
|
422
551
|
.option("--min-influence-score <n>", "Minimum derived influence score")
|
|
423
552
|
.option("--max-influence-score <n>", "Maximum derived influence score")
|
|
424
|
-
.option("--sort <mode>", "recent or
|
|
553
|
+
.option("--sort <mode>", "recent or followers", "recent")
|
|
425
554
|
.option(
|
|
426
555
|
"--context <n>",
|
|
427
556
|
"Include N messages before and after each match",
|
|
@@ -447,6 +576,10 @@ searchCommand
|
|
|
447
576
|
if (context === undefined) {
|
|
448
577
|
return;
|
|
449
578
|
}
|
|
579
|
+
const inbox = parseDmInboxOption(options.inbox);
|
|
580
|
+
if (inbox === undefined) {
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
450
583
|
const replyFilter = options.replied
|
|
451
584
|
? "replied"
|
|
452
585
|
: options.unreplied
|
|
@@ -454,6 +587,7 @@ searchCommand
|
|
|
454
587
|
: "all";
|
|
455
588
|
const dmQuery = {
|
|
456
589
|
search: query,
|
|
590
|
+
...(inbox !== "all" ? { inbox } : {}),
|
|
457
591
|
participant: options.participant,
|
|
458
592
|
minFollowers: options.minFollowers
|
|
459
593
|
? Number(options.minFollowers)
|
|
@@ -467,7 +601,10 @@ searchCommand
|
|
|
467
601
|
maxInfluenceScore: options.maxInfluenceScore
|
|
468
602
|
? Number(options.maxInfluenceScore)
|
|
469
603
|
: undefined,
|
|
470
|
-
sort:
|
|
604
|
+
sort:
|
|
605
|
+
options.sort === "followers" || options.sort === "influence"
|
|
606
|
+
? "followers"
|
|
607
|
+
: "recent",
|
|
471
608
|
replyFilter,
|
|
472
609
|
context,
|
|
473
610
|
limit: Number(options.limit),
|
|
@@ -720,6 +857,40 @@ program
|
|
|
720
857
|
);
|
|
721
858
|
});
|
|
722
859
|
|
|
860
|
+
program
|
|
861
|
+
.command("today")
|
|
862
|
+
.description("Stream an AI digest of what happened today")
|
|
863
|
+
.option("--account <accountId>", "Account id")
|
|
864
|
+
.option("--include-dms", "Include private DM context")
|
|
865
|
+
.option("--model <model>", "OpenAI model id")
|
|
866
|
+
.option("--refresh", "Bypass the local digest cache")
|
|
867
|
+
.option("--max-tweets <n>", "Maximum tweet context", "300")
|
|
868
|
+
.option("--max-links <n>", "Maximum linked articles", "12")
|
|
869
|
+
.action(async (options) => {
|
|
870
|
+
await autoUpdateBeforeRead();
|
|
871
|
+
const digestOptions = buildDigestOptions("today", options);
|
|
872
|
+
if (!digestOptions) return;
|
|
873
|
+
await runDigestCli(digestOptions);
|
|
874
|
+
});
|
|
875
|
+
|
|
876
|
+
program
|
|
877
|
+
.command("digest [period]")
|
|
878
|
+
.description("Stream an AI digest for today, 24h, yesterday, or week")
|
|
879
|
+
.option("--account <accountId>", "Account id")
|
|
880
|
+
.option("--include-dms", "Include private DM context")
|
|
881
|
+
.option("--since <isoDate>", "Start of explicit window")
|
|
882
|
+
.option("--until <isoDate>", "End of explicit window")
|
|
883
|
+
.option("--model <model>", "OpenAI model id")
|
|
884
|
+
.option("--refresh", "Bypass the local digest cache")
|
|
885
|
+
.option("--max-tweets <n>", "Maximum tweet context", "300")
|
|
886
|
+
.option("--max-links <n>", "Maximum linked articles", "12")
|
|
887
|
+
.action(async (period, options) => {
|
|
888
|
+
await autoUpdateBeforeRead();
|
|
889
|
+
const digestOptions = buildDigestOptions(period, options);
|
|
890
|
+
if (!digestOptions) return;
|
|
891
|
+
await runDigestCli(digestOptions);
|
|
892
|
+
});
|
|
893
|
+
|
|
723
894
|
const mentionsCommand = program
|
|
724
895
|
.command("mentions")
|
|
725
896
|
.description("Export local mention tweets for scripts and agents");
|
|
@@ -1033,6 +1204,92 @@ const jobsCommand = program
|
|
|
1033
1204
|
.command("jobs")
|
|
1034
1205
|
.description("Run and install background Birdclaw jobs");
|
|
1035
1206
|
|
|
1207
|
+
jobsCommand
|
|
1208
|
+
.command("sync-account")
|
|
1209
|
+
.description("Refresh live account timelines and append a JSONL audit entry")
|
|
1210
|
+
.option("--account <accountId>", "Account id")
|
|
1211
|
+
.option(
|
|
1212
|
+
"--steps <steps>",
|
|
1213
|
+
"Comma list: timeline,mentions,mention-threads,likes,bookmarks,dms",
|
|
1214
|
+
)
|
|
1215
|
+
.option("--mode <mode>", "auto, xurl, or bird for likes/bookmarks", "auto")
|
|
1216
|
+
.option("--limit <n>", "Per-page/result limit", "100")
|
|
1217
|
+
.option("--max-pages <n>", "Stop after N pages", "3")
|
|
1218
|
+
.option("--cache-ttl <seconds>", "Live-cache freshness window", "120")
|
|
1219
|
+
.option("--refresh", "Bypass live-cache freshness window")
|
|
1220
|
+
.option(
|
|
1221
|
+
"--allow-bird-account",
|
|
1222
|
+
"Assert bird cookies match --account for Bird-backed steps",
|
|
1223
|
+
)
|
|
1224
|
+
.option("--log <path>", "Audit JSONL path")
|
|
1225
|
+
.action(async (options) => {
|
|
1226
|
+
const result = await runAccountSyncJob({
|
|
1227
|
+
account: options.account,
|
|
1228
|
+
steps: parseAccountSyncSteps(options.steps),
|
|
1229
|
+
mode: options.mode as TimelineCollectionMode,
|
|
1230
|
+
limit: Number(options.limit),
|
|
1231
|
+
maxPages: Number(options.maxPages),
|
|
1232
|
+
refresh: Boolean(options.refresh),
|
|
1233
|
+
cacheTtlMs: Number(options.cacheTtl) * 1000,
|
|
1234
|
+
allowBirdAccount: Boolean(options.allowBirdAccount),
|
|
1235
|
+
logPath: options.log,
|
|
1236
|
+
});
|
|
1237
|
+
print(result, true);
|
|
1238
|
+
if (!result.ok) {
|
|
1239
|
+
process.exitCode = 1;
|
|
1240
|
+
}
|
|
1241
|
+
});
|
|
1242
|
+
|
|
1243
|
+
jobsCommand
|
|
1244
|
+
.command("install-account-launchd")
|
|
1245
|
+
.description("Install a LaunchAgent that runs account sync")
|
|
1246
|
+
.option("--label <label>", "LaunchAgent label")
|
|
1247
|
+
.option("--interval-seconds <seconds>", "Launch interval", "1800")
|
|
1248
|
+
.option("--program <path>", "birdclaw executable or command", "birdclaw")
|
|
1249
|
+
.option("--account <accountId>", "Account id")
|
|
1250
|
+
.option(
|
|
1251
|
+
"--steps <steps>",
|
|
1252
|
+
"Comma list: timeline,mentions,mention-threads,likes,bookmarks,dms",
|
|
1253
|
+
)
|
|
1254
|
+
.option("--mode <mode>", "auto, xurl, or bird for likes/bookmarks", "auto")
|
|
1255
|
+
.option("--limit <n>", "Per-page/result limit", "100")
|
|
1256
|
+
.option("--max-pages <n>", "Stop after N pages", "3")
|
|
1257
|
+
.option("--cache-ttl <seconds>", "Live-cache freshness window", "120")
|
|
1258
|
+
.option("--no-refresh", "Allow live-cache reuse")
|
|
1259
|
+
.option(
|
|
1260
|
+
"--allow-bird-account",
|
|
1261
|
+
"Assert bird cookies match --account for Bird-backed steps",
|
|
1262
|
+
)
|
|
1263
|
+
.option("--log <path>", "Audit JSONL path")
|
|
1264
|
+
.option("--env-path <path>", "Shell env file to source before running")
|
|
1265
|
+
.option("--env-file <path>", "Deprecated alias for --env-path")
|
|
1266
|
+
.option("--stdout <path>", "launchd stdout path")
|
|
1267
|
+
.option("--stderr <path>", "launchd stderr path")
|
|
1268
|
+
.option("--launch-agents-dir <path>", "LaunchAgents directory")
|
|
1269
|
+
.option("--no-load", "Write plist without loading it")
|
|
1270
|
+
.action(async (options) => {
|
|
1271
|
+
const result = await installAccountSyncLaunchAgent({
|
|
1272
|
+
label: options.label,
|
|
1273
|
+
intervalSeconds: Number(options.intervalSeconds),
|
|
1274
|
+
program: options.program,
|
|
1275
|
+
account: options.account,
|
|
1276
|
+
steps: parseAccountSyncSteps(options.steps),
|
|
1277
|
+
mode: options.mode as TimelineCollectionMode,
|
|
1278
|
+
limit: Number(options.limit),
|
|
1279
|
+
maxPages: Number(options.maxPages),
|
|
1280
|
+
refresh: options.refresh,
|
|
1281
|
+
allowBirdAccount: Boolean(options.allowBirdAccount),
|
|
1282
|
+
cacheTtlSeconds: Number(options.cacheTtl),
|
|
1283
|
+
logPath: options.log,
|
|
1284
|
+
envFile: options.envPath ?? options.envFile,
|
|
1285
|
+
stdoutPath: options.stdout,
|
|
1286
|
+
stderrPath: options.stderr,
|
|
1287
|
+
launchAgentsDir: options.launchAgentsDir,
|
|
1288
|
+
load: options.load,
|
|
1289
|
+
});
|
|
1290
|
+
print(result, true);
|
|
1291
|
+
});
|
|
1292
|
+
|
|
1036
1293
|
jobsCommand
|
|
1037
1294
|
.command("sync-bookmarks")
|
|
1038
1295
|
.description("Refresh live bookmarks and append a JSONL audit entry")
|
|
@@ -1074,7 +1331,8 @@ jobsCommand
|
|
|
1074
1331
|
.option("--cache-ttl <seconds>", "Live-cache freshness window", "120")
|
|
1075
1332
|
.option("--no-refresh", "Allow live-cache reuse")
|
|
1076
1333
|
.option("--log <path>", "Audit JSONL path")
|
|
1077
|
-
.option("--env-
|
|
1334
|
+
.option("--env-path <path>", "Shell env file to source before running")
|
|
1335
|
+
.option("--env-file <path>", "Deprecated alias for --env-path")
|
|
1078
1336
|
.option("--stdout <path>", "launchd stdout path")
|
|
1079
1337
|
.option("--stderr <path>", "launchd stderr path")
|
|
1080
1338
|
.option("--launch-agents-dir <path>", "LaunchAgents directory")
|
|
@@ -1091,7 +1349,7 @@ jobsCommand
|
|
|
1091
1349
|
refresh: options.refresh,
|
|
1092
1350
|
cacheTtlSeconds: Number(options.cacheTtl),
|
|
1093
1351
|
logPath: options.log,
|
|
1094
|
-
envFile: options.envFile,
|
|
1352
|
+
envFile: options.envPath ?? options.envFile,
|
|
1095
1353
|
stdoutPath: options.stdout,
|
|
1096
1354
|
stderrPath: options.stderr,
|
|
1097
1355
|
launchAgentsDir: options.launchAgentsDir,
|
|
@@ -1103,14 +1361,19 @@ jobsCommand
|
|
|
1103
1361
|
dmsCommand
|
|
1104
1362
|
.command("list")
|
|
1105
1363
|
.option("--account <accountId>", "Account id")
|
|
1106
|
-
.option("--
|
|
1364
|
+
.option("--mode <mode>", "auto, bird, or xurl", "bird")
|
|
1365
|
+
.option("--refresh", "Refresh live DMs before listing")
|
|
1107
1366
|
.option("--cache-ttl <seconds>", "Live-cache freshness window", "120")
|
|
1367
|
+
.option("--inbox <kind>", "all, accepted, or requests", "all")
|
|
1368
|
+
.option("--max-pages <n>", "Additional accepted/request pages to sync", "0")
|
|
1369
|
+
.option("--all-pages", "Fetch all accepted/request pages while syncing")
|
|
1370
|
+
.option("--page-delay-ms <n>", "Delay between live DM page requests", "0")
|
|
1108
1371
|
.option("--participant <value>")
|
|
1109
1372
|
.option("--min-followers <n>", "Minimum sender follower count")
|
|
1110
1373
|
.option("--max-followers <n>", "Maximum sender follower count")
|
|
1111
1374
|
.option("--min-influence-score <n>", "Minimum derived influence score")
|
|
1112
1375
|
.option("--max-influence-score <n>", "Maximum derived influence score")
|
|
1113
|
-
.option("--sort <mode>", "recent or
|
|
1376
|
+
.option("--sort <mode>", "recent or followers", "recent")
|
|
1114
1377
|
.option(
|
|
1115
1378
|
"--resolve-profiles",
|
|
1116
1379
|
"Resolve placeholder DM profiles through cache/bird/xurl",
|
|
@@ -1131,10 +1394,33 @@ dmsCommand
|
|
|
1131
1394
|
: options.unreplied
|
|
1132
1395
|
? "unreplied"
|
|
1133
1396
|
: "all";
|
|
1397
|
+
const inbox = parseDmInboxOption(options.inbox);
|
|
1398
|
+
const mode = parseDmSyncModeOption(options.mode);
|
|
1399
|
+
const maxPages = parseNonNegativeIntegerOption(
|
|
1400
|
+
options.maxPages,
|
|
1401
|
+
"--max-pages",
|
|
1402
|
+
);
|
|
1403
|
+
const pageDelayMs = parseNonNegativeIntegerOption(
|
|
1404
|
+
options.pageDelayMs,
|
|
1405
|
+
"--page-delay-ms",
|
|
1406
|
+
);
|
|
1407
|
+
if (
|
|
1408
|
+
inbox === undefined ||
|
|
1409
|
+
mode === undefined ||
|
|
1410
|
+
maxPages === undefined ||
|
|
1411
|
+
pageDelayMs === undefined
|
|
1412
|
+
) {
|
|
1413
|
+
return;
|
|
1414
|
+
}
|
|
1134
1415
|
if (options.refresh) {
|
|
1135
1416
|
await syncDirectMessagesViaCachedBird({
|
|
1136
1417
|
account: options.account,
|
|
1418
|
+
mode,
|
|
1137
1419
|
limit: Number(options.limit),
|
|
1420
|
+
...(inbox !== "all" ? { inbox } : {}),
|
|
1421
|
+
...(maxPages > 0 ? { maxPages } : {}),
|
|
1422
|
+
...(options.allPages ? { allPages: true } : {}),
|
|
1423
|
+
...(pageDelayMs > 0 ? { pageDelayMs } : {}),
|
|
1138
1424
|
refresh: true,
|
|
1139
1425
|
cacheTtlMs: Number(options.cacheTtl) * 1000,
|
|
1140
1426
|
});
|
|
@@ -1145,6 +1431,7 @@ dmsCommand
|
|
|
1145
1431
|
const items = await enrichDmItems(
|
|
1146
1432
|
{
|
|
1147
1433
|
account: options.account,
|
|
1434
|
+
...(inbox !== "all" ? { inbox } : {}),
|
|
1148
1435
|
participant: options.participant,
|
|
1149
1436
|
minFollowers: options.minFollowers
|
|
1150
1437
|
? Number(options.minFollowers)
|
|
@@ -1158,7 +1445,10 @@ dmsCommand
|
|
|
1158
1445
|
maxInfluenceScore: options.maxInfluenceScore
|
|
1159
1446
|
? Number(options.maxInfluenceScore)
|
|
1160
1447
|
: undefined,
|
|
1161
|
-
sort:
|
|
1448
|
+
sort:
|
|
1449
|
+
options.sort === "followers" || options.sort === "influence"
|
|
1450
|
+
? "followers"
|
|
1451
|
+
: "recent",
|
|
1162
1452
|
replyFilter,
|
|
1163
1453
|
limit: Number(options.limit),
|
|
1164
1454
|
},
|
|
@@ -1175,15 +1465,43 @@ dmsCommand
|
|
|
1175
1465
|
|
|
1176
1466
|
dmsCommand
|
|
1177
1467
|
.command("sync")
|
|
1178
|
-
.description("Refresh live direct messages
|
|
1468
|
+
.description("Refresh live direct messages into the local store")
|
|
1179
1469
|
.option("--account <accountId>", "Account id")
|
|
1470
|
+
.option("--mode <mode>", "auto, bird, or xurl", "bird")
|
|
1180
1471
|
.option("--limit <n>", "Limit messages", "20")
|
|
1472
|
+
.option("--inbox <kind>", "all, accepted, or requests", "all")
|
|
1473
|
+
.option("--max-pages <n>", "Additional accepted/request pages to sync", "0")
|
|
1474
|
+
.option("--all-pages", "Fetch all accepted/request pages")
|
|
1475
|
+
.option("--page-delay-ms <n>", "Delay between live DM page requests", "0")
|
|
1181
1476
|
.option("--cache-ttl <seconds>", "Live-cache freshness window", "120")
|
|
1182
1477
|
.option("--refresh", "Bypass live-cache freshness window")
|
|
1183
1478
|
.action(async (options) => {
|
|
1479
|
+
const inbox = parseDmInboxOption(options.inbox);
|
|
1480
|
+
const mode = parseDmSyncModeOption(options.mode);
|
|
1481
|
+
const maxPages = parseNonNegativeIntegerOption(
|
|
1482
|
+
options.maxPages,
|
|
1483
|
+
"--max-pages",
|
|
1484
|
+
);
|
|
1485
|
+
const pageDelayMs = parseNonNegativeIntegerOption(
|
|
1486
|
+
options.pageDelayMs,
|
|
1487
|
+
"--page-delay-ms",
|
|
1488
|
+
);
|
|
1489
|
+
if (
|
|
1490
|
+
inbox === undefined ||
|
|
1491
|
+
mode === undefined ||
|
|
1492
|
+
maxPages === undefined ||
|
|
1493
|
+
pageDelayMs === undefined
|
|
1494
|
+
) {
|
|
1495
|
+
return;
|
|
1496
|
+
}
|
|
1184
1497
|
const result = await syncDirectMessagesViaCachedBird({
|
|
1185
1498
|
account: options.account,
|
|
1499
|
+
mode,
|
|
1186
1500
|
limit: Number(options.limit),
|
|
1501
|
+
...(inbox !== "all" ? { inbox } : {}),
|
|
1502
|
+
...(maxPages > 0 ? { maxPages } : {}),
|
|
1503
|
+
...(options.allPages ? { allPages: true } : {}),
|
|
1504
|
+
...(pageDelayMs > 0 ? { pageDelayMs } : {}),
|
|
1187
1505
|
refresh: Boolean(options.refresh),
|
|
1188
1506
|
cacheTtlMs: Number(options.cacheTtl) * 1000,
|
|
1189
1507
|
});
|
|
@@ -1191,6 +1509,39 @@ dmsCommand
|
|
|
1191
1509
|
print(result, true);
|
|
1192
1510
|
});
|
|
1193
1511
|
|
|
1512
|
+
for (const action of ["accept", "reject", "block"] as const) {
|
|
1513
|
+
const command = dmsCommand
|
|
1514
|
+
.command(`${action} <conversationId>`)
|
|
1515
|
+
.description(`${action} a live DM message request through bird`);
|
|
1516
|
+
if (action === "block") {
|
|
1517
|
+
command
|
|
1518
|
+
.option("--max-pages <n>", "Additional timeline pages to search", "3")
|
|
1519
|
+
.option("--all-pages", "Search all accepted/request timeline pages");
|
|
1520
|
+
}
|
|
1521
|
+
command.action(async (conversationId, options) => {
|
|
1522
|
+
const maxPages =
|
|
1523
|
+
action === "block"
|
|
1524
|
+
? parseNonNegativeIntegerOption(options.maxPages, "--max-pages")
|
|
1525
|
+
: undefined;
|
|
1526
|
+
if (action === "block" && maxPages === undefined) {
|
|
1527
|
+
return;
|
|
1528
|
+
}
|
|
1529
|
+
const result = await runDirectMessageRequestMutationViaBird({
|
|
1530
|
+
action,
|
|
1531
|
+
conversationId,
|
|
1532
|
+
...(action === "block" && maxPages !== undefined ? { maxPages } : {}),
|
|
1533
|
+
...(action === "block" && options.allPages ? { allPages: true } : {}),
|
|
1534
|
+
});
|
|
1535
|
+
if (result.success) {
|
|
1536
|
+
applyDmRequestMutationToLocalStore(conversationId, action);
|
|
1537
|
+
} else {
|
|
1538
|
+
process.exitCode = 1;
|
|
1539
|
+
}
|
|
1540
|
+
await autoSyncAfterWrite();
|
|
1541
|
+
print(result, true);
|
|
1542
|
+
});
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1194
1545
|
registerModerationCommands({
|
|
1195
1546
|
program,
|
|
1196
1547
|
print,
|
|
@@ -1470,9 +1821,17 @@ program
|
|
|
1470
1821
|
await autoUpdateBeforeRead();
|
|
1471
1822
|
const child = spawn(
|
|
1472
1823
|
process.execPath,
|
|
1473
|
-
[
|
|
1824
|
+
[
|
|
1825
|
+
"node_modules/vite/bin/vite.js",
|
|
1826
|
+
"dev",
|
|
1827
|
+
"--host",
|
|
1828
|
+
"127.0.0.1",
|
|
1829
|
+
"--port",
|
|
1830
|
+
"3000",
|
|
1831
|
+
],
|
|
1474
1832
|
{
|
|
1475
1833
|
cwd: packageRoot,
|
|
1834
|
+
env: { ...process.env, BIRDCLAW_LOCAL_WEB: "1" },
|
|
1476
1835
|
stdio: "inherit",
|
|
1477
1836
|
detached: process.platform !== "win32",
|
|
1478
1837
|
},
|
|
@@ -1527,7 +1886,11 @@ program
|
|
|
1527
1886
|
});
|
|
1528
1887
|
|
|
1529
1888
|
export async function runCli(argv = process.argv) {
|
|
1530
|
-
|
|
1889
|
+
try {
|
|
1890
|
+
await program.parseAsync(argv);
|
|
1891
|
+
} finally {
|
|
1892
|
+
await closeDatabase();
|
|
1893
|
+
}
|
|
1531
1894
|
}
|
|
1532
1895
|
|
|
1533
1896
|
/* v8 ignore next 5 */
|