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 @@
1
+ {"version":3,"file":"posts.js","sourceRoot":"","sources":["../../src/tools/posts.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 registerSettingsTools(server: McpServer): void;
3
+ //# sourceMappingURL=settings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../src/tools/settings.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAMzE,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,QA+CtD"}
@@ -0,0 +1,45 @@
1
+ import { z } from "zod";
2
+ // in-memory settings store for v1
3
+ // TODO: persist to supabase user_settings table
4
+ const userSettings = new Map();
5
+ export function registerSettingsTools(server) {
6
+ // shout_settings - configure behavior
7
+ server.tool("shout_settings", "configure your shout behavior preferences.", {
8
+ user_id: z.string().uuid().describe("the user's id"),
9
+ auto_detect: z
10
+ .boolean()
11
+ .optional()
12
+ .describe("auto-detect links in conversation"),
13
+ default_visibility: z
14
+ .enum(["public", "private", "unlisted"])
15
+ .optional()
16
+ .describe("default visibility for new shouts"),
17
+ digest_frequency: z
18
+ .enum(["daily", "weekly", "monthly", "never"])
19
+ .optional()
20
+ .describe("how often to generate digests"),
21
+ }, async ({ user_id, auto_detect, default_visibility, digest_frequency }) => {
22
+ const current = userSettings.get(user_id) || {
23
+ auto_detect: true,
24
+ default_visibility: "public",
25
+ digest_frequency: "weekly",
26
+ };
27
+ if (auto_detect !== undefined)
28
+ current.auto_detect = auto_detect;
29
+ if (default_visibility !== undefined)
30
+ current.default_visibility = default_visibility;
31
+ if (digest_frequency !== undefined)
32
+ current.digest_frequency = digest_frequency;
33
+ userSettings.set(user_id, current);
34
+ return {
35
+ content: [
36
+ {
37
+ type: "text",
38
+ text: `settings updated:\n- auto_detect: ${current.auto_detect}\n- default_visibility: ${current.default_visibility}\n- digest_frequency: ${current.digest_frequency}`,
39
+ },
40
+ ],
41
+ };
42
+ });
43
+ // shout_generate_digest is registered in links.ts
44
+ }
45
+ //# sourceMappingURL=settings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings.js","sourceRoot":"","sources":["../../src/tools/settings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,kCAAkC;AAClC,gDAAgD;AAChD,MAAM,YAAY,GAAyC,IAAI,GAAG,EAAE,CAAC;AAErE,MAAM,UAAU,qBAAqB,CAAC,MAAiB;IACrD,sCAAsC;IACtC,MAAM,CAAC,IAAI,CACT,gBAAgB,EAChB,4CAA4C,EAC5C;QACE,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,eAAe,CAAC;QACpD,WAAW,EAAE,CAAC;aACX,OAAO,EAAE;aACT,QAAQ,EAAE;aACV,QAAQ,CAAC,mCAAmC,CAAC;QAChD,kBAAkB,EAAE,CAAC;aAClB,IAAI,CAAC,CAAC,QAAQ,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC;aACvC,QAAQ,EAAE;aACV,QAAQ,CAAC,mCAAmC,CAAC;QAChD,gBAAgB,EAAE,CAAC;aAChB,IAAI,CAAC,CAAC,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;aAC7C,QAAQ,EAAE;aACV,QAAQ,CAAC,+BAA+B,CAAC;KAC7C,EACD,KAAK,EAAE,EAAE,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,EAAE,EAAE;QACvE,MAAM,OAAO,GAAG,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI;YAC3C,WAAW,EAAE,IAAI;YACjB,kBAAkB,EAAE,QAAQ;YAC5B,gBAAgB,EAAE,QAAQ;SAC3B,CAAC;QAEF,IAAI,WAAW,KAAK,SAAS;YAAE,OAAO,CAAC,WAAW,GAAG,WAAW,CAAC;QACjE,IAAI,kBAAkB,KAAK,SAAS;YAClC,OAAO,CAAC,kBAAkB,GAAG,kBAAkB,CAAC;QAClD,IAAI,gBAAgB,KAAK,SAAS;YAChC,OAAO,CAAC,gBAAgB,GAAG,gBAAgB,CAAC;QAE9C,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAEnC,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAe;oBACrB,IAAI,EAAE,qCAAqC,OAAO,CAAC,WAAW,2BAA2B,OAAO,CAAC,kBAAkB,yBAAyB,OAAO,CAAC,gBAAgB,EAAE;iBACvK;aACF;SACF,CAAC;IACJ,CAAC,CACF,CAAC;IAEF,kDAAkD;AACpD,CAAC"}
@@ -0,0 +1,28 @@
1
+ import { z } from "zod";
2
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ export declare const shoutAgentCurateSchema: z.ZodObject<{
4
+ url: z.ZodString;
5
+ agent_take: z.ZodOptional<z.ZodString>;
6
+ tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
7
+ collection_id: z.ZodOptional<z.ZodString>;
8
+ user_id: z.ZodString;
9
+ }, "strip", z.ZodTypeAny, {
10
+ url: string;
11
+ user_id: string;
12
+ agent_take?: string | undefined;
13
+ tags?: string[] | undefined;
14
+ collection_id?: string | undefined;
15
+ }, {
16
+ url: string;
17
+ user_id: string;
18
+ agent_take?: string | undefined;
19
+ tags?: string[] | undefined;
20
+ collection_id?: string | undefined;
21
+ }>;
22
+ export type ShoutAgentCurateInput = z.infer<typeof shoutAgentCurateSchema>;
23
+ export declare function shoutAgentCurate(input: ShoutAgentCurateInput): Promise<{
24
+ message: string;
25
+ shout: any;
26
+ }>;
27
+ export declare function registerAgentCurateTools(server: McpServer): void;
28
+ //# sourceMappingURL=shout_agent_curate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shout_agent_curate.d.ts","sourceRoot":"","sources":["../../src/tools/shout_agent_curate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;EAgBjC,CAAC;AAEH,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAE3E,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,qBAAqB;;;GAwDlE;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,SAAS,QAUzD"}
@@ -0,0 +1,80 @@
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
+ export const shoutAgentCurateSchema = z.object({
6
+ url: z.string().url().describe("The URL to save"),
7
+ agent_take: z
8
+ .string()
9
+ .optional()
10
+ .describe("The agent's own commentary or opinion on why this link is interesting"),
11
+ tags: z
12
+ .array(z.string())
13
+ .optional()
14
+ .describe("Override auto-generated tags with these tags"),
15
+ collection_id: z
16
+ .string()
17
+ .uuid()
18
+ .optional()
19
+ .describe("Put the shout in a specific collection by ID"),
20
+ user_id: z.string().uuid().describe("The user's profile ID (fubz)"),
21
+ });
22
+ export async function shoutAgentCurate(input) {
23
+ // 1. Fetch metadata
24
+ const meta = await extractMetadata(input.url);
25
+ // 2. AI summary + tags + category (all returned from generateSummary)
26
+ const aiResult = await generateSummary({
27
+ url: input.url,
28
+ title: meta.title,
29
+ description: meta.description,
30
+ bodyText: meta.bodyText,
31
+ userContext: input.agent_take ?? null,
32
+ });
33
+ const tags = input.tags ?? aiResult.tags;
34
+ const category = aiResult.category;
35
+ const summary = aiResult.summary;
36
+ // 3. PII filter before saving
37
+ const { filterShoutContent } = await import("../lib/content-filter.js");
38
+ const filtered = filterShoutContent({
39
+ take: input.agent_take ?? null,
40
+ summary,
41
+ title: meta.title,
42
+ description: meta.description,
43
+ });
44
+ if (filtered.filterReport) {
45
+ console.log(`[nod-shout] agent curate filter: ${filtered.filterReport}`);
46
+ }
47
+ // 4. Insert shout with source "agent_curated"
48
+ const { data, error } = await supabase
49
+ .from("shouts")
50
+ .insert({
51
+ user_id: input.user_id,
52
+ url: input.url,
53
+ title: filtered.title,
54
+ description: filtered.description,
55
+ summary: filtered.summary,
56
+ user_take: filtered.take,
57
+ image_url: meta.image_url,
58
+ tags,
59
+ category,
60
+ collection_id: input.collection_id ?? null,
61
+ source: "agent_curated",
62
+ agent_context: input.agent_take ? `Agent curated: ${input.agent_take}` : "Agent curated link",
63
+ visibility: "public",
64
+ })
65
+ .select()
66
+ .single();
67
+ if (error)
68
+ throw new Error(`Failed to save agent-curated shout: ${error.message}`);
69
+ return {
70
+ message: `Agent curated! "${meta.title}" saved.`,
71
+ shout: data,
72
+ };
73
+ }
74
+ export function registerAgentCurateTools(server) {
75
+ server.tool("shout_agent_curate", "Save a link to the agent's shout page. Use this when you find an interesting link worth sharing — from conversations, research, or browsing. The agent curates its own page.", shoutAgentCurateSchema.shape, async (params) => {
76
+ const result = await shoutAgentCurate(params);
77
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
78
+ });
79
+ }
80
+ //# sourceMappingURL=shout_agent_curate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shout_agent_curate.js","sourceRoot":"","sources":["../../src/tools/shout_agent_curate.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 registerSocialTools(server: McpServer): void;
3
+ //# sourceMappingURL=social.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"social.d.ts","sourceRoot":"","sources":["../../src/tools/social.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,QAyMpD"}
@@ -0,0 +1,169 @@
1
+ import { z } from "zod";
2
+ import { supabase } from "../lib/supabase.js";
3
+ export function registerSocialTools(server) {
4
+ // shout_follow - subscribe to a user's shouts
5
+ server.tool("shout_follow", "follow another user's shouts. optionally follow a specific collection.", {
6
+ user_id: z.string().uuid().describe("your user id"),
7
+ username: z.string().describe("username to follow"),
8
+ collection: z
9
+ .string()
10
+ .optional()
11
+ .describe("specific collection slug to follow"),
12
+ }, async ({ user_id, username, collection }) => {
13
+ // resolve username to user id
14
+ const { data: profile } = await supabase
15
+ .from("profiles")
16
+ .select("id")
17
+ .eq("username", username)
18
+ .single();
19
+ if (!profile) {
20
+ return {
21
+ content: [
22
+ { type: "text", text: `user "${username}" not found.` },
23
+ ],
24
+ };
25
+ }
26
+ // resolve collection if provided
27
+ let collection_id = null;
28
+ if (collection) {
29
+ const { data: col } = await supabase
30
+ .from("collections")
31
+ .select("id")
32
+ .eq("user_id", profile.id)
33
+ .eq("slug", collection)
34
+ .single();
35
+ collection_id = col?.id || null;
36
+ }
37
+ // check if already following
38
+ let existingQuery = supabase
39
+ .from("subscriptions")
40
+ .select("id")
41
+ .eq("follower_id", user_id)
42
+ .eq("following_id", profile.id);
43
+ if (collection_id) {
44
+ existingQuery = existingQuery.eq("collection_id", collection_id);
45
+ }
46
+ else {
47
+ existingQuery = existingQuery.is("collection_id", null);
48
+ }
49
+ const { data: existing } = await existingQuery.single();
50
+ if (existing) {
51
+ return {
52
+ content: [
53
+ { type: "text", text: `you're already following ${username}${collection ? ` (${collection})` : ""}.` },
54
+ ],
55
+ };
56
+ }
57
+ const { error } = await supabase.from("subscriptions").insert({
58
+ follower_id: user_id,
59
+ following_id: profile.id,
60
+ collection_id,
61
+ });
62
+ if (error) {
63
+ return {
64
+ content: [
65
+ { type: "text", text: `error following: ${error.message}` },
66
+ ],
67
+ };
68
+ }
69
+ return {
70
+ content: [
71
+ {
72
+ type: "text",
73
+ text: `now following ${username}${collection ? ` (${collection})` : ""}!`,
74
+ },
75
+ ],
76
+ };
77
+ });
78
+ // shout_unfollow
79
+ server.tool("shout_unfollow", "unfollow a user's shouts.", {
80
+ user_id: z.string().uuid().describe("your user id"),
81
+ username: z.string().describe("username to unfollow"),
82
+ }, async ({ user_id, username }) => {
83
+ const { data: profile } = await supabase
84
+ .from("profiles")
85
+ .select("id")
86
+ .eq("username", username)
87
+ .single();
88
+ if (!profile) {
89
+ return {
90
+ content: [
91
+ { type: "text", text: `user "${username}" not found.` },
92
+ ],
93
+ };
94
+ }
95
+ const { error } = await supabase
96
+ .from("subscriptions")
97
+ .delete()
98
+ .eq("follower_id", user_id)
99
+ .eq("following_id", profile.id);
100
+ if (error) {
101
+ return {
102
+ content: [
103
+ { type: "text", text: `error unfollowing: ${error.message}` },
104
+ ],
105
+ };
106
+ }
107
+ return {
108
+ content: [
109
+ { type: "text", text: `unfollowed ${username}.` },
110
+ ],
111
+ };
112
+ });
113
+ // shout_feed - aggregated feed from followed users
114
+ server.tool("shout_feed", "get an aggregated feed of shouts from users you follow.", {
115
+ user_id: z.string().uuid().describe("your user id"),
116
+ limit: z.number().optional().default(20).describe("max results"),
117
+ }, async ({ user_id, limit }) => {
118
+ // get list of followed user ids
119
+ const { data: subs } = await supabase
120
+ .from("subscriptions")
121
+ .select("following_id, collection_id")
122
+ .eq("follower_id", user_id);
123
+ if (!subs || subs.length === 0) {
124
+ return {
125
+ content: [
126
+ {
127
+ type: "text",
128
+ text: "you're not following anyone yet. use shout_follow to subscribe to someone's shouts.",
129
+ },
130
+ ],
131
+ };
132
+ }
133
+ const followingIds = subs.map((s) => s.following_id);
134
+ const { data, error } = await supabase
135
+ .from("shouts")
136
+ .select("*, profiles!inner(username, display_name)")
137
+ .in("user_id", followingIds)
138
+ .eq("visibility", "public")
139
+ .order("created_at", { ascending: false })
140
+ .limit(limit || 20);
141
+ if (error) {
142
+ return {
143
+ content: [
144
+ { type: "text", text: `error fetching feed: ${error.message}` },
145
+ ],
146
+ };
147
+ }
148
+ if (!data || data.length === 0) {
149
+ return {
150
+ content: [
151
+ { type: "text", text: "no shouts in your feed yet." },
152
+ ],
153
+ };
154
+ }
155
+ const lines = data.map((s, i) => {
156
+ const who = s.profiles?.username || "unknown";
157
+ return `${i + 1}. @${who}: ${s.title || s.url}\n ${s.summary || ""}\n tags: ${(s.tags || []).join(", ")} | ${s.created_at}`;
158
+ });
159
+ return {
160
+ content: [
161
+ {
162
+ type: "text",
163
+ text: `feed (${data.length} shouts):\n\n${lines.join("\n\n")}`,
164
+ },
165
+ ],
166
+ };
167
+ });
168
+ }
169
+ //# sourceMappingURL=social.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"social.js","sourceRoot":"","sources":["../../src/tools/social.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ export interface Profile {
2
+ id: string;
3
+ username: string;
4
+ display_name: string | null;
5
+ bio: string | null;
6
+ avatar_url: string | null;
7
+ created_at: string;
8
+ updated_at: string;
9
+ }
10
+ export interface Shout {
11
+ id: string;
12
+ user_id: string;
13
+ url: string;
14
+ title: string | null;
15
+ description: string | null;
16
+ summary: string | null;
17
+ user_take: string | null;
18
+ image_url: string | null;
19
+ tags: string[] | null;
20
+ category: string | null;
21
+ collection_id: string | null;
22
+ source: string;
23
+ agent_context: string | null;
24
+ visibility: "public" | "private" | "unlisted";
25
+ created_at: string;
26
+ updated_at: string;
27
+ }
28
+ export interface Collection {
29
+ id: string;
30
+ user_id: string;
31
+ name: string;
32
+ description: string | null;
33
+ slug: string;
34
+ visibility: "public" | "private" | "unlisted";
35
+ auto_rules: Record<string, unknown> | null;
36
+ created_at: string;
37
+ updated_at: string;
38
+ }
39
+ export interface Subscription {
40
+ id: string;
41
+ follower_id: string;
42
+ following_id: string;
43
+ collection_id: string | null;
44
+ notify: boolean;
45
+ created_at: string;
46
+ }
47
+ export interface PageMetadata {
48
+ title: string | null;
49
+ description: string | null;
50
+ image_url: string | null;
51
+ author: string | null;
52
+ date: string | null;
53
+ bodyText: string | null;
54
+ }
55
+ export interface AISummaryResult {
56
+ summary: string;
57
+ tags: string[];
58
+ category: string;
59
+ }
60
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IACtB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,UAAU,EAAE,QAAQ,GAAG,SAAS,GAAG,UAAU,CAAC;IAC9C,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,QAAQ,GAAG,SAAS,GAAG,UAAU,CAAC;IAC9C,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB"}
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ // shared types for nod-shout mcp server
2
+ export {};
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,wCAAwC"}
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "nod-shout",
3
+ "version": "0.1.0",
4
+ "description": "mcp server for nod social - turn links into curated public pages",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "start": "node dist/index.js",
10
+ "dev": "tsx src/index.ts",
11
+ "lint": "tsc --noEmit"
12
+ },
13
+ "dependencies": {
14
+ "@modelcontextprotocol/sdk": "^1.12.1",
15
+ "@supabase/supabase-js": "^2.49.1",
16
+ "cheerio": "^1.0.0",
17
+ "zod": "^3.24.2"
18
+ },
19
+ "devDependencies": {
20
+ "@types/node": "^22.13.10",
21
+ "tsx": "^4.19.3",
22
+ "typescript": "^5.8.2"
23
+ }
24
+ }
package/quick-test.ts ADDED
@@ -0,0 +1,22 @@
1
+ import { readFileSync } from 'fs';
2
+ const env = Object.fromEntries(
3
+ readFileSync('.env', 'utf8').split('\n').filter(l => l.includes('=')).map(l => {
4
+ const [k, ...v] = l.split('=');
5
+ return [k.trim(), v.join('=').trim()];
6
+ })
7
+ );
8
+
9
+ async function main() {
10
+ const res = await fetch("https://api.openai.com/v1/chat/completions", {
11
+ method: "POST",
12
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${env.OPENAI_API_KEY}` },
13
+ body: JSON.stringify({
14
+ model: "gpt-5-mini",
15
+ messages: [{ role: "user", content: 'Summarize in 1 sentence: Apple skipped AI data center spending. Respond in JSON: {"summary": "..."}' }],
16
+ max_completion_tokens: 200,
17
+ }),
18
+ });
19
+ const data = await res.json();
20
+ console.log(JSON.stringify(data, null, 2));
21
+ }
22
+ main();
@@ -0,0 +1,111 @@
1
+ import { createClient } from "@supabase/supabase-js";
2
+ import { readFileSync } from "fs";
3
+
4
+ const env = Object.fromEntries(
5
+ readFileSync(".env", "utf8").split("\n").filter(l => l.includes("=")).map(l => {
6
+ const [k, ...v] = l.split("=");
7
+ return [k.trim(), v.join("=").trim()];
8
+ })
9
+ );
10
+
11
+ const supabase = createClient(env.SUPABASE_URL, env.SUPABASE_SERVICE_ROLE_KEY);
12
+ const OPENAI_API_KEY = env.OPENAI_API_KEY;
13
+
14
+ async function regenerate() {
15
+ // get fubz's profile
16
+ const { data: profile } = await supabase
17
+ .from("profiles")
18
+ .select("id")
19
+ .eq("username", "fubz")
20
+ .single();
21
+
22
+ if (!profile) { console.log("no profile found"); return; }
23
+
24
+ const { data: shouts } = await supabase
25
+ .from("shouts")
26
+ .select("id, url, title, summary")
27
+ .eq("user_id", profile.id)
28
+ .order("created_at", { ascending: false });
29
+
30
+ if (!shouts || shouts.length === 0) { console.log("no shouts"); return; }
31
+
32
+ console.log(`regenerating ${shouts.length} summaries...\n`);
33
+
34
+ for (const shout of shouts) {
35
+ // fetch page content
36
+ let bodyText = "";
37
+ try {
38
+ const pageRes = await fetch(shout.url, {
39
+ headers: { "User-Agent": "Mozilla/5.0 (compatible; NodShout/0.1)" },
40
+ signal: AbortSignal.timeout(10000),
41
+ });
42
+ if (pageRes.ok) {
43
+ const html = await pageRes.text();
44
+ // rough text extraction - strip tags
45
+ bodyText = html
46
+ .replace(/<script[\s\S]*?<\/script>/gi, "")
47
+ .replace(/<style[\s\S]*?<\/style>/gi, "")
48
+ .replace(/<[^>]+>/g, " ")
49
+ .replace(/\s+/g, " ")
50
+ .trim()
51
+ .slice(0, 1500);
52
+ }
53
+ } catch { /* ignore fetch errors */ }
54
+
55
+ const prompt = `Write a nod shout card summary.
56
+
57
+ Rules:
58
+ - 2 short sentences max
59
+ - sentence 1: what the thing is or what happened
60
+ - sentence 2: a reason to click or a key detail
61
+ - include concrete specifics when available (names, numbers, claims)
62
+ - write like you're texting a friend, not writing a blurb
63
+ - never start with: "this article discusses", "this post is about", "the page explains", "X tweeted that", "post by", "page", "clicking gives", "clicking provides", "clicking reveals"
64
+ - never use: "not just X but Y", "isn't just X"
65
+ - no filler like "thought-provoking", "must-read", "fascinating", "insightful"
66
+
67
+ url: ${shout.url}
68
+ title: ${shout.title || "unknown"}
69
+ ${bodyText ? `page content: ${bodyText}` : ""}
70
+
71
+ respond in json only, no markdown:
72
+ {"summary": "..."}`;
73
+
74
+ try {
75
+ const res = await fetch("https://api.openai.com/v1/chat/completions", {
76
+ method: "POST",
77
+ headers: {
78
+ "Content-Type": "application/json",
79
+ Authorization: `Bearer ${OPENAI_API_KEY}`,
80
+ },
81
+ body: JSON.stringify({
82
+ model: "gpt-4.1-mini",
83
+ messages: [{ role: "user", content: prompt }],
84
+ temperature: 0.7,
85
+ max_tokens: 250,
86
+ response_format: { type: "json_object" },
87
+ }),
88
+ });
89
+
90
+ const data = await res.json();
91
+ const content = data.choices?.[0]?.message?.content;
92
+ const parsed = JSON.parse(content);
93
+ const newSummary = parsed.summary;
94
+
95
+ await supabase
96
+ .from("shouts")
97
+ .update({ summary: newSummary })
98
+ .eq("id", shout.id);
99
+
100
+ console.log(`✓ ${shout.title?.slice(0, 60) || shout.url}`);
101
+ console.log(` OLD: ${shout.summary?.slice(0, 100)}`);
102
+ console.log(` NEW: ${newSummary?.slice(0, 100)}`);
103
+ console.log();
104
+ } catch (err) {
105
+ console.log(`✗ ${shout.title?.slice(0, 60)} — ${err}`);
106
+ }
107
+ }
108
+ console.log("done!");
109
+ }
110
+
111
+ regenerate();