birdclaw 0.4.0 → 0.5.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 (64) hide show
  1. package/CHANGELOG.md +39 -1
  2. package/README.md +108 -7
  3. package/package.json +24 -23
  4. package/playwright.config.ts +1 -0
  5. package/public/favicon.ico +0 -0
  6. package/public/logo192.png +0 -0
  7. package/public/logo512.png +0 -0
  8. package/public/manifest.json +2 -2
  9. package/src/cli.ts +563 -18
  10. package/src/components/AppNav.tsx +66 -29
  11. package/src/components/AvatarChip.tsx +10 -5
  12. package/src/components/ConversationThread.tsx +125 -0
  13. package/src/components/DmWorkspace.tsx +96 -98
  14. package/src/components/EmbeddedTweetCard.tsx +20 -14
  15. package/src/components/InboxCard.tsx +92 -90
  16. package/src/components/LinkPreviewCard.tsx +270 -0
  17. package/src/components/ProfilePreview.tsx +8 -3
  18. package/src/components/SavedTimelineView.tsx +48 -38
  19. package/src/components/TimelineCard.tsx +228 -84
  20. package/src/components/TweetRichText.tsx +6 -2
  21. package/src/lib/archive-import.ts +1567 -65
  22. package/src/lib/authored-live.ts +1074 -0
  23. package/src/lib/backup.ts +318 -14
  24. package/src/lib/bird-actions.ts +1 -10
  25. package/src/lib/bird-command.ts +57 -0
  26. package/src/lib/bird.ts +89 -5
  27. package/src/lib/config.ts +1 -1
  28. package/src/lib/db.ts +282 -4
  29. package/src/lib/follow-graph.ts +1053 -0
  30. package/src/lib/link-index.ts +629 -0
  31. package/src/lib/link-insights.ts +834 -0
  32. package/src/lib/link-preview-metadata.ts +334 -0
  33. package/src/lib/media-fetch.ts +787 -0
  34. package/src/lib/media-includes.ts +165 -0
  35. package/src/lib/mention-threads-live.ts +535 -43
  36. package/src/lib/mentions-live.ts +623 -19
  37. package/src/lib/moderation-target.ts +6 -0
  38. package/src/lib/profile-hydration.ts +1 -1
  39. package/src/lib/profile-resolver.ts +115 -1
  40. package/src/lib/queries.ts +233 -7
  41. package/src/lib/seed.ts +127 -8
  42. package/src/lib/timeline-collections-live.ts +145 -22
  43. package/src/lib/timeline-live.ts +10 -15
  44. package/src/lib/tweet-account-edges.ts +9 -2
  45. package/src/lib/tweet-render.ts +97 -0
  46. package/src/lib/types.ts +219 -2
  47. package/src/lib/ui.ts +375 -147
  48. package/src/lib/url-expansion-store.ts +110 -0
  49. package/src/lib/url-expansion.ts +33 -8
  50. package/src/lib/x-profile.ts +7 -2
  51. package/src/lib/xurl.ts +296 -12
  52. package/src/routeTree.gen.ts +105 -0
  53. package/src/routes/__root.tsx +7 -3
  54. package/src/routes/api/conversation.tsx +34 -0
  55. package/src/routes/api/link-insights.tsx +79 -0
  56. package/src/routes/api/link-preview.tsx +43 -0
  57. package/src/routes/api/profile-hydrate.tsx +51 -0
  58. package/src/routes/blocks.tsx +111 -86
  59. package/src/routes/dms.tsx +90 -78
  60. package/src/routes/inbox.tsx +98 -86
  61. package/src/routes/index.tsx +63 -50
  62. package/src/routes/links.tsx +883 -0
  63. package/src/routes/mentions.tsx +63 -50
  64. package/src/styles.css +106 -43
package/src/lib/types.ts CHANGED
@@ -1,4 +1,4 @@
1
- export type ResourceKind = "home" | "mentions" | "dms";
1
+ export type ResourceKind = "home" | "mentions" | "authored" | "dms";
2
2
  export type InboxKind = "mixed" | "mentions" | "dms";
