nod-shout 0.3.0 → 0.3.1

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.
@@ -1 +1 @@
1
- {"version":3,"file":"social.d.ts","sourceRoot":"","sources":["../../src/tools/social.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,QA4MpD"}
1
+ {"version":3,"file":"social.d.ts","sourceRoot":"","sources":["../../src/tools/social.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,QAwWpD"}
@@ -169,5 +169,136 @@ export function registerSocialTools(server) {
169
169
  ],
170
170
  };
171
171
  });
172
+ // shout_curate_feed - ai-curated feed based on your interests
173
+ server.tool("shout_curate_feed", "get a curated feed from people you follow, ranked by relevance to your interests. compares your shout history (tags, topics) against your follows' recent shouts and returns the best matches with relevance scores. use this when the user asks 'what's good from my follows?' or 'curate my feed'.", {
174
+ user_id: z.string().uuid().optional().describe("your user id (auto-resolved if omitted)"),
175
+ limit: z.number().optional().default(10).describe("max curated results to return"),
176
+ days: z.number().optional().default(7).describe("how many days back to look"),
177
+ }, async ({ user_id: rawUserId4, limit, days }) => {
178
+ const user_id = rawUserId4 || await resolveUserId();
179
+ // 1. build interest profile from user's own shouts
180
+ const { data: myShouts } = await supabase
181
+ .from("shouts")
182
+ .select("tags, category")
183
+ .eq("user_id", user_id)
184
+ .order("created_at", { ascending: false })
185
+ .limit(50);
186
+ const tagCounts = {};
187
+ const catCounts = {};
188
+ for (const s of myShouts || []) {
189
+ for (const tag of (s.tags || [])) {
190
+ tagCounts[tag] = (tagCounts[tag] || 0) + 1;
191
+ }
192
+ if (s.category) {
193
+ catCounts[s.category] = (catCounts[s.category] || 0) + 1;
194
+ }
195
+ }
196
+ const totalTags = Object.values(tagCounts).reduce((a, b) => a + b, 0) || 1;
197
+ const tagWeights = {};
198
+ for (const [tag, count] of Object.entries(tagCounts)) {
199
+ tagWeights[tag] = count / totalTags;
200
+ }
201
+ // 2. get followed users
202
+ const { data: subs } = await supabase
203
+ .from("subscriptions")
204
+ .select("following_id")
205
+ .eq("follower_id", user_id);
206
+ if (!subs || subs.length === 0) {
207
+ return {
208
+ content: [{
209
+ type: "text",
210
+ text: "you're not following anyone yet. use shout_follow to subscribe to someone's shouts.",
211
+ }],
212
+ };
213
+ }
214
+ const followingIds = subs.map((s) => s.following_id);
215
+ // 3. fetch recent shouts from follows
216
+ const since = new Date();
217
+ since.setDate(since.getDate() - (days || 7));
218
+ const { data: feedShouts, error } = await supabase
219
+ .from("shouts")
220
+ .select("*, profiles!inner(username, display_name)")
221
+ .in("user_id", followingIds)
222
+ .eq("visibility", "public")
223
+ .gte("created_at", since.toISOString())
224
+ .order("created_at", { ascending: false })
225
+ .limit(100);
226
+ if (error || !feedShouts || feedShouts.length === 0) {
227
+ return {
228
+ content: [{
229
+ type: "text",
230
+ text: error ? `error: ${error.message}` : "no recent shouts from your follows.",
231
+ }],
232
+ };
233
+ }
234
+ // 4. score each shout by relevance
235
+ const scored = feedShouts.map((s) => {
236
+ let score = 0;
237
+ const shoutTags = s.tags || [];
238
+ const matchedTags = [];
239
+ // tag overlap scoring
240
+ for (const tag of shoutTags) {
241
+ const normalTag = tag.toLowerCase().replace(/[^a-z0-9-]/g, "");
242
+ // direct match
243
+ if (tagWeights[tag]) {
244
+ score += tagWeights[tag] * 10;
245
+ matchedTags.push(tag);
246
+ }
247
+ // partial match (e.g. "ai-agents" matches "ai")
248
+ for (const [myTag, weight] of Object.entries(tagWeights)) {
249
+ if (normalTag.includes(myTag) || myTag.includes(normalTag)) {
250
+ score += weight * 5;
251
+ if (!matchedTags.includes(myTag))
252
+ matchedTags.push(myTag);
253
+ }
254
+ }
255
+ }
256
+ // category match bonus
257
+ if (s.category && catCounts[s.category]) {
258
+ score += 2;
259
+ }
260
+ // recency bonus (newer = higher)
261
+ const ageHours = (Date.now() - new Date(s.created_at).getTime()) / (1000 * 60 * 60);
262
+ score += Math.max(0, 5 - (ageHours / 24)); // up to 5 bonus points for very recent
263
+ return {
264
+ ...s,
265
+ relevanceScore: Math.round(score * 100) / 100,
266
+ matchedInterests: matchedTags,
267
+ };
268
+ });
269
+ // 5. sort by score, take top N
270
+ scored.sort((a, b) => b.relevanceScore - a.relevanceScore);
271
+ const topItems = scored.slice(0, limit || 10);
272
+ if (topItems.every((s) => s.relevanceScore === 0)) {
273
+ // no tag overlap — just return most recent
274
+ const lines = topItems.map((s, i) => {
275
+ const who = s.profiles?.username || "unknown";
276
+ return `${i + 1}. @${who}: ${s.title || s.url}\n ${s.summary || ""}\n tags: ${(s.tags || []).join(", ")}`;
277
+ });
278
+ return {
279
+ content: [{
280
+ type: "text",
281
+ text: `no strong interest matches yet (you and your follows may cover different topics). here are the most recent:\n\n${lines.join("\n\n")}`,
282
+ }],
283
+ };
284
+ }
285
+ const lines = topItems.map((s, i) => {
286
+ const who = s.profiles?.username || "unknown";
287
+ const matchNote = s.matchedInterests.length > 0
288
+ ? `(matches your interests: ${s.matchedInterests.join(", ")})`
289
+ : "";
290
+ return `${i + 1}. [score: ${s.relevanceScore}] @${who}: ${s.title || s.url}\n ${s.summary || ""}\n tags: ${(s.tags || []).join(", ")} ${matchNote}`;
291
+ });
292
+ const myTopTags = Object.entries(tagCounts)
293
+ .sort((a, b) => b[1] - a[1])
294
+ .slice(0, 5)
295
+ .map(([t]) => t);
296
+ return {
297
+ content: [{
298
+ type: "text",
299
+ text: `curated feed (${topItems.length} picks from ${feedShouts.length} total, based on your interests: ${myTopTags.join(", ")}):\n\n${lines.join("\n\n")}`,
300
+ }],
301
+ };
302
+ });
172
303
  }
