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,5 +1,7 @@
1
1
  import type { Database } from "./sqlite";
2
- import { lookupProfileViaBird } from "./bird";
2
+ import { Effect } from "effect";
3
+ import { lookupProfileViaBirdEffect } from "./bird";
4
+ import { runEffectPromise } from "./effect-runtime";
3
5
  import { syncIdentitySearchIndexForProfileIds } from "./identity-search-index";
4
6
  import { syncProfileBioEntitiesForProfileId } from "./profile-bio-entities";
5
7
  import { recordProfileSnapshot } from "./profile-history";
@@ -24,6 +26,17 @@ interface SyntheticAffiliationRow {
24
26
  raw_json: string;
25
27
  }
26
28
 
29
+ function toError(error: unknown) {
30
+ return error instanceof Error ? error : new Error(String(error));
31
+ }
32
+
33
+ function trySync<T>(try_: () => T) {
34
+ return Effect.try({
35
+ try: try_,
36
+ catch: toError,
37
+ });
38
+ }
39
+
27
40
  function normalizeHandle(value: string | null) {
28
41
  const handle = value?.trim().replace(/^@/, "");
29
42
  return handle && handle.length > 0 ? handle : null;
@@ -112,13 +125,16 @@ function findLocalOrganizationProfileId(db: Database, handle: string) {
112
125
  return row?.id ?? null;
113
126
  }
114
127
 
115
- export async function hydrateProfileAffiliationOrganizations(
128
+ export function hydrateProfileAffiliationOrganizationsEffect(
116
129
  db: Database,
117
130
  subjectProfileId: string,
118
- ): Promise<ProfileAffiliationHydrationResult> {
119
- const rows = db
120
- .prepare(
121
- `
131
+ ): Effect.Effect<ProfileAffiliationHydrationResult, unknown> {
132
+ return Effect.gen(function* () {
133
+ const rows = yield* trySync(
134
+ () =>
135
+ db
136
+ .prepare(
137
+ `
122
138
  select subject_profile_id, organization_profile_id, organization_name,
123
139
  organization_handle, badge_url, url, label, source, raw_json
124
140
  from profile_affiliations
@@ -128,62 +144,85 @@ export async function hydrateProfileAffiliationOrganizations(
128
144
  and organization_handle is not null
129
145
  order by last_seen_at desc
130
146
  `,
131
- )
132
- .all(subjectProfileId) as SyntheticAffiliationRow[];
133
-
134
- const result: ProfileAffiliationHydrationResult = {
135
- checked: rows.length,
136
- hydrated: 0,
137
- skipped: 0,
138
- errors: [],
139
- };
140
-
141
- for (const row of rows) {
142
- const handle = normalizeHandle(row.organization_handle);
143
- if (!handle) {
144
- result.skipped += 1;
145
- continue;
146
- }
147
- try {
148
- const localOrganizationProfileId = findLocalOrganizationProfileId(
149
- db,
150
- handle,
151
- );
152
- if (localOrganizationProfileId) {
153
- db.transaction(() => {
154
- replaceSyntheticAffiliation(db, row, localOrganizationProfileId);
155
- })();
156
- result.hydrated += 1;
157
- continue;
158
- }
147
+ )
148
+ .all(subjectProfileId) as SyntheticAffiliationRow[],
149
+ );
159
150
 
160
- const user = await lookupProfileViaBird(handle);
161
- if (!user) {
151
+ const result: ProfileAffiliationHydrationResult = {
152
+ checked: rows.length,
153
+ hydrated: 0,
154
+ skipped: 0,
155
+ errors: [],
156
+ };
157
+
158
+ for (const row of rows) {
159
+ const handle = normalizeHandle(row.organization_handle);
160
+ if (!handle) {
162
161
  result.skipped += 1;
163
162
  continue;
164
163
  }
165
- const resolved = upsertProfileFromXUser(db, user);
166
- if (resolved.profile.id === row.organization_profile_id) {
167
- result.skipped += 1;
164
+ const hydrated = yield* Effect.gen(function* () {
165
+ const localOrganizationProfileId = yield* trySync(() =>
166
+ findLocalOrganizationProfileId(db, handle),
167
+ );
168
+ if (localOrganizationProfileId) {
169
+ yield* trySync(() =>
170
+ db.transaction(() => {
171
+ replaceSyntheticAffiliation(db, row, localOrganizationProfileId);
172
+ })(),
173
+ );
174
+ result.hydrated += 1;
175
+ return true;
176
+ }
177
+
178
+ const user = yield* lookupProfileViaBirdEffect(handle);
179
+ if (!user) {
180
+ result.skipped += 1;
181
+ return true;
182
+ }
183
+ const resolved = yield* trySync(() => upsertProfileFromXUser(db, user));
184
+ if (resolved.profile.id === row.organization_profile_id) {
185
+ result.skipped += 1;
186
+ return true;
187
+ }
188
+ yield* trySync(() =>
189
+ db.transaction(() => {
190
+ replaceSyntheticAffiliation(db, row, resolved.profile.id);
191
+ })(),
192
+ );
193
+ result.hydrated += 1;
194
+ return true;
195
+ }).pipe(
196
+ Effect.catchAll((error) => {
197
+ result.errors.push({
198
+ handle,
199
+ error: error instanceof Error ? error.message : String(error),
200
+ });
201
+ return Effect.succeed(false);
202
+ }),
203
+ );
204
+ if (!hydrated) {
168
205
  continue;
169
206
  }
170
- db.transaction(() => {
171
- replaceSyntheticAffiliation(db, row, resolved.profile.id);
172
- })();
173
- result.hydrated += 1;
174
- } catch (error) {
175
- result.errors.push({
176
- handle,
177
- error: error instanceof Error ? error.message : String(error),
207
+ }
208
+
209
+ if (result.hydrated > 0) {
210
+ yield* trySync(() => {
211
+ recordProfileSnapshot(db, subjectProfileId, "affiliation_hydration");
212
+ syncProfileBioEntitiesForProfileId(db, subjectProfileId);
213
+ syncIdentitySearchIndexForProfileIds(db, [subjectProfileId]);
178
214
  });
179
215
  }
180
- }
181
216
 
182
- if (result.hydrated > 0) {
183
- recordProfileSnapshot(db, subjectProfileId, "affiliation_hydration");
184
- syncProfileBioEntitiesForProfileId(db, subjectProfileId);
185
- syncIdentitySearchIndexForProfileIds(db, [subjectProfileId]);
186
- }
217
+ return result;
218
+ });
219
+ }
187
220
 
188
- return result;
221
+ export function hydrateProfileAffiliationOrganizations(
222
+ db: Database,
223
+ subjectProfileId: string,
224
+ ): Promise<ProfileAffiliationHydrationResult> {
225
+ return runEffectPromise(
226
+ hydrateProfileAffiliationOrganizationsEffect(db, subjectProfileId),
227
+ );
189
228
  }