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.
Files changed (110) hide show
  1. package/README.md +82 -0
  2. package/TASK-AGENT-POSTS.md +112 -0
  3. package/assets/shout-default.svg +5 -0
  4. package/bin/shout +68 -0
  5. package/dist/index.d.ts +2 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +29 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/lib/ai.d.ts +13 -0
  10. package/dist/lib/ai.d.ts.map +1 -0
  11. package/dist/lib/ai.js +135 -0
  12. package/dist/lib/ai.js.map +1 -0
  13. package/dist/lib/content-filter.d.ts +74 -0
  14. package/dist/lib/content-filter.d.ts.map +1 -0
  15. package/dist/lib/content-filter.js +188 -0
  16. package/dist/lib/content-filter.js.map +1 -0
  17. package/dist/lib/context-extractor.d.ts +39 -0
  18. package/dist/lib/context-extractor.d.ts.map +1 -0
  19. package/dist/lib/context-extractor.js +170 -0
  20. package/dist/lib/context-extractor.js.map +1 -0
  21. package/dist/lib/match-engine.d.ts +31 -0
  22. package/dist/lib/match-engine.d.ts.map +1 -0
  23. package/dist/lib/match-engine.js +322 -0
  24. package/dist/lib/match-engine.js.map +1 -0
  25. package/dist/lib/metadata.d.ts +7 -0
  26. package/dist/lib/metadata.d.ts.map +1 -0
  27. package/dist/lib/metadata.js +311 -0
  28. package/dist/lib/metadata.js.map +1 -0
  29. package/dist/lib/skills.d.ts +3 -0
  30. package/dist/lib/skills.d.ts.map +1 -0
  31. package/dist/lib/skills.js +20 -0
  32. package/dist/lib/skills.js.map +1 -0
  33. package/dist/lib/supabase.d.ts +2 -0
  34. package/dist/lib/supabase.d.ts.map +1 -0
  35. package/dist/lib/supabase.js +8 -0
  36. package/dist/lib/supabase.js.map +1 -0
  37. package/dist/tools/collections.d.ts +3 -0
  38. package/dist/tools/collections.d.ts.map +1 -0
  39. package/dist/tools/collections.js +142 -0
  40. package/dist/tools/collections.js.map +1 -0
  41. package/dist/tools/intros.d.ts +3 -0
  42. package/dist/tools/intros.d.ts.map +1 -0
  43. package/dist/tools/intros.js +483 -0
  44. package/dist/tools/intros.js.map +1 -0
  45. package/dist/tools/links.d.ts +3 -0
  46. package/dist/tools/links.d.ts.map +1 -0
  47. package/dist/tools/links.js +424 -0
  48. package/dist/tools/links.js.map +1 -0
  49. package/dist/tools/posts.d.ts +3 -0
  50. package/dist/tools/posts.d.ts.map +1 -0
  51. package/dist/tools/posts.js +212 -0
  52. package/dist/tools/posts.js.map +1 -0
  53. package/dist/tools/settings.d.ts +3 -0
  54. package/dist/tools/settings.d.ts.map +1 -0
  55. package/dist/tools/settings.js +45 -0
  56. package/dist/tools/settings.js.map +1 -0
  57. package/dist/tools/shout_agent_curate.d.ts +28 -0
  58. package/dist/tools/shout_agent_curate.d.ts.map +1 -0
  59. package/dist/tools/shout_agent_curate.js +80 -0
  60. package/dist/tools/shout_agent_curate.js.map +1 -0
  61. package/dist/tools/social.d.ts +3 -0
  62. package/dist/tools/social.d.ts.map +1 -0
  63. package/dist/tools/social.js +169 -0
  64. package/dist/tools/social.js.map +1 -0
  65. package/dist/types.d.ts +60 -0
  66. package/dist/types.d.ts.map +1 -0
  67. package/dist/types.js +3 -0
  68. package/dist/types.js.map +1 -0
  69. package/package.json +24 -0
  70. package/quick-test.ts +22 -0
  71. package/regenerate-summaries.ts +111 -0
  72. package/save-jeffries-shout.ts +38 -0
  73. package/save-openai-shout.ts +35 -0
  74. package/save-prcarly.ts +46 -0
  75. package/save-shout.ts +35 -0
  76. package/save-techcrunch-shout.ts +59 -0
  77. package/save-zdnet-shout.ts +36 -0
  78. package/skills/collection-routing/SKILL.md +34 -0
  79. package/skills/link-summary/SKILL.md +53 -0
  80. package/skills/tagging-and-routing/SKILL.md +54 -0
  81. package/src/index.ts +32 -0
  82. package/src/lib/ai.ts +166 -0
  83. package/src/lib/content-filter.ts +258 -0
  84. package/src/lib/metadata.ts +353 -0
  85. package/src/lib/skills.ts +21 -0
  86. package/src/lib/supabase.ts +12 -0
  87. package/src/tools/collections.ts +182 -0
  88. package/src/tools/links.ts +524 -0
  89. package/src/tools/posts.ts +264 -0
  90. package/src/tools/settings.ts +55 -0
  91. package/src/tools/shout_agent_curate.ts +95 -0
  92. package/src/tools/social.ts +206 -0
  93. package/src/types.ts +66 -0
  94. package/supabase/.temp/cli-latest +1 -0
  95. package/supabase/.temp/gotrue-version +1 -0
  96. package/supabase/.temp/pooler-url +1 -0
  97. package/supabase/.temp/postgres-version +1 -0
  98. package/supabase/.temp/project-ref +1 -0
  99. package/supabase/.temp/rest-version +1 -0
  100. package/supabase/.temp/storage-migration +1 -0
  101. package/supabase/.temp/storage-version +1 -0
  102. package/supabase/migrations/001_initial_schema.sql +147 -0
  103. package/supabase/migrations/20260317010000_decouple_profiles_from_auth.sql +9 -0
  104. package/supabase/migrations/20260317020000_agent_curation.sql +10 -0
  105. package/supabase/migrations/20260320000000_agent_posts.sql +41 -0
  106. package/supabase/migrations/20260320120000_fix_draft_fk.sql +2 -0
  107. package/supabase/migrations/20260320130000_fix_identity.sql +17 -0
  108. package/supabase/migrations/20260320170000_intros.sql +118 -0
  109. package/test-model-comparison.ts +89 -0
  110. 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,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerPostTools(server: McpServer): void;
3
+ //# sourceMappingURL=posts.d.ts.map
@@ -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