birdclaw 0.5.0 → 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 +63 -0
- package/README.md +55 -5
- package/bin/birdclaw.mjs +50 -11
- package/package.json +9 -7
- package/public/birdclaw-mark.png +0 -0
- package/scripts/browser-perf.mjs +400 -0
- package/scripts/build-docs-site.mjs +940 -0
- package/scripts/docs-site-assets.mjs +311 -0
- package/scripts/run-vitest.mjs +21 -0
- package/scripts/sanitize-node-options.mjs +23 -0
- package/scripts/start-test-server.mjs +42 -0
- package/src/cli.ts +422 -14
- package/src/components/AccountSwitcher.tsx +186 -0
- package/src/components/AppNav.tsx +29 -9
- package/src/components/AvatarChip.tsx +9 -3
- package/src/components/BrandMark.tsx +67 -0
- package/src/components/ConversationThread.tsx +11 -10
- package/src/components/DmWorkspace.tsx +39 -14
- package/src/components/FeedState.tsx +147 -0
- package/src/components/InboxCard.tsx +14 -2
- package/src/components/LinkPreviewCard.tsx +40 -18
- package/src/components/MarkdownViewer.tsx +452 -0
- package/src/components/SavedTimelineView.tsx +64 -56
- package/src/components/SyncNowButton.tsx +137 -0
- package/src/components/ThemeSlider.tsx +49 -93
- package/src/components/TimelineCard.tsx +364 -136
- package/src/components/TimelineRouteFrame.tsx +170 -0
- package/src/components/TweetMediaGrid.tsx +170 -24
- package/src/components/TweetRichText.tsx +28 -13
- package/src/components/account-selection.ts +64 -0
- package/src/components/useTimelineRouteData.ts +153 -0
- package/src/lib/account-sync-job.ts +654 -0
- package/src/lib/actions-transport.ts +216 -146
- package/src/lib/api-client.ts +304 -0
- package/src/lib/archive-finder.ts +72 -53
- package/src/lib/archive-import.ts +1377 -1298
- 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 +205 -0
- package/src/lib/db.ts +35 -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 +1024 -189
- 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 +41 -10
- package/src/lib/url-expansion.ts +226 -55
- package/src/lib/url-safety.ts +220 -0
- package/src/lib/web-sync.ts +511 -0
- 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 +63 -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 +105 -0
- package/src/routes/dms.tsx +195 -55
- package/src/routes/inbox.tsx +32 -19
- package/src/routes/index.tsx +21 -127
- package/src/routes/links.tsx +50 -5
- package/src/routes/mentions.tsx +21 -127
- package/src/routes/today.tsx +441 -0
- package/src/styles.css +74 -11
- package/vite.config.ts +8 -0
package/src/lib/openai.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { runEffectPromise, tryPromise } from "./effect-runtime";
|
|
3
|
+
|
|
1
4
|
export interface OpenAIInboxScore {
|
|
2
5
|
score: number;
|
|
3
6
|
summary: string;
|
|
@@ -22,65 +25,93 @@ function clampScore(value: number) {
|
|
|
22
25
|
return Math.max(0, Math.min(100, Math.round(value)));
|
|
23
26
|
}
|
|
24
27
|
|
|
25
|
-
|
|
28
|
+
function toError(error: unknown) {
|
|
29
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function trySync<T>(try_: () => T) {
|
|
33
|
+
return Effect.try({
|
|
34
|
+
try: try_,
|
|
35
|
+
catch: toError,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function scoreInboxItemWithOpenAIEffect(
|
|
26
40
|
input: OpenAIInboxInput,
|
|
27
|
-
):
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
41
|
+
): Effect.Effect<OpenAIInboxScore, Error> {
|
|
42
|
+
return Effect.gen(function* () {
|
|
43
|
+
const apiKey = process.env.OPENAI_API_KEY;
|
|
44
|
+
if (!apiKey) {
|
|
45
|
+
return yield* Effect.fail(new Error("OPENAI_API_KEY is not set"));
|
|
46
|
+
}
|
|
32
47
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
body: JSON.stringify({
|
|
41
|
-
model,
|
|
42
|
-
response_format: { type: "json_object" },
|
|
43
|
-
messages: [
|
|
44
|
-
{
|
|
45
|
-
role: "system",
|
|
46
|
-
content:
|
|
47
|
-
"You rank inbound Twitter mentions and DMs for Peter Steinberger. Return JSON only with keys score, summary, reasoning. Score 0-100. High score means worth replying soon. Prefer specific, actionable, novel, high-signal items. Penalize generic praise, low-context asks, and low-signal chatter. summary max 18 words. reasoning max 28 words.",
|
|
48
|
+
const model = process.env.BIRDCLAW_OPENAI_MODEL || "gpt-5.2";
|
|
49
|
+
const response = yield* tryPromise(() =>
|
|
50
|
+
fetch("https://api.openai.com/v1/chat/completions", {
|
|
51
|
+
method: "POST",
|
|
52
|
+
headers: {
|
|
53
|
+
authorization: `Bearer ${apiKey}`,
|
|
54
|
+
"content-type": "application/json",
|
|
48
55
|
},
|
|
49
|
-
{
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
body: JSON.stringify({
|
|
57
|
+
model,
|
|
58
|
+
response_format: { type: "json_object" },
|
|
59
|
+
messages: [
|
|
60
|
+
{
|
|
61
|
+
role: "system",
|
|
62
|
+
content:
|
|
63
|
+
"You rank inbound Twitter mentions and DMs for Peter Steinberger. Return JSON only with keys score, summary, reasoning. Score 0-100. High score means worth replying soon. Prefer specific, actionable, novel, high-signal items. Penalize generic praise, low-context asks, and low-signal chatter. summary max 18 words. reasoning max 28 words.",
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
role: "user",
|
|
67
|
+
content: JSON.stringify(input),
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
}),
|
|
71
|
+
}),
|
|
72
|
+
).pipe(Effect.mapError(toError));
|
|
56
73
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
74
|
+
if (!response.ok) {
|
|
75
|
+
return yield* Effect.fail(
|
|
76
|
+
new Error(`OpenAI request failed: ${response.status}`),
|
|
77
|
+
);
|
|
78
|
+
}
|
|
60
79
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
80
|
+
const payload = (yield* tryPromise(() => response.json()).pipe(
|
|
81
|
+
Effect.mapError(toError),
|
|
82
|
+
)) as {
|
|
83
|
+
choices?: Array<{
|
|
84
|
+
message?: {
|
|
85
|
+
content?: string;
|
|
86
|
+
};
|
|
87
|
+
}>;
|
|
88
|
+
};
|
|
68
89
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
90
|
+
const content = payload.choices?.[0]?.message?.content;
|
|
91
|
+
if (!content) {
|
|
92
|
+
return yield* Effect.fail(new Error("OpenAI returned no content"));
|
|
93
|
+
}
|
|
73
94
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
95
|
+
const parsed = yield* trySync(
|
|
96
|
+
() =>
|
|
97
|
+
JSON.parse(content) as {
|
|
98
|
+
score?: number;
|
|
99
|
+
summary?: string;
|
|
100
|
+
reasoning?: string;
|
|
101
|
+
},
|
|
102
|
+
);
|
|
79
103
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
104
|
+
return {
|
|
105
|
+
model,
|
|
106
|
+
score: clampScore(parsed.score ?? 0),
|
|
107
|
+
summary: String(parsed.summary ?? "No summary"),
|
|
108
|
+
reasoning: String(parsed.reasoning ?? "No reasoning"),
|
|
109
|
+
};
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function scoreInboxItemWithOpenAI(
|
|
114
|
+
input: OpenAIInboxInput,
|
|
115
|
+
): Promise<OpenAIInboxScore> {
|
|
116
|
+
return runEffectPromise(scoreInboxItemWithOpenAIEffect(input));
|
|
86
117
|
}
|