birdclaw 0.8.1 → 0.8.3

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 (103) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/package.json +2 -4
  3. package/scripts/browser-perf.mjs +27 -0
  4. package/src/cli/command-context.ts +17 -0
  5. package/src/cli/register-analysis.ts +500 -0
  6. package/src/cli/register-compose.ts +40 -0
  7. package/src/cli/register-graph.ts +132 -0
  8. package/src/cli/register-inbox.ts +41 -0
  9. package/src/cli/register-storage.ts +106 -0
  10. package/src/cli.ts +30 -750
  11. package/src/components/AccountSwitcher.tsx +7 -15
  12. package/src/components/AvatarChip.tsx +1 -1
  13. package/src/components/AvatarPreload.ts +149 -0
  14. package/src/components/ConversationThread.tsx +4 -0
  15. package/src/components/EmbeddedTweetCard.tsx +4 -0
  16. package/src/components/FloatingPreview.tsx +382 -0
  17. package/src/components/MarkdownCitations.tsx +680 -0
  18. package/src/components/MarkdownViewer.tsx +7 -715
  19. package/src/components/ProfileAnalysisClient.ts +191 -0
  20. package/src/components/ProfileAnalysisStream.tsx +16 -185
  21. package/src/components/ProfilePreview.tsx +41 -81
  22. package/src/components/TimelineCard.tsx +18 -2
  23. package/src/components/TweetArticleCard.tsx +66 -0
  24. package/src/components/TweetRichText.tsx +33 -2
  25. package/src/components/links-controller.ts +162 -0
  26. package/src/components/links-model.ts +198 -0
  27. package/src/components/network-map-controller.ts +84 -0
  28. package/src/components/network-map-model.ts +255 -0
  29. package/src/components/useDebouncedValue.ts +12 -0
  30. package/src/components/useTimelineRouteData.ts +142 -170
  31. package/src/lib/analysis-runtime.ts +238 -0
  32. package/src/lib/api-client.ts +16 -100
  33. package/src/lib/api-contracts.ts +328 -0
  34. package/src/lib/archive-finder.ts +38 -0
  35. package/src/lib/archive-import-plan.ts +102 -0
  36. package/src/lib/archive-import.ts +170 -239
  37. package/src/lib/authored-live.ts +77 -182
  38. package/src/lib/backup.ts +335 -424
  39. package/src/lib/bird.ts +32 -1
  40. package/src/lib/blocks-write.ts +30 -26
  41. package/src/lib/blocks.ts +18 -20
  42. package/src/lib/database-metrics.ts +88 -0
  43. package/src/lib/database-migrations.ts +34 -0
  44. package/src/lib/database-schema.ts +312 -0
  45. package/src/lib/database-writer.ts +69 -0
  46. package/src/lib/db.ts +141 -334
  47. package/src/lib/dm-read-model.ts +533 -0
  48. package/src/lib/dms-live.ts +34 -97
  49. package/src/lib/follow-graph.ts +17 -27
  50. package/src/lib/import-repository.ts +138 -0
  51. package/src/lib/inbox.ts +2 -1
  52. package/src/lib/live-sync-engine.ts +209 -0
  53. package/src/lib/live-transport-gateway.ts +128 -0
  54. package/src/lib/mention-threads-live.ts +90 -176
  55. package/src/lib/mentions-export.ts +1 -1
  56. package/src/lib/mentions-live.ts +57 -225
  57. package/src/lib/moderation-target.ts +15 -4
  58. package/src/lib/moderation-write.ts +1 -1
  59. package/src/lib/mutes-write.ts +30 -26
  60. package/src/lib/openai-response-runtime.ts +251 -0
  61. package/src/lib/paginated-sync.ts +93 -0
  62. package/src/lib/period-digest.ts +116 -304
  63. package/src/lib/profile-analysis.ts +37 -111
  64. package/src/lib/queries.ts +6 -2380
  65. package/src/lib/query-actions.ts +437 -0
  66. package/src/lib/query-client.tsx +47 -0
  67. package/src/lib/query-read-model-shared.ts +52 -0
  68. package/src/lib/query-read-models.ts +5 -0
  69. package/src/lib/query-resource.ts +41 -0
  70. package/src/lib/query-status.ts +164 -0
  71. package/src/lib/research.ts +1 -1
  72. package/src/lib/runtime-services.ts +20 -0
  73. package/src/lib/search-discussion.ts +75 -279
  74. package/src/lib/server-runtime-services.ts +30 -0
  75. package/src/lib/sqlite.ts +53 -14
  76. package/src/lib/streaming-ingestion.ts +240 -0
  77. package/src/lib/sync-cache.ts +6 -1
  78. package/src/lib/sync-plan.ts +175 -0
  79. package/src/lib/timeline-collections-live.ts +83 -256
  80. package/src/lib/timeline-live.ts +86 -235
  81. package/src/lib/timeline-read-model.ts +1191 -0
  82. package/src/lib/tweet-render.ts +78 -0
  83. package/src/lib/tweet-repository.ts +156 -0
  84. package/src/lib/tweet-search-live.ts +63 -166
  85. package/src/lib/types.ts +8 -0
  86. package/src/lib/ui.ts +1 -1
  87. package/src/lib/web-sync.ts +67 -50
  88. package/src/lib/whois.ts +2 -1
  89. package/src/router.tsx +1 -1
  90. package/src/routes/__root.tsx +11 -21
  91. package/src/routes/api/action.tsx +1 -1
  92. package/src/routes/api/conversation.tsx +1 -1
  93. package/src/routes/api/query.tsx +32 -26
  94. package/src/routes/api/status.tsx +6 -2
  95. package/src/routes/api/sync.tsx +5 -2
  96. package/src/routes/blocks.tsx +97 -131
  97. package/src/routes/data-sources.tsx +17 -25
  98. package/src/routes/dms.tsx +168 -167
  99. package/src/routes/inbox.tsx +63 -57
  100. package/src/routes/links.tsx +31 -329
  101. package/src/routes/network-map.tsx +41 -344
  102. package/src/routes/rate-limits.tsx +17 -21
  103. package/vite.config.ts +0 -2