173
304
  //# sourceMappingURL=social.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"social.js","sourceRoot":"","sources":["../../src/tools/social.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"social.js","sourceRoot":"","sources":["../../src/tools/social.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nod-shout",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "mcp server for nod social - turn links into curated public pages",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -207,4 +207,160 @@ export function registerSocialTools(server: McpServer) {
207
207
  };
208
208
  }
209
209
  );
210
+
211
+ // shout_curate_feed - ai-curated feed based on your interests
212
+ server.tool(
213
+ "shout_curate_feed",
214
+ "get a curated feed from people you follow, ranked by relevance to your interests. compares your shout history (tags, topics) against your follows' recent shouts and returns the best matches with relevance scores. use this when the user asks 'what's good from my follows?' or 'curate my feed'.",
215
+ {
216
+ user_id: z.string().uuid().optional().describe("your user id (auto-resolved if omitted)"),
217
+ limit: z.number().optional().default(10).describe("max curated results to return"),
218
+ days: z.number().optional().default(7).describe("how many days back to look"),
219
+ },
220
+ async ({ user_id: rawUserId4, limit, days }) => {
221
+ const user_id = rawUserId4 || await resolveUserId();
222
+
223
+ // 1. build interest profile from user's own shouts
224
+ const { data: myShouts } = await supabase
225
+ .from("shouts")
226
+ .select("tags, category")
227
+ .eq("user_id", user_id)
228
+ .order("created_at", { ascending: false })
229
+ .limit(50);
230
+
231
+ const tagCounts: Record<string, number> = {};
232
+ const catCounts: Record<string, number> = {};
233
+ for (const s of myShouts || []) {
234
+ for (const tag of (s.tags || [])) {
235
+ tagCounts[tag] = (tagCounts[tag] || 0) + 1;
236
+ }
237
+ if (s.category) {
238
+ catCounts[s.category] = (catCounts[s.category] || 0) + 1;
239
+ }
240
+ }
241
+
242
+ const totalTags = Object.values(tagCounts).reduce((a, b) => a + b, 0) || 1;
243
+ const tagWeights: Record<string, number> = {};
244
+ for (const [tag, count] of Object.entries(tagCounts)) {
245
+ tagWeights[tag] = count / totalTags;
246
+ }
247
+
248
+ // 2. get followed users
249
+ const { data: subs } = await supabase
250
+ .from("subscriptions")
251
+ .select("following_id")
252
+ .eq("follower_id", user_id);
253
+
254
+ if (!subs || subs.length === 0) {
255
+ return {
256
+ content: [{
257
+ type: "text" as const,
258
+ text: "you're not following anyone yet. use shout_follow to subscribe to someone's shouts.",
259
+ }],
260
+ };
261
+ }
262
+
263
+ const followingIds = subs.map((s: any) => s.following_id);
264
+
265
+ // 3. fetch recent shouts from follows
266
+ const since = new Date();
267
+ since.setDate(since.getDate() - (days || 7));
268
+
269
+ const { data: feedShouts, error } = await supabase
270
+ .from("shouts")
271
+ .select("*, profiles!inner(username, display_name)")
272
+ .in("user_id", followingIds)
273
+ .eq("visibility", "public")
274
+ .gte("created_at", since.toISOString())
275
+ .order("created_at", { ascending: false })
276
+ .limit(100);
277
+
278
+ if (error || !feedShouts || feedShouts.length === 0) {
279
+ return {
280
+ content: [{
281
+ type: "text" as const,
282
+ text: error ? `error: ${error.message}` : "no recent shouts from your follows.",
283
+ }],
284
+ };
285
+ }
286
+
287
+ // 4. score each shout by relevance
288
+ const scored = feedShouts.map((s: any) => {
289
+ let score = 0;
290
+ const shoutTags = s.tags || [];
291
+ const matchedTags: string[] = [];
292
+
293
+ // tag overlap scoring
294
+ for (const tag of shoutTags) {
295
+ const normalTag = tag.toLowerCase().replace(/[^a-z0-9-]/g, "");
296
+ // direct match
297
+ if (tagWeights[tag]) {
298
+ score += tagWeights[tag] * 10;
299
+ matchedTags.push(tag);
300
+ }
301
+ // partial match (e.g. "ai-agents" matches "ai")
302
+ for (const [myTag, weight] of Object.entries(tagWeights)) {
303
+ if (normalTag.includes(myTag) || myTag.includes(normalTag)) {
304
+ score += weight * 5;
305
+ if (!matchedTags.includes(myTag)) matchedTags.push(myTag);
306
+ }
307
+ }
308
+ }
309
+
310
+ // category match bonus
311
+ if (s.category && catCounts[s.category]) {
312
+ score += 2;
313
+ }
314
+
315
+ // recency bonus (newer = higher)
316
+ const ageHours = (Date.now() - new Date(s.created_at).getTime()) / (1000 * 60 * 60);
317
+ score += Math.max(0, 5 - (ageHours / 24)); // up to 5 bonus points for very recent
318
+
319
+ return {
320
+ ...s,
321
+ relevanceScore: Math.round(score * 100) / 100,
322
+ matchedInterests: matchedTags,
323
+ };
324
+ });
325
+
326
+ // 5. sort by score, take top N
327
+ scored.sort((a: any, b: any) => b.relevanceScore - a.relevanceScore);
328
+ const topItems = scored.slice(0, limit || 10);
329
+
330
+ if (topItems.every((s: any) => s.relevanceScore === 0)) {
331
+ // no tag overlap — just return most recent
332
+ const lines = topItems.map((s: any, i: number) => {
333
+ const who = s.profiles?.username || "unknown";
334
+ return `${i + 1}. @${who}: ${s.title || s.url}\n ${s.summary || ""}\n tags: ${(s.tags || []).join(", ")}`;
335
+ });
336
+
337
+ return {
338
+ content: [{
339
+ type: "text" as const,
340
+ text: `no strong interest matches yet (you and your follows may cover different topics). here are the most recent:\n\n${lines.join("\n\n")}`,
341
+ }],
342
+ };
343
+ }
344
+
345
+ const lines = topItems.map((s: any, i: number) => {
346
+ const who = s.profiles?.username || "unknown";
347
+ const matchNote = s.matchedInterests.length > 0
348
+ ? `(matches your interests: ${s.matchedInterests.join(", ")})`
349
+ : "";
350
+ return `${i + 1}. [score: ${s.relevanceScore}] @${who}: ${s.title || s.url}\n ${s.summary || ""}\n tags: ${(s.tags || []).join(", ")} ${matchNote}`;
351
+ });
352
+
353
+ const myTopTags = Object.entries(tagCounts)
354
+ .sort((a, b) => b[1] - a[1])
355
+ .slice(0, 5)
356
+ .map(([t]) => t);
357
+
358
+ return {
359
+ content: [{
360
+ type: "text" as const,
361
+ text: `curated feed (${topItems.length} picks from ${feedShouts.length} total, based on your interests: ${myTopTags.join(", ")}):\n\n${lines.join("\n\n")}`,
362
+ }],
363
+ };
364
+ }
365
+ );
210
366
  }