3
3
 
4
4
  export type ReplyFilter = "all" | "replied" | "unreplied";
@@ -88,6 +88,8 @@ export interface TweetUrlEntity {
88
88
  end: number;
89
89
  title?: string;
90
90
  description?: string | null;
91
+ imageUrl?: string | null;
92
+ siteName?: string | null;
91
93
  }
92
94
 
93
95
  export interface TweetHashtagEntity {
@@ -109,17 +111,29 @@ export interface TweetMediaItem {
109
111
  width?: number;
110
112
  height?: number;
111
113
  thumbnailUrl?: string;
114
+ durationMs?: number;
115
+ variants?: Array<{
116
+ url: string;
117
+ contentType?: string;
118
+ bitRate?: number;
119
+ }>;
112
120
  }
113
121
 
114
122
  export interface EmbeddedTweet {
115
123
  id: string;
116
124
  text: string;
117
125
  createdAt: string;
126
+ replyToId?: string | null;
118
127
  author: ProfileRecord;
119
128
  entities: TweetEntities;
120
129
  media: TweetMediaItem[];
121
130
  }
122
131
 
132
+ export interface TweetConversationResponse {
133
+ anchorId: string;
134
+ items: EmbeddedTweet[];
135
+ }
136
+
123
137
  export interface BlockItem {
124
138
  accountId: string;
125
139
  accountHandle: string;
@@ -143,10 +157,11 @@ export interface TimelineItem {
143
157
  id: string;
144
158
  accountId: string;
145
159
  accountHandle: string;
146
- kind: "home" | "mention" | "like" | "bookmark";
160
+ kind: "home" | "mention" | "authored" | "like" | "bookmark";
147
161
  text: string;
148
162
  searchSnippet?: string;
149
163
  createdAt: string;
164
+ replyToId?: string | null;
150
165
  isReplied: boolean;
151
166
  likeCount: number;
152
167
  mediaCount: number;
@@ -183,6 +198,122 @@ export interface UrlExpansionItem {
183
198
  updatedAt: string;
184
199
  }
185
200
 
201
+ export interface LinkOccurrenceItem {
202
+ sourceKind: "dm" | "tweet";
203
+ sourceId: string;
204
+ sourcePosition: number;
205
+ shortUrl: string;
206
+ accountId?: string | null;
207
+ conversationId?: string | null;
208
+ direction?: string | null;
209
+ createdAt: string;
210
+ }
211
+
212
+ export interface LinkIndexItem {
213
+ shortUrl: string;
214
+ expandedUrl: string;
215
+ finalUrl: string;
216
+ status: "hit" | "miss" | "error";
217
+ expandedTweetId?: string | null;
218
+ expandedHandle?: string | null;
219
+ title?: string | null;
220
+ description?: string | null;
221
+ imageUrl?: string | null;
222
+ siteName?: string | null;
223
+ error?: string | null;
224
+ source: string;
225
+ updatedAt: string;
226
+ }
227
+
228
+ export interface LinkSearchItem {
229
+ occurrence: LinkOccurrenceItem;
230
+ expansion: LinkIndexItem;
231
+ sourceText: string;
232
+ sourceAuthor?: ProfileRecord | null;
233
+ participant?: ProfileRecord | null;
234
+ linkedTweet?: TimelineItem | null;
235
+ }
236
+
237
+ export type LinkInsightKind = "links" | "videos";
238
+ export type LinkInsightRange = "today" | "week" | "month" | "year" | "all";
239
+ export type LinkInsightSort = "rank" | "recent" | "comments";
240
+ export type LinkInsightSource = "all" | "tweet" | "dm";
241
+
242
+ export interface LinkInsightMention {
243
+ id: string;
244
+ sourceKind: "dm" | "tweet";
245
+ sourceId: string;
246
+ sourceUrl?: string | null;
247
+ sourceLabel: string;
248
+ shortUrl: string;
249
+ conversationId?: string | null;
250
+ createdAt: string;
251
+ text: string;
252
+ rawText: string;
253
+ commentText: string;
254
+ sharedContentText?: string | null;
255
+ hasComment: boolean;
256
+ isPureShare: boolean;
257
+ timelineTweetId?: string | null;
258
+ contentTweetId?: string | null;
259
+ contentTweetUrl?: string | null;
260
+ contentAuthor?: ProfileRecord | null;
261
+ media: TweetMediaItem[];
262
+ direction?: string | null;
263
+ accountHandle?: string | null;
264
+ sharedBy?: ProfileRecord | null;
265
+ participant?: ProfileRecord | null;
266
+ }
267
+
268
+ export interface LinkInsightItem {
269
+ id: string;
270
+ kind: LinkInsightKind;
271
+ url: string;
272
+ canonicalKey: string;
273
+ displayUrl: string;
274
+ host: string;
275
+ title?: string | null;
276
+ description?: string | null;
277
+ shareCount: number;
278
+ uniqueSharers: number;
279
+ totalInfluence: number;
280
+ mentionCount: number;
281
+ commentCount: number;
282
+ pureShareCount: number;
283
+ hiddenMentionCount: number;
284
+ firstSeenAt: string;
285
+ lastSeenAt: string;
286
+ topSharer?: ProfileRecord | null;
287
+ sharers: ProfileRecord[];
288
+ mentions: LinkInsightMention[];
289
+ }
290
+
291
+ export interface LinkInsightQuery {
292
+ kind?: LinkInsightKind;
293
+ range?: LinkInsightRange;
294
+ sort?: LinkInsightSort;
295
+ source?: LinkInsightSource;
296
+ since?: string;
297
+ until?: string;
298
+ limit?: number;
299
+ commentsLimit?: number;
300
+ now?: Date;
301
+ }
302
+
303
+ export interface LinkInsightResponse {
304
+ kind: LinkInsightKind;
305
+ range: LinkInsightRange;
306
+ sort: LinkInsightSort;
307
+ source: LinkInsightSource;
308
+ since: string | null;
309
+ until: string | null;
310
+ items: LinkInsightItem[];
311
+ stats: {
312
+ occurrences: number;
313
+ groups: number;
314
+ };
315
+ }
316
+
186
317
  export interface DmSearchMatchItem {
187
318
  message: DmMessageItem;
188
319
  before: DmMessageItem[];
@@ -327,6 +458,8 @@ export interface XurlPublicMetrics {
327
458
  impression_count?: number;
328
459
  followers_count?: number;
329
460
  following_count?: number;
461
+ tweet_count?: number;
462
+ listed_count?: number;
330
463
  }
331
464
 
332
465
  export interface XurlMentionUser {
@@ -343,6 +476,7 @@ export interface XurlMentionUser {
343
476
  affiliation?: Record<string, unknown>;
344
477
  public_metrics?: XurlPublicMetrics;
345
478
  created_at?: string;
479
+ protected?: boolean;
346
480
  }
347
481
 
348
482
  export interface XurlMentionData {
@@ -351,6 +485,8 @@ export interface XurlMentionData {
351
485
  text: string;
352
486
  created_at: string;
353
487
  conversation_id?: string;
488
+ in_reply_to_user_id?: string;
489
+ attachments?: XurlTweetAttachments;
354
490
  entities?: Record<string, unknown>;
355
491
  referenced_tweets?: XurlReferencedTweet[];
356
492
  public_metrics?: XurlPublicMetrics;
@@ -364,9 +500,12 @@ export interface XurlReferencedTweet {
364
500
 
365
501
  export interface XurlUserTweet {
366
502
  id: string;
503
+ author_id?: string;
367
504
  text: string;
368
505
  created_at: string;
369
506
  conversation_id?: string;
507
+ attachments?: XurlTweetAttachments;
508
+ entities?: Record<string, unknown>;
370
509
  referenced_tweets?: XurlReferencedTweet[];
371
510
  public_metrics?: XurlPublicMetrics;
372
511
  edit_history_tweet_ids?: string[];
@@ -378,12 +517,50 @@ export interface XurlTweetData {
378
517
  text: string;
379
518
  created_at: string;
380
519
  conversation_id?: string;
520
+ in_reply_to_user_id?: string;
521
+ attachments?: XurlTweetAttachments;
381
522
  entities?: Record<string, unknown>;
382
523
  referenced_tweets?: XurlReferencedTweet[];
383
524
  public_metrics?: XurlPublicMetrics;
384
525
  edit_history_tweet_ids?: string[];
385
526
  }
386
527
 
528
+ export interface XurlTweetAttachments {
529
+ media_keys?: string[];
530
+ poll_ids?: string[];
531
+ }
532
+
533
+ export interface XurlMediaItem {
534
+ media_key: string;
535
+ type: "photo" | "video" | "animated_gif" | string;
536
+ url?: string;
537
+ preview_image_url?: string;
538
+ duration_ms?: number;
539
+ width?: number;
540
+ height?: number;
541
+ alt_text?: string;
542
+ public_metrics?: XurlPublicMetrics;
543
+ variants?: Array<{
544
+ url: string;
545
+ content_type: string;
546
+ bit_rate?: number;
547
+ }>;
548
+ }
549
+
550
+ export type XurlMedia = XurlMediaItem;
551
+
552
+ export interface XurlTweetIncludes {
553
+ users?: XurlMentionUser[];
554
+ tweets?: XurlTweetData[];
555
+ media?: XurlMedia[];
556
+ }
557
+
558
+ export interface XurlUserTweetsResponse {
559
+ items: XurlUserTweet[];
560
+ nextToken: string | null;
561
+ includes?: XurlTweetIncludes;
562
+ }
563
+
387
564
  export interface ProfileReplyItem {
388
565
  id: string;
389
566
  text: string;
@@ -413,6 +590,7 @@ export interface XurlMentionsResponse {
413
590
  data: XurlMentionData[];
414
591
  includes?: {
415
592
  users?: XurlMentionUser[];
593
+ media?: XurlMediaItem[];
416
594
  };
417
595
  meta?: Record<string, unknown>;
418
596
  }
@@ -421,6 +599,45 @@ export interface XurlTweetsResponse {
421
599
  data: XurlTweetData[];
422
600
  includes?: {
423
601
  users?: XurlMentionUser[];
602
+ media?: XurlMediaItem[];
424
603
  };
425
604
  meta?: Record<string, unknown>;
426
605
  }
606
+
607
+ export type FollowDirection = "followers" | "following";
608
+
609
+ export interface XurlFollowUsersResponse {
610
+ data: XurlMentionUser[];
611
+ meta?: Record<string, unknown>;
612
+ }
613
+
614
+ export interface FollowGraphProfile {
615
+ id: string;
616
+ externalUserId: string;
617
+ handle: string;
618
+ displayName: string;
619
+ bio: string;
620
+ followersCount: number;
621
+ publicMetrics: XurlPublicMetrics;
622
+ avatarUrl?: string;
623
+ }
624
+
625
+ export type FollowEventKind = "started" | "ended";
626
+
627
+ export interface FollowGraphEvent {
628
+ eventAt: string;
629
+ direction: FollowDirection;
630
+ kind: FollowEventKind;
631
+ snapshotId: string;
632
+ profile: FollowGraphProfile;
633
+ }
634
+
635
+ export interface FollowGraphSummary {
636
+ accountId: string;
637
+ followers: number;
638
+ following: number;
639
+ mutuals: number;
640
+ nonMutualFollowing: number;
641
+ lastCompleteSnapshots: Partial<Record<FollowDirection, string>>;
642
+ lastIncompleteSnapshots: Partial<Record<FollowDirection, string>>;
643
+ }