nod-shout 0.1.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/README.md +82 -0
- package/TASK-AGENT-POSTS.md +112 -0
- package/assets/shout-default.svg +5 -0
- package/bin/shout +68 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/ai.d.ts +13 -0
- package/dist/lib/ai.d.ts.map +1 -0
- package/dist/lib/ai.js +135 -0
- package/dist/lib/ai.js.map +1 -0
- package/dist/lib/content-filter.d.ts +74 -0
- package/dist/lib/content-filter.d.ts.map +1 -0
- package/dist/lib/content-filter.js +188 -0
- package/dist/lib/content-filter.js.map +1 -0
- package/dist/lib/context-extractor.d.ts +39 -0
- package/dist/lib/context-extractor.d.ts.map +1 -0
- package/dist/lib/context-extractor.js +170 -0
- package/dist/lib/context-extractor.js.map +1 -0
- package/dist/lib/match-engine.d.ts +31 -0
- package/dist/lib/match-engine.d.ts.map +1 -0
- package/dist/lib/match-engine.js +322 -0
- package/dist/lib/match-engine.js.map +1 -0
- package/dist/lib/metadata.d.ts +7 -0
- package/dist/lib/metadata.d.ts.map +1 -0
- package/dist/lib/metadata.js +311 -0
- package/dist/lib/metadata.js.map +1 -0
- package/dist/lib/skills.d.ts +3 -0
- package/dist/lib/skills.d.ts.map +1 -0
- package/dist/lib/skills.js +20 -0
- package/dist/lib/skills.js.map +1 -0
- package/dist/lib/supabase.d.ts +2 -0
- package/dist/lib/supabase.d.ts.map +1 -0
- package/dist/lib/supabase.js +8 -0
- package/dist/lib/supabase.js.map +1 -0
- package/dist/tools/collections.d.ts +3 -0
- package/dist/tools/collections.d.ts.map +1 -0
- package/dist/tools/collections.js +142 -0
- package/dist/tools/collections.js.map +1 -0
- package/dist/tools/intros.d.ts +3 -0
- package/dist/tools/intros.d.ts.map +1 -0
- package/dist/tools/intros.js +483 -0
- package/dist/tools/intros.js.map +1 -0
- package/dist/tools/links.d.ts +3 -0
- package/dist/tools/links.d.ts.map +1 -0
- package/dist/tools/links.js +424 -0
- package/dist/tools/links.js.map +1 -0
- package/dist/tools/posts.d.ts +3 -0
- package/dist/tools/posts.d.ts.map +1 -0
- package/dist/tools/posts.js +212 -0
- package/dist/tools/posts.js.map +1 -0
- package/dist/tools/settings.d.ts +3 -0
- package/dist/tools/settings.d.ts.map +1 -0
- package/dist/tools/settings.js +45 -0
- package/dist/tools/settings.js.map +1 -0
- package/dist/tools/shout_agent_curate.d.ts +28 -0
- package/dist/tools/shout_agent_curate.d.ts.map +1 -0
- package/dist/tools/shout_agent_curate.js +80 -0
- package/dist/tools/shout_agent_curate.js.map +1 -0
- package/dist/tools/social.d.ts +3 -0
- package/dist/tools/social.d.ts.map +1 -0
- package/dist/tools/social.js +169 -0
- package/dist/tools/social.js.map +1 -0
- package/dist/types.d.ts +60 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +24 -0
- package/quick-test.ts +22 -0
- package/regenerate-summaries.ts +111 -0
- package/save-jeffries-shout.ts +38 -0
- package/save-openai-shout.ts +35 -0
- package/save-prcarly.ts +46 -0
- package/save-shout.ts +35 -0
- package/save-techcrunch-shout.ts +59 -0
- package/save-zdnet-shout.ts +36 -0
- package/skills/collection-routing/SKILL.md +34 -0
- package/skills/link-summary/SKILL.md +53 -0
- package/skills/tagging-and-routing/SKILL.md +54 -0
- package/src/index.ts +32 -0
- package/src/lib/ai.ts +166 -0
- package/src/lib/content-filter.ts +258 -0
- package/src/lib/metadata.ts +353 -0
- package/src/lib/skills.ts +21 -0
- package/src/lib/supabase.ts +12 -0
- package/src/tools/collections.ts +182 -0
- package/src/tools/links.ts +524 -0
- package/src/tools/posts.ts +264 -0
- package/src/tools/settings.ts +55 -0
- package/src/tools/shout_agent_curate.ts +95 -0
- package/src/tools/social.ts +206 -0
- package/src/types.ts +66 -0
- package/supabase/.temp/cli-latest +1 -0
- package/supabase/.temp/gotrue-version +1 -0
- package/supabase/.temp/pooler-url +1 -0
- package/supabase/.temp/postgres-version +1 -0
- package/supabase/.temp/project-ref +1 -0
- package/supabase/.temp/rest-version +1 -0
- package/supabase/.temp/storage-migration +1 -0
- package/supabase/.temp/storage-version +1 -0
- package/supabase/migrations/001_initial_schema.sql +147 -0
- package/supabase/migrations/20260317010000_decouple_profiles_from_auth.sql +9 -0
- package/supabase/migrations/20260317020000_agent_curation.sql +10 -0
- package/supabase/migrations/20260320000000_agent_posts.sql +41 -0
- package/supabase/migrations/20260320120000_fix_draft_fk.sql +2 -0
- package/supabase/migrations/20260320130000_fix_identity.sql +17 -0
- package/supabase/migrations/20260320170000_intros.sql +118 -0
- package/test-model-comparison.ts +89 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { supabase } from "../lib/supabase.js";
|
|
3
|
+
import { extractMetadata } from "../lib/metadata.js";
|
|
4
|
+
import { generateSummary } from "../lib/ai.js";
|
|
5
|
+
import { filterShoutContent } from "../lib/content-filter.js";
|
|
6
|
+
function inferRoutingHints(params) {
|
|
7
|
+
const haystack = [
|
|
8
|
+
params.url,
|
|
9
|
+
params.title || "",
|
|
10
|
+
params.description || "",
|
|
11
|
+
params.category,
|
|
12
|
+
...params.tags,
|
|
13
|
+
]
|
|
14
|
+
.join(" ")
|
|
15
|
+
.toLowerCase();
|
|
16
|
+
const hints = new Set();
|
|
17
|
+
if (/(\bmcp\b|model context protocol|a2a|agent discovery|agents\.json|json agents|agent manifest|agent skills)/.test(haystack)) {
|
|
18
|
+
hints.add("agents");
|
|
19
|
+
hints.add("protocols");
|
|
20
|
+
}
|
|
21
|
+
if (/(github|sdk|api|cli|claude code|codex|developer|devtool|typescript|repo)/.test(haystack)) {
|
|
22
|
+
hints.add("devtools");
|
|
23
|
+
}
|
|
24
|
+
if (/(prompt injection|security|auth|exploit|abuse|vulnerability)/.test(haystack)) {
|
|
25
|
+
hints.add("security");
|
|
26
|
+
}
|
|
27
|
+
return Array.from(hints);
|
|
28
|
+
}
|
|
29
|
+
export function registerLinkTools(server) {
|
|
30
|
+
// shout_save_link - fetch metadata, generate summary, store in supabase
|
|
31
|
+
server.tool("shout_save_link", "save a link to your shout page. fetches metadata, generates summary and tags automatically.", {
|
|
32
|
+
url: z.string().url().describe("the url to save"),
|
|
33
|
+
user_id: z.string().uuid().describe("the user's id"),
|
|
34
|
+
take: z
|
|
35
|
+
.string()
|
|
36
|
+
.optional()
|
|
37
|
+
.describe("user's commentary or take on the link"),
|
|
38
|
+
collection: z
|
|
39
|
+
.string()
|
|
40
|
+
.optional()
|
|
41
|
+
.describe("collection slug to add this to"),
|
|
42
|
+
visibility: z
|
|
43
|
+
.enum(["public", "private", "unlisted"])
|
|
44
|
+
.optional()
|
|
45
|
+
.default("public")
|
|
46
|
+
.describe("visibility of this shout"),
|
|
47
|
+
}, async ({ url, user_id, take, collection, visibility }) => {
|
|
48
|
+
// fetch page metadata
|
|
49
|
+
const metadata = await extractMetadata(url);
|
|
50
|
+
// generate ai summary + tags (stubbed for v1)
|
|
51
|
+
const aiResult = await generateSummary({
|
|
52
|
+
url,
|
|
53
|
+
title: metadata.title,
|
|
54
|
+
description: metadata.description,
|
|
55
|
+
bodyText: metadata.bodyText,
|
|
56
|
+
userContext: take || null,
|
|
57
|
+
});
|
|
58
|
+
// resolve collection id if slug provided
|
|
59
|
+
let collection_id = null;
|
|
60
|
+
if (collection) {
|
|
61
|
+
const { data: col } = await supabase
|
|
62
|
+
.from("collections")
|
|
63
|
+
.select("id")
|
|
64
|
+
.eq("user_id", user_id)
|
|
65
|
+
.eq("slug", collection)
|
|
66
|
+
.single();
|
|
67
|
+
collection_id = col?.id || null;
|
|
68
|
+
}
|
|
69
|
+
// auto-rules engine: if no explicit collection, check tag-based auto-routing
|
|
70
|
+
if (!collection_id) {
|
|
71
|
+
const routingHints = inferRoutingHints({
|
|
72
|
+
url,
|
|
73
|
+
title: metadata.title,
|
|
74
|
+
description: metadata.description,
|
|
75
|
+
tags: aiResult.tags,
|
|
76
|
+
category: aiResult.category,
|
|
77
|
+
});
|
|
78
|
+
const combinedTags = Array.from(new Set([...aiResult.tags, ...routingHints]));
|
|
79
|
+
if (combinedTags.length > 0) {
|
|
80
|
+
const { data: collections } = await supabase
|
|
81
|
+
.from("collections")
|
|
82
|
+
.select("id, auto_rules")
|
|
83
|
+
.eq("user_id", user_id)
|
|
84
|
+
.not("auto_rules", "is", null);
|
|
85
|
+
if (collections && collections.length > 0) {
|
|
86
|
+
for (const col of collections) {
|
|
87
|
+
const rules = col.auto_rules;
|
|
88
|
+
if (!rules)
|
|
89
|
+
continue;
|
|
90
|
+
const matchTags = rules.match_tags;
|
|
91
|
+
if (!Array.isArray(matchTags))
|
|
92
|
+
continue;
|
|
93
|
+
const hasMatch = matchTags.some((ruleTag) => typeof ruleTag === "string" &&
|
|
94
|
+
combinedTags.some((t) => t.toLowerCase() === ruleTag.toLowerCase()));
|
|
95
|
+
if (hasMatch) {
|
|
96
|
+
collection_id = col.id;
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// PII/proprietary data filter before saving
|
|
104
|
+
const filtered = filterShoutContent({
|
|
105
|
+
take: take || null,
|
|
106
|
+
summary: aiResult.summary,
|
|
107
|
+
title: metadata.title,
|
|
108
|
+
description: metadata.description,
|
|
109
|
+
});
|
|
110
|
+
if (filtered.filterReport) {
|
|
111
|
+
console.log(`[nod-shout] content filter active: ${filtered.filterReport}`);
|
|
112
|
+
}
|
|
113
|
+
// insert the shout
|
|
114
|
+
const { data, error } = await supabase
|
|
115
|
+
.from("shouts")
|
|
116
|
+
.insert({
|
|
117
|
+
user_id,
|
|
118
|
+
url,
|
|
119
|
+
title: filtered.title,
|
|
120
|
+
description: filtered.description,
|
|
121
|
+
summary: filtered.summary,
|
|
122
|
+
user_take: filtered.take,
|
|
123
|
+
image_url: metadata.image_url,
|
|
124
|
+
tags: aiResult.tags,
|
|
125
|
+
category: aiResult.category,
|
|
126
|
+
collection_id,
|
|
127
|
+
source: "conversation",
|
|
128
|
+
visibility: visibility || "public",
|
|
129
|
+
})
|
|
130
|
+
.select()
|
|
131
|
+
.single();
|
|
132
|
+
if (error) {
|
|
133
|
+
return {
|
|
134
|
+
content: [
|
|
135
|
+
{ type: "text", text: `error saving shout: ${error.message}` },
|
|
136
|
+
],
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
const tagStr = aiResult.tags.length > 0 ? aiResult.tags.join(", ") : "none";
|
|
140
|
+
return {
|
|
141
|
+
content: [
|
|
142
|
+
{
|
|
143
|
+
type: "text",
|
|
144
|
+
text: `saved! "${metadata.title || url}"\nsummary: ${aiResult.summary}\ntags: ${tagStr}\ncategory: ${aiResult.category}`,
|
|
145
|
+
},
|
|
146
|
+
],
|
|
147
|
+
};
|
|
148
|
+
});
|
|
149
|
+
// shout_agent_curate - agent proactively saves links it finds interesting
|
|
150
|
+
server.tool("shout_agent_curate", "save a link the agent (fubz/clawd) finds interesting. use this to proactively curate links from conversations, research, or browsing. sets source to 'agent_curated' to distinguish from user-shared links.", {
|
|
151
|
+
url: z.string().url().describe("the URL to save"),
|
|
152
|
+
user_id: z.string().uuid().describe("the fubz profile user id"),
|
|
153
|
+
agent_take: z
|
|
154
|
+
.string()
|
|
155
|
+
.optional()
|
|
156
|
+
.describe("the agent's own commentary on why this link is interesting"),
|
|
157
|
+
tags: z
|
|
158
|
+
.array(z.string())
|
|
159
|
+
.optional()
|
|
160
|
+
.describe("override auto-generated tags"),
|
|
161
|
+
collection_id: z
|
|
162
|
+
.string()
|
|
163
|
+
.uuid()
|
|
164
|
+
.optional()
|
|
165
|
+
.describe("put the shout in a specific collection"),
|
|
166
|
+
}, async ({ url, user_id, agent_take, tags, collection_id }) => {
|
|
167
|
+
const metadata = await extractMetadata(url);
|
|
168
|
+
const aiResult = await generateSummary({
|
|
169
|
+
url,
|
|
170
|
+
title: metadata.title,
|
|
171
|
+
description: metadata.description,
|
|
172
|
+
bodyText: metadata.bodyText,
|
|
173
|
+
userContext: agent_take || null,
|
|
174
|
+
});
|
|
175
|
+
const finalTags = tags || aiResult.tags;
|
|
176
|
+
const { data, error } = await supabase
|
|
177
|
+
.from("shouts")
|
|
178
|
+
.insert({
|
|
179
|
+
user_id,
|
|
180
|
+
url,
|
|
181
|
+
title: metadata.title,
|
|
182
|
+
description: metadata.description,
|
|
183
|
+
summary: aiResult.summary,
|
|
184
|
+
user_take: agent_take || null,
|
|
185
|
+
image_url: metadata.image_url,
|
|
186
|
+
tags: finalTags,
|
|
187
|
+
category: aiResult.category,
|
|
188
|
+
collection_id: collection_id || null,
|
|
189
|
+
source: "agent_curated",
|
|
190
|
+
visibility: "public",
|
|
191
|
+
})
|
|
192
|
+
.select()
|
|
193
|
+
.single();
|
|
194
|
+
if (error) {
|
|
195
|
+
return {
|
|
196
|
+
content: [{ type: "text", text: `error saving agent-curated shout: ${error.message}` }],
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
const tagStr = finalTags.length > 0 ? finalTags.join(", ") : "none";
|
|
200
|
+
return {
|
|
201
|
+
content: [
|
|
202
|
+
{
|
|
203
|
+
type: "text",
|
|
204
|
+
text: `agent curated! "${metadata.title || url}"\nsummary: ${aiResult.summary}\ntags: ${tagStr}\ncategory: ${aiResult.category}${agent_take ? `\nagent take: ${agent_take}` : ""}`,
|
|
205
|
+
},
|
|
206
|
+
],
|
|
207
|
+
};
|
|
208
|
+
});
|
|
209
|
+
// shout_detect_links - scan text for URLs and offer to save them
|
|
210
|
+
server.tool("shout_detect_links", "scan a message or conversation text for URLs. returns found links with metadata so the agent can ask the user if they want to shout any of them. call this when a user shares links in conversation.", {
|
|
211
|
+
text: z.string().describe("the message text to scan for URLs"),
|
|
212
|
+
user_id: z.string().uuid().describe("the user's id"),
|
|
213
|
+
}, async ({ text, user_id }) => {
|
|
214
|
+
// extract URLs from text
|
|
215
|
+
const urlRegex = /https?:\/\/[^\s<>"')\]]+/gi;
|
|
216
|
+
const urls = [...new Set(text.match(urlRegex) || [])];
|
|
217
|
+
if (urls.length === 0) {
|
|
218
|
+
return {
|
|
219
|
+
content: [
|
|
220
|
+
{ type: "text", text: "no links found in the text." },
|
|
221
|
+
],
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
// check which URLs are already saved
|
|
225
|
+
const { data: existing } = await supabase
|
|
226
|
+
.from("shouts")
|
|
227
|
+
.select("url")
|
|
228
|
+
.eq("user_id", user_id)
|
|
229
|
+
.in("url", urls);
|
|
230
|
+
const existingUrls = new Set((existing || []).map((s) => s.url));
|
|
231
|
+
const newUrls = urls.filter((u) => !existingUrls.has(u));
|
|
232
|
+
if (newUrls.length === 0) {
|
|
233
|
+
return {
|
|
234
|
+
content: [
|
|
235
|
+
{
|
|
236
|
+
type: "text",
|
|
237
|
+
text: `found ${urls.length} link${urls.length !== 1 ? "s" : ""} but ${urls.length === 1 ? "it's" : "they're"} already saved.`,
|
|
238
|
+
},
|
|
239
|
+
],
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
// fetch metadata for new URLs (in parallel)
|
|
243
|
+
const previews = await Promise.all(newUrls.map(async (url) => {
|
|
244
|
+
try {
|
|
245
|
+
const metadata = await extractMetadata(url);
|
|
246
|
+
return {
|
|
247
|
+
url,
|
|
248
|
+
title: metadata.title || url,
|
|
249
|
+
description: metadata.description || null,
|
|
250
|
+
already_saved: false,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
return { url, title: url, description: null, already_saved: false };
|
|
255
|
+
}
|
|
256
|
+
}));
|
|
257
|
+
const lines = previews.map((p, i) => `${i + 1}. ${p.title}\n ${p.url}${p.description ? `\n ${p.description.slice(0, 120)}` : ""}`);
|
|
258
|
+
return {
|
|
259
|
+
content: [
|
|
260
|
+
{
|
|
261
|
+
type: "text",
|
|
262
|
+
text: `found ${newUrls.length} new link${newUrls.length !== 1 ? "s" : ""}:\n\n${lines.join("\n\n")}\n\nask the user which ones to shout, then call shout_save_link for each.`,
|
|
263
|
+
},
|
|
264
|
+
],
|
|
265
|
+
};
|
|
266
|
+
});
|
|
267
|
+
// shout_generate_digest - generate a weekly digest from recent shouts
|
|
268
|
+
server.tool("shout_generate_digest", "generate a digest summary from recent shouts. returns markdown suitable for a blog post or email. also stores the digest in the digests table.", {
|
|
269
|
+
user_id: z.string().uuid().describe("the user's id"),
|
|
270
|
+
days: z
|
|
271
|
+
.number()
|
|
272
|
+
.optional()
|
|
273
|
+
.default(7)
|
|
274
|
+
.describe("number of days to include (default 7)"),
|
|
275
|
+
}, async ({ user_id, days }) => {
|
|
276
|
+
const since = new Date(Date.now() - (days || 7) * 24 * 60 * 60 * 1000).toISOString();
|
|
277
|
+
const { data: shouts } = await supabase
|
|
278
|
+
.from("shouts")
|
|
279
|
+
.select("url, title, summary, user_take, tags, category, created_at")
|
|
280
|
+
.eq("user_id", user_id)
|
|
281
|
+
.gte("created_at", since)
|
|
282
|
+
.order("created_at", { ascending: true });
|
|
283
|
+
if (!shouts || shouts.length === 0) {
|
|
284
|
+
return {
|
|
285
|
+
content: [
|
|
286
|
+
{
|
|
287
|
+
type: "text",
|
|
288
|
+
text: `no shouts in the last ${days} days.`,
|
|
289
|
+
},
|
|
290
|
+
],
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
const { data: profile } = await supabase
|
|
294
|
+
.from("profiles")
|
|
295
|
+
.select("username, display_name")
|
|
296
|
+
.eq("id", user_id)
|
|
297
|
+
.single();
|
|
298
|
+
const name = profile?.display_name ||
|
|
299
|
+
profile?.username ||
|
|
300
|
+
"someone";
|
|
301
|
+
// build the digest content
|
|
302
|
+
const categories = new Map();
|
|
303
|
+
for (const s of shouts) {
|
|
304
|
+
const cat = s.category || "uncategorized";
|
|
305
|
+
if (!categories.has(cat))
|
|
306
|
+
categories.set(cat, []);
|
|
307
|
+
categories.get(cat).push(s);
|
|
308
|
+
}
|
|
309
|
+
let markdown = `# ${name}'s weekly shouts\n\n`;
|
|
310
|
+
markdown += `*${shouts.length} links from the last ${days} days*\n\n`;
|
|
311
|
+
for (const [cat, items] of categories) {
|
|
312
|
+
markdown += `## ${cat}\n\n`;
|
|
313
|
+
for (const s of items) {
|
|
314
|
+
markdown += `**[${s.title || s.url}](${s.url})**\n`;
|
|
315
|
+
if (s.summary)
|
|
316
|
+
markdown += `${s.summary}\n`;
|
|
317
|
+
if (s.user_take)
|
|
318
|
+
markdown += `> ${s.user_take}\n`;
|
|
319
|
+
markdown += `\n`;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
// store digest in supabase
|
|
323
|
+
const { error: digestError } = await supabase.from("digests").insert({
|
|
324
|
+
user_id,
|
|
325
|
+
period_start: since,
|
|
326
|
+
period_end: new Date().toISOString(),
|
|
327
|
+
content: markdown,
|
|
328
|
+
shout_count: shouts.length,
|
|
329
|
+
});
|
|
330
|
+
// don't fail if digests table doesn't exist yet
|
|
331
|
+
if (digestError) {
|
|
332
|
+
console.error("digest storage failed (table may not exist):", digestError.message);
|
|
333
|
+
}
|
|
334
|
+
return {
|
|
335
|
+
content: [
|
|
336
|
+
{
|
|
337
|
+
type: "text",
|
|
338
|
+
text: markdown,
|
|
339
|
+
},
|
|
340
|
+
],
|
|
341
|
+
};
|
|
342
|
+
});
|
|
343
|
+
// shout_list - list user's shouts with optional filters
|
|
344
|
+
server.tool("shout_list", "list your saved shouts. filter by tag, collection, or limit results.", {
|
|
345
|
+
user_id: z.string().uuid().describe("the user's id"),
|
|
346
|
+
filter: z
|
|
347
|
+
.string()
|
|
348
|
+
.optional()
|
|
349
|
+
.describe("filter by tag name"),
|
|
350
|
+
collection: z
|
|
351
|
+
.string()
|
|
352
|
+
.optional()
|
|
353
|
+
.describe("filter by collection slug"),
|
|
354
|
+
limit: z
|
|
355
|
+
.number()
|
|
356
|
+
.optional()
|
|
357
|
+
.default(20)
|
|
358
|
+
.describe("max results to return"),
|
|
359
|
+
}, async ({ user_id, filter, collection, limit }) => {
|
|
360
|
+
let query = supabase
|
|
361
|
+
.from("shouts")
|
|
362
|
+
.select("*")
|
|
363
|
+
.eq("user_id", user_id)
|
|
364
|
+
.order("created_at", { ascending: false })
|
|
365
|
+
.limit(limit || 20);
|
|
366
|
+
// filter by tag (postgres array contains)
|
|
367
|
+
if (filter) {
|
|
368
|
+
query = query.contains("tags", [filter]);
|
|
369
|
+
}
|
|
370
|
+
// filter by collection slug
|
|
371
|
+
if (collection) {
|
|
372
|
+
const { data: col } = await supabase
|
|
373
|
+
.from("collections")
|
|
374
|
+
.select("id")
|
|
375
|
+
.eq("user_id", user_id)
|
|
376
|
+
.eq("slug", collection)
|
|
377
|
+
.single();
|
|
378
|
+
if (col) {
|
|
379
|
+
query = query.eq("collection_id", col.id);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
const { data, error } = await query;
|
|
383
|
+
if (error) {
|
|
384
|
+
return {
|
|
385
|
+
content: [
|
|
386
|
+
{ type: "text", text: `error listing shouts: ${error.message}` },
|
|
387
|
+
],
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
if (!data || data.length === 0) {
|
|
391
|
+
return {
|
|
392
|
+
content: [{ type: "text", text: "no shouts found." }],
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
const lines = data.map((s, i) => `${i + 1}. ${s.title || s.url}\n ${s.summary || ""}\n tags: ${(s.tags || []).join(", ")} | ${s.visibility} | ${s.created_at}`);
|
|
396
|
+
return {
|
|
397
|
+
content: [
|
|
398
|
+
{ type: "text", text: `${data.length} shouts:\n\n${lines.join("\n\n")}` },
|
|
399
|
+
],
|
|
400
|
+
};
|
|
401
|
+
});
|
|
402
|
+
// shout_remove - delete a shout by id
|
|
403
|
+
server.tool("shout_remove", "delete a shout by its id.", {
|
|
404
|
+
id: z.string().uuid().describe("the shout id to delete"),
|
|
405
|
+
user_id: z.string().uuid().describe("the user's id"),
|
|
406
|
+
}, async ({ id, user_id }) => {
|
|
407
|
+
const { error } = await supabase
|
|
408
|
+
.from("shouts")
|
|
409
|
+
.delete()
|
|
410
|
+
.eq("id", id)
|
|
411
|
+
.eq("user_id", user_id);
|
|
412
|
+
if (error) {
|
|
413
|
+
return {
|
|
414
|
+
content: [
|
|
415
|
+
{ type: "text", text: `error removing shout: ${error.message}` },
|
|
416
|
+
],
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
return {
|
|
420
|
+
content: [{ type: "text", text: `shout ${id} removed.` }],
|
|
421
|
+
};
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
//# sourceMappingURL=links.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"links.js","sourceRoot":"","sources":["../../src/tools/links.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"posts.d.ts","sourceRoot":"","sources":["../../src/tools/posts.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAwDzE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,QA4MlD"}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { supabase } from "../lib/supabase.js";
|
|
3
|
+
import { filterPII, filterShoutContentFull } from "../lib/content-filter.js";
|
|
4
|
+
async function publishDraft(draftId, userId, text, tags, category, collectionId, visibility) {
|
|
5
|
+
// PII filter runs again on publish (hard constraint)
|
|
6
|
+
const filtered = await filterShoutContentFull({ take: text, skipLLMForMetadata: true });
|
|
7
|
+
if (filtered.blocked) {
|
|
8
|
+
// update draft status to rejected with reason
|
|
9
|
+
await supabase.from("draft_posts").update({
|
|
10
|
+
status: "rejected",
|
|
11
|
+
filter_report: filtered.blockReason,
|
|
12
|
+
updated_at: new Date().toISOString(),
|
|
13
|
+
}).eq("id", draftId);
|
|
14
|
+
return { published: false, reason: filtered.blockReason };
|
|
15
|
+
}
|
|
16
|
+
const finalText = filtered.take || text;
|
|
17
|
+
// insert into shouts
|
|
18
|
+
const { data: shout, error: shoutErr } = await supabase
|
|
19
|
+
.from("shouts")
|
|
20
|
+
.insert({
|
|
21
|
+
user_id: userId,
|
|
22
|
+
url: null,
|
|
23
|
+
title: null,
|
|
24
|
+
description: finalText,
|
|
25
|
+
summary: finalText,
|
|
26
|
+
user_take: finalText,
|
|
27
|
+
image_url: null,
|
|
28
|
+
tags,
|
|
29
|
+
category,
|
|
30
|
+
collection_id: collectionId,
|
|
31
|
+
source: "agent_post",
|
|
32
|
+
visibility,
|
|
33
|
+
shout_type: "post",
|
|
34
|
+
draft_id: draftId,
|
|
35
|
+
})
|
|
36
|
+
.select()
|
|
37
|
+
.single();
|
|
38
|
+
if (shoutErr) {
|
|
39
|
+
return { published: false, reason: `db error: ${shoutErr.message}` };
|
|
40
|
+
}
|
|
41
|
+
// update draft status
|
|
42
|
+
await supabase.from("draft_posts").update({
|
|
43
|
+
status: "published",
|
|
44
|
+
published_shout_id: shout.id,
|
|
45
|
+
filtered_text: finalText,
|
|
46
|
+
filter_report: filtered.filterReport,
|
|
47
|
+
updated_at: new Date().toISOString(),
|
|
48
|
+
}).eq("id", draftId);
|
|
49
|
+
return { published: true, shoutId: shout.id, text: finalText };
|
|
50
|
+
}
|
|
51
|
+
export function registerPostTools(server) {
|
|
52
|
+
// shout_draft_post — create a text post draft
|
|
53
|
+
server.tool("shout_draft_post", "draft a text post for a shout page. runs PII filter. saved as pending unless auto-publish is on.", {
|
|
54
|
+
user_id: z.string().uuid().describe("the user's id"),
|
|
55
|
+
text: z.string().max(500).describe("post content, max 500 chars"),
|
|
56
|
+
tags: z.array(z.string()).optional().describe("tags for the post"),
|
|
57
|
+
category: z.string().optional().describe("category"),
|
|
58
|
+
collection: z.string().optional().describe("collection slug"),
|
|
59
|
+
visibility: z.enum(["public", "private", "unlisted"]).optional().default("public"),
|
|
60
|
+
}, async ({ user_id, text, tags, category, collection, visibility }) => {
|
|
61
|
+
// layer 1: regex PII filter
|
|
62
|
+
const regexResult = filterPII(text);
|
|
63
|
+
// resolve collection
|
|
64
|
+
let collection_id = null;
|
|
65
|
+
if (collection) {
|
|
66
|
+
const { data: col } = await supabase
|
|
67
|
+
.from("collections")
|
|
68
|
+
.select("id")
|
|
69
|
+
.eq("user_id", user_id)
|
|
70
|
+
.eq("slug", collection)
|
|
71
|
+
.single();
|
|
72
|
+
if (col)
|
|
73
|
+
collection_id = col.id;
|
|
74
|
+
}
|
|
75
|
+
// create draft
|
|
76
|
+
const { data: draft, error } = await supabase
|
|
77
|
+
.from("draft_posts")
|
|
78
|
+
.insert({
|
|
79
|
+
user_id,
|
|
80
|
+
text: regexResult.text,
|
|
81
|
+
filtered_text: regexResult.text,
|
|
82
|
+
tags: tags || [],
|
|
83
|
+
category: category || null,
|
|
84
|
+
collection_id,
|
|
85
|
+
visibility: visibility || "public",
|
|
86
|
+
status: "pending",
|
|
87
|
+
filter_report: regexResult.filtered ? `regex: ${regexResult.removals.join(", ")}` : null,
|
|
88
|
+
source: "agent",
|
|
89
|
+
})
|
|
90
|
+
.select()
|
|
91
|
+
.single();
|
|
92
|
+
if (error) {
|
|
93
|
+
return { content: [{ type: "text", text: `error creating draft: ${error.message}` }] };
|
|
94
|
+
}
|
|
95
|
+
// check auto-publish setting
|
|
96
|
+
const { data: settings } = await supabase
|
|
97
|
+
.from("user_settings")
|
|
98
|
+
.select("auto_publish")
|
|
99
|
+
.eq("user_id", user_id)
|
|
100
|
+
.single();
|
|
101
|
+
if (settings?.auto_publish) {
|
|
102
|
+
const result = await publishDraft(draft.id, user_id, regexResult.text, tags || [], category || null, collection_id, visibility || "public");
|
|
103
|
+
if (result.published) {
|
|
104
|
+
return { content: [{ type: "text", text: `auto-published post: "${result.text}" (shout id: ${result.shoutId})` }] };
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
return { content: [{ type: "text", text: `auto-publish blocked by content filter: ${result.reason}` }] };
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
content: [{
|
|
112
|
+
type: "text",
|
|
113
|
+
text: `draft created (id: ${draft.id}). status: pending approval.${regexResult.filtered ? ` PII filter applied: ${regexResult.removals.join(", ")}` : ""}\n\ntext: "${regexResult.text}"`,
|
|
114
|
+
}],
|
|
115
|
+
};
|
|
116
|
+
});
|
|
117
|
+
// shout_list_drafts — list draft posts
|
|
118
|
+
server.tool("shout_list_drafts", "list draft posts by status. defaults to pending.", {
|
|
119
|
+
user_id: z.string().uuid().describe("the user's id"),
|
|
120
|
+
status: z.enum(["pending", "approved", "rejected", "published"]).optional().default("pending"),
|
|
121
|
+
limit: z.number().optional().default(20),
|
|
122
|
+
}, async ({ user_id, status, limit }) => {
|
|
123
|
+
const { data, error } = await supabase
|
|
124
|
+
.from("draft_posts")
|
|
125
|
+
.select("*")
|
|
126
|
+
.eq("user_id", user_id)
|
|
127
|
+
.eq("status", status || "pending")
|
|
128
|
+
.order("created_at", { ascending: false })
|
|
129
|
+
.limit(limit || 20);
|
|
130
|
+
if (error) {
|
|
131
|
+
return { content: [{ type: "text", text: `error listing drafts: ${error.message}` }] };
|
|
132
|
+
}
|
|
133
|
+
if (!data || data.length === 0) {
|
|
134
|
+
return { content: [{ type: "text", text: `no ${status || "pending"} drafts found.` }] };
|
|
135
|
+
}
|
|
136
|
+
const lines = data.map((d, i) => `${i + 1}. [${d.id}] "${d.text?.substring(0, 80)}${(d.text?.length || 0) > 80 ? "..." : ""}" (${d.status}, ${new Date(d.created_at).toLocaleDateString()})`);
|
|
137
|
+
return { content: [{ type: "text", text: `${data.length} ${status || "pending"} drafts:\n\n${lines.join("\n")}` }] };
|
|
138
|
+
});
|
|
139
|
+
// shout_approve_draft — approve, reject, or edit a draft
|
|
140
|
+
server.tool("shout_approve_draft", "approve, reject, or edit a draft post. approved posts are published to the shout page.", {
|
|
141
|
+
draft_id: z.string().uuid().describe("the draft post id"),
|
|
142
|
+
user_id: z.string().uuid().describe("the user's id (for auth)"),
|
|
143
|
+
action: z.enum(["approve", "reject", "edit"]).describe("action to take"),
|
|
144
|
+
edited_text: z.string().max(500).optional().describe("revised text if action is edit"),
|
|
145
|
+
}, async ({ draft_id, user_id, action, edited_text }) => {
|
|
146
|
+
// fetch draft
|
|
147
|
+
const { data: draft, error } = await supabase
|
|
148
|
+
.from("draft_posts")
|
|
149
|
+
.select("*")
|
|
150
|
+
.eq("id", draft_id)
|
|
151
|
+
.eq("user_id", user_id)
|
|
152
|
+
.single();
|
|
153
|
+
if (error || !draft) {
|
|
154
|
+
return { content: [{ type: "text", text: `draft not found or access denied.` }] };
|
|
155
|
+
}
|
|
156
|
+
if (draft.status !== "pending") {
|
|
157
|
+
return { content: [{ type: "text", text: `draft is already ${draft.status}, can't ${action}.` }] };
|
|
158
|
+
}
|
|
159
|
+
if (action === "reject") {
|
|
160
|
+
await supabase.from("draft_posts").update({
|
|
161
|
+
status: "rejected",
|
|
162
|
+
updated_at: new Date().toISOString(),
|
|
163
|
+
}).eq("id", draft_id);
|
|
164
|
+
return { content: [{ type: "text", text: `draft rejected.` }] };
|
|
165
|
+
}
|
|
166
|
+
if (action === "edit") {
|
|
167
|
+
if (!edited_text) {
|
|
168
|
+
return { content: [{ type: "text", text: `edited_text is required for edit action.` }] };
|
|
169
|
+
}
|
|
170
|
+
const regexResult = filterPII(edited_text);
|
|
171
|
+
await supabase.from("draft_posts").update({
|
|
172
|
+
text: regexResult.text,
|
|
173
|
+
filtered_text: regexResult.text,
|
|
174
|
+
filter_report: regexResult.filtered ? `regex: ${regexResult.removals.join(", ")}` : null,
|
|
175
|
+
updated_at: new Date().toISOString(),
|
|
176
|
+
}).eq("id", draft_id);
|
|
177
|
+
return { content: [{ type: "text", text: `draft updated. text: "${regexResult.text}"${regexResult.filtered ? ` (PII filtered: ${regexResult.removals.join(", ")})` : ""}` }] };
|
|
178
|
+
}
|
|
179
|
+
// approve → publish
|
|
180
|
+
const result = await publishDraft(draft_id, user_id, draft.text, draft.tags || [], draft.category, draft.collection_id, draft.visibility);
|
|
181
|
+
if (result.published) {
|
|
182
|
+
return { content: [{ type: "text", text: `published! shout id: ${result.shoutId}\ntext: "${result.text}"` }] };
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
return { content: [{ type: "text", text: `publish blocked by content filter: ${result.reason}` }] };
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
// shout_set_auto_publish — toggle auto-publish
|
|
189
|
+
server.tool("shout_set_auto_publish", "enable or disable auto-publish for agent posts. PII filter always runs regardless.", {
|
|
190
|
+
user_id: z.string().uuid().describe("the user's id"),
|
|
191
|
+
enabled: z.boolean().describe("true to enable auto-publish, false to disable"),
|
|
192
|
+
filter_level: z.enum(["strict", "standard"]).optional().default("strict"),
|
|
193
|
+
}, async ({ user_id, enabled, filter_level }) => {
|
|
194
|
+
const { error } = await supabase
|
|
195
|
+
.from("user_settings")
|
|
196
|
+
.upsert({
|
|
197
|
+
user_id,
|
|
198
|
+
auto_publish: enabled,
|
|
199
|
+
auto_publish_filter_level: filter_level || "strict",
|
|
200
|
+
}, { onConflict: "user_id" });
|
|
201
|
+
if (error) {
|
|
202
|
+
return { content: [{ type: "text", text: `error updating settings: ${error.message}` }] };
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
content: [{
|
|
206
|
+
type: "text",
|
|
207
|
+
text: `auto-publish ${enabled ? "enabled" : "disabled"}. filter level: ${filter_level || "strict"}. PII filter always runs.`,
|
|
208
|
+
}],
|
|
209
|
+
};
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
//# sourceMappingURL=posts.js.map
|