birdclaw 0.5.1 → 0.7.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 (116) hide show
  1. package/CHANGELOG.md +92 -1
  2. package/README.md +75 -5
  3. package/package.json +8 -2
  4. package/scripts/browser-perf.mjs +1 -0
  5. package/scripts/start-test-server.mjs +16 -3
  6. package/src/cli.ts +812 -37
  7. package/src/components/AccountSwitcher.tsx +186 -0
  8. package/src/components/AppNav.tsx +37 -7
  9. package/src/components/AvatarChip.tsx +9 -3
  10. package/src/components/DmWorkspace.tsx +18 -8
  11. package/src/components/LinkPreviewCard.tsx +40 -18
  12. package/src/components/MarkdownViewer.tsx +818 -0
  13. package/src/components/ProfileAnalysisStream.tsx +428 -0
  14. package/src/components/ProfilePreview.tsx +120 -9
  15. package/src/components/SavedTimelineView.tsx +30 -8
  16. package/src/components/SyncNowButton.tsx +60 -25
  17. package/src/components/ThemeSlider.tsx +55 -50
  18. package/src/components/TimelineCard.tsx +240 -92
  19. package/src/components/TimelineRouteFrame.tsx +38 -8
  20. package/src/components/TweetMediaGrid.tsx +87 -38
  21. package/src/components/TweetRichText.tsx +45 -17
  22. package/src/components/account-selection.ts +64 -0
  23. package/src/components/useTimelineRouteData.ts +97 -13
  24. package/src/lib/account-sync-job.ts +666 -0
  25. package/src/lib/actions-transport.ts +216 -146
  26. package/src/lib/api-client.ts +128 -53
  27. package/src/lib/archive-finder.ts +78 -63
  28. package/src/lib/archive-import.ts +1593 -1291
  29. package/src/lib/authored-live.ts +262 -204
  30. package/src/lib/avatar-cache.ts +208 -43
  31. package/src/lib/backup.ts +1536 -954
  32. package/src/lib/bird-actions.ts +139 -57
  33. package/src/lib/bird-command.ts +101 -28
  34. package/src/lib/bird.ts +582 -194
  35. package/src/lib/blocklist.ts +40 -23
  36. package/src/lib/blocks-write.ts +129 -80
  37. package/src/lib/blocks.ts +165 -97
  38. package/src/lib/bookmark-sync-job.ts +250 -160
  39. package/src/lib/config.ts +35 -2
  40. package/src/lib/conversation-surface.ts +79 -48
  41. package/src/lib/data-sources.ts +219 -0
  42. package/src/lib/db.ts +95 -4
  43. package/src/lib/dms-live.ts +720 -66
  44. package/src/lib/effect-runtime.ts +45 -0
  45. package/src/lib/follow-graph.ts +224 -180
  46. package/src/lib/geocoding.ts +296 -0
  47. package/src/lib/http-effect.ts +222 -0
  48. package/src/lib/inbox.ts +74 -43
  49. package/src/lib/link-index.ts +88 -76
  50. package/src/lib/link-insights.ts +24 -0
  51. package/src/lib/link-preview-metadata.ts +472 -52
  52. package/src/lib/location.ts +137 -0
  53. package/src/lib/media-fetch.ts +286 -213
  54. package/src/lib/mention-threads-live.ts +445 -288
  55. package/src/lib/mentions-live.ts +549 -354
  56. package/src/lib/moderation-target.ts +102 -65
  57. package/src/lib/moderation-write.ts +77 -18
  58. package/src/lib/mutes-write.ts +129 -80
  59. package/src/lib/mutes.ts +8 -1
  60. package/src/lib/network-map.ts +382 -0
  61. package/src/lib/openai.ts +84 -53
  62. package/src/lib/period-digest.ts +1317 -0
  63. package/src/lib/profile-affiliation-hydration.ts +93 -54
  64. package/src/lib/profile-analysis.ts +1272 -0
  65. package/src/lib/profile-bio-entities.ts +1 -1
  66. package/src/lib/profile-hydration.ts +124 -72
  67. package/src/lib/profile-replies.ts +60 -43
  68. package/src/lib/profile-resolver.ts +402 -294
  69. package/src/lib/queries.ts +983 -203
  70. package/src/lib/research.ts +165 -120
  71. package/src/lib/search-discussion.ts +1016 -0
  72. package/src/lib/sqlite.ts +1 -0
  73. package/src/lib/timeline-collections-live.ts +204 -167
  74. package/src/lib/timeline-live.ts +325 -51
  75. package/src/lib/tweet-account-edges.ts +2 -0
  76. package/src/lib/tweet-lookup.ts +30 -19
  77. package/src/lib/tweet-render.ts +141 -1
  78. package/src/lib/tweet-search-live.ts +565 -0
  79. package/src/lib/types.ts +75 -3
  80. package/src/lib/ui.ts +31 -8
  81. package/src/lib/url-expansion.ts +226 -55
  82. package/src/lib/url-safety.ts +220 -0
  83. package/src/lib/web-sync.ts +222 -149
  84. package/src/lib/whois.ts +166 -147
  85. package/src/lib/x-web.ts +102 -71
  86. package/src/lib/xurl-rate-limits.ts +267 -0
  87. package/src/lib/xurl.ts +1185 -405
  88. package/src/routeTree.gen.ts +273 -0
  89. package/src/routes/__root.tsx +24 -5
  90. package/src/routes/api/action.tsx +127 -78
  91. package/src/routes/api/avatar.tsx +39 -30
  92. package/src/routes/api/blocks.tsx +26 -23
  93. package/src/routes/api/conversation.tsx +25 -14
  94. package/src/routes/api/data-sources.tsx +24 -0
  95. package/src/routes/api/inbox.tsx +27 -21
  96. package/src/routes/api/link-insights.tsx +31 -25
  97. package/src/routes/api/link-preview.tsx +25 -21
  98. package/src/routes/api/network-map.tsx +55 -0
  99. package/src/routes/api/period-digest.tsx +133 -0
  100. package/src/routes/api/profile-analysis.tsx +152 -0
  101. package/src/routes/api/profile-hydrate.tsx +31 -28
  102. package/src/routes/api/query.tsx +80 -55
  103. package/src/routes/api/search-discussion.tsx +169 -0
  104. package/src/routes/api/status.tsx +18 -10
  105. package/src/routes/api/sync.tsx +75 -29
  106. package/src/routes/api/xurl-rate-limits.tsx +24 -0
  107. package/src/routes/data-sources.tsx +255 -0
  108. package/src/routes/discuss.tsx +419 -0
  109. package/src/routes/dms.tsx +95 -28
  110. package/src/routes/inbox.tsx +32 -19
  111. package/src/routes/network-map.tsx +1035 -0
  112. package/src/routes/profile-analyze.tsx +112 -0
  113. package/src/routes/profiles.$handle.tsx +228 -0
  114. package/src/routes/rate-limits.tsx +309 -0
  115. package/src/routes/today.tsx +455 -0
  116. package/src/styles.css +22 -0