@@ -8,11 +8,16 @@ import { runEffectPromise, tryPromise } from "./effect-runtime";
8
8
  import type { ArchiveCandidate } from "./types";
9
9
 
10
10
  const execAsync = promisify(exec);
11
+ const ARCHIVE_DISCOVERY_CACHE_MS = 5 * 60_000;
11
12
  const ARCHIVE_NAME_PATTERNS = [
12
13
  /^twitter-.*\.zip$/i,
13
14
  /^x-.*\.zip$/i,
14
15
  /archive.*\.zip$/i,
15
16
  ];
17
+ let archiveDiscoveryCache:
18
+ | { value: ArchiveCandidate[]; updatedAt: number }
19
+ | undefined;
20
+ let archiveDiscoveryInFlight: Promise<ArchiveCandidate[]> | undefined;
16
21
 
17
22
  function formatFileSize(bytes: number): string {
18
23
  const units = ["B", "KB", "MB", "GB"];
@@ -142,6 +147,39 @@ export function findArchivesEffect(): Effect.Effect<
142
147
  });
143
148
  }
144
149
 
150
+ export function findArchivesCachedEffect(): Effect.Effect<
151
+ ArchiveCandidate[],
152
+ unknown
153
+ > {
154
+ return Effect.suspend(() => {
155
+ const cached = archiveDiscoveryCache;
156
+ if (cached && Date.now() - cached.updatedAt < ARCHIVE_DISCOVERY_CACHE_MS) {
157
+ return Effect.succeed(cached.value);
158
+ }
159
+ if (archiveDiscoveryInFlight) {
160
+ return Effect.promise(() => archiveDiscoveryInFlight!);
161
+ }
162
+
163
+ const request = runEffectPromise(findArchivesEffect())
164
+ .then((value) => {
165
+ archiveDiscoveryCache = { value, updatedAt: Date.now() };
166
+ return value;
167
+ })
168
+ .finally(() => {
169
+ if (archiveDiscoveryInFlight === request) {
170
+ archiveDiscoveryInFlight = undefined;
171
+ }
172
+ });
173
+ archiveDiscoveryInFlight = request;
174
+ return Effect.promise(() => request);
175
+ });
176
+ }
177
+
145
178
  export function findArchives(): Promise<ArchiveCandidate[]> {
146
179
  return runEffectPromise(findArchivesEffect());
147
180
  }
181
+
182
+ export function clearArchiveFinderCacheForTests() {
183
+ archiveDiscoveryCache = undefined;
184
+ archiveDiscoveryInFlight = undefined;
185
+ }
@@ -0,0 +1,102 @@
1
+ export type ArchiveTweetRow = {
2
+ id: string;
3
+ kind: "home" | "like" | "bookmark";
4
+ authorProfileId: string;
5
+ text: string;
6
+ createdAt: string;
7
+ isReplied: number;
8
+ replyToId: string | null;
9
+ likeCount: number;
10
+ mediaCount: number;
11
+ bookmarked: number;
12
+ liked: number;
13
+ entitiesJson: string;
14
+ mediaJson: string;
15
+ quotedTweetId: string | null;
16
+ };
17
+
18
+ export type ArchiveCollectionRow = {
19
+ tweetId: string;
20
+ kind: "likes" | "bookmarks";
21
+ collectedAt: string | null;
22
+ source: string;
23
+ rawJson: string;
24
+ };
25
+
26
+ export type ArchiveProfileRow = {
27
+ id: string;
28
+ handle: string;
29
+ displayName: string;
30
+ bio: string;
31
+ followersCount: number;
32
+ followingCount: number;
33
+ publicMetricsJson: string;
34
+ avatarHue: number;
35
+ avatarUrl: string | null;
36
+ location: string | null;
37
+ url: string | null;
38
+ verifiedType: string | null;
39
+ entitiesJson: string;
40
+ rawJson: string;
41
+ createdAt: string;
42
+ };
43
+
44
+ export type ArchiveConversationRow = {
45
+ id: string;
46
+ title: string;
47
+ accountId: string;
48
+ participantProfileId: string;
49
+ lastMessageAt: string;
50
+ unreadCount: number;
51
+ needsReply: number;
52
+ };
53
+
54
+ export type ArchiveMessageRow = {
55
+ id: string;
56
+ conversationId: string;
57
+ senderProfileId: string;
58
+ text: string;
59
+ createdAt: string;
60
+ direction: "inbound" | "outbound";
61
+ mediaCount: number;
62
+ };
63
+
64
+ export type ArchiveFollowRow = {
65
+ profileId: string;
66
+ externalUserId: string;
67
+ };
68
+
69
+ export class ArchiveImportPlan {
70
+ readonly mentionDirectory = new Map<
71
+ string,
72
+ { handle?: string; displayName?: string }
73
+ >();
74
+ readonly tweets: ArchiveTweetRow[] = [];
75
+ readonly collections: ArchiveCollectionRow[] = [];
76
+ readonly profiles = new Map<string, ArchiveProfileRow>();
77
+ readonly conversations = new Map<string, ArchiveConversationRow>();
78
+ readonly dmMessages: ArchiveMessageRow[] = [];
79
+ readonly followers: ArchiveFollowRow[] = [];
80
+ readonly following: ArchiveFollowRow[] = [];
81
+ readonly followerIds = new Set<string>();
82
+ readonly followingIds = new Set<string>();
83
+
84
+ private readonly tweetsById = new Map<string, ArchiveTweetRow>();
85
+
86
+ addTweet(row: ArchiveTweetRow) {
87
+ const existing = this.tweetsById.get(row.id);
88
+ if (existing) {
89
+ existing.bookmarked = Math.max(existing.bookmarked, row.bookmarked);
90
+ existing.liked = Math.max(existing.liked, row.liked);
91
+ if (!existing.text && row.text) existing.text = row.text;
92
+ return existing;
93
+ }
94
+ this.tweets.push(row);
95
+ this.tweetsById.set(row.id, row);
96
+ return row;
97
+ }
98
+
99
+ getTweet(id: string) {
100
+ return this.tweetsById.get(id);
101
+ }
102
+ }