@@ -1,7 +1,9 @@
1
1
  import { createFileRoute } from "@tanstack/react-router";
2
2
  import { Sparkles } from "lucide-react";
3
3
  import { useEffect, useMemo, useState } from "react";
4
+ import { useSelectedAccountId } from "#/components/account-selection";
4
5
  import { InboxCard } from "#/components/InboxCard";
6
+ import { postAction } from "#/lib/api-client";
5
7
  import type {
6
8
  InboxItem,
7
9
  InboxKind,
@@ -48,7 +50,9 @@ function InboxRoute() {
48
50
  const [activeReplyId, setActiveReplyId] = useState<string | null>(null);
49
51
  const [replyDraft, setReplyDraft] = useState("");
50
52
  const [isSendingReply, setIsSendingReply] = useState(false);
53
+ const [replyError, setReplyError] = useState<string | null>(null);
51
54
  const [stats, setStats] = useState<InboxResponse["stats"] | null>(null);
55
+ const selectedAccountId = useSelectedAccountId(meta?.accounts);
52
56
 
53
57
  useEffect(() => {
54
58
  fetch("/api/status")
@@ -61,6 +65,9 @@ function InboxRoute() {
61
65
  url.searchParams.set("kind", kind);
62
66
  url.searchParams.set("minScore", minScore);
63
67
  url.searchParams.set("refresh", String(refreshTick));
68
+ if (selectedAccountId) {
69
+ url.searchParams.set("account", selectedAccountId);
70
+ }
64
71
  if (hideLowSignal) {
65
72
  url.searchParams.set("hideLowSignal", "1");
66
73
  }
@@ -71,7 +78,7 @@ function InboxRoute() {
71
78
  setItems(data.items);
72
79
  setStats(data.stats);
73
80
  });
74
- }, [hideLowSignal, kind, minScore, refreshTick]);
81
+ }, [hideLowSignal, kind, minScore, refreshTick, selectedAccountId]);
75
82
 
76
83
  const subtitle = useMemo(() => {
77
84
  if (!meta || !stats) return "Ranking unreplied mentions and DMs...";
@@ -87,6 +94,7 @@ function InboxRoute() {
87
94
  body: JSON.stringify({
88
95
  kind: "scoreInbox",
89
96
  scoreKind: kind,
97
+ account: selectedAccountId,
90
98
  limit: 8,
91
99
  }),
92
100
  });
@@ -99,28 +107,27 @@ function InboxRoute() {
99
107
  async function sendReply(item: InboxItem) {
100
108
  if (!replyDraft.trim()) return;
101
109
  setIsSendingReply(true);
110
+ setReplyError(null);
102
111
  try {
103
- await fetch("/api/action", {
104
- method: "POST",
105
- headers: { "content-type": "application/json" },
106
- body: JSON.stringify(
107
- item.entityKind === "dm"
108
- ? {
109
- kind: "replyDm",
110
- conversationId: item.entityId,
111
- text: replyDraft,
112
- }
113
- : {
114
- kind: "replyTweet",
115
- accountId: item.accountId,
116
- tweetId: item.entityId,
117
- text: replyDraft,
118
- },
119
- ),
120
- });
112
+ await postAction(
113
+ item.entityKind === "dm"
114
+ ? {
115
+ kind: "replyDm",
116
+ conversationId: item.entityId,
117
+ text: replyDraft,
118
+ }
119
+ : {
120
+ kind: "replyTweet",
121
+ accountId: item.accountId,
122
+ tweetId: item.entityId,
123
+ text: replyDraft,
124
+ },
125
+ );
121
126
  setReplyDraft("");
122
127
  setActiveReplyId(null);
123
128
  setRefreshTick((value) => value + 1);
129
+ } catch (error) {
130
+ setReplyError(error instanceof Error ? error.message : "Reply failed");
124
131
  } finally {
125
132
  setIsSendingReply(false);
126
133
  }
@@ -181,6 +188,11 @@ function InboxRoute() {
181
188
  })}
182
189
  </div>
183
190
  </header>
191
+ {replyError ? (
192
+ <p className={cx(timestampClass, "px-4 py-2 text-red-500")}>
193
+ {replyError}
194
+ </p>
195
+ ) : null}
184
196
  <section className={feedClass}>
185
197
  {items.length === 0 ? (
186
198
  <div className={emptyStateClass}>Inbox clear.</div>
@@ -193,6 +205,7 @@ function InboxRoute() {
193
205
  onReplyChange={setReplyDraft}
194
206
  onReplySend={() => void sendReply(item)}
195
207
  onReplyToggle={() => {
208
+ setReplyError(null);
196
209
  if (activeReplyId === item.id) {
197
210
  setActiveReplyId(null);
198
211
  setReplyDraft("");