@clubnet/seedclub 0.2.11 → 0.2.13

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,5 +1,8 @@
1
1
  /**
2
2
  * /extract, /clubtone, /extractions commands.
3
+ *
4
+ * Key design: the LLM responds BY calling the save tool.
5
+ * The tool parameters ARE the extraction. No text-then-save — saving IS responding.
3
6
  */
4
7
 
5
8
  import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
@@ -31,31 +34,41 @@ export function registerExtractCommand(pi: ExtensionAPI) {
31
34
  ? result.content.slice(0, 40000) + "\n\n[Content truncated at 40K chars]"
32
35
  : result.content;
33
36
  const prompt = buildExtractionPrompt(content, url, title);
34
- pi.sendUserMessage(prompt + "\n\nAfter completing the extraction, call seed_save_extraction with the structured results so they are saved to persistent memory. Include all 9 dimensions.");
37
+ pi.sendUserMessage(
38
+ prompt +
39
+ "\n\nIMPORTANT: Respond ONLY by calling the seed_save_extraction tool with your complete analysis as the parameters. " +
40
+ "Do not write the extraction as text first. The tool call IS your response. " +
41
+ "Fill in every dimension directly in the tool parameters: sourceUrl, sourceTitle, sourceType, summary, claims, entities, quotes, patterns, relevance, actionable, extractionNotes. " +
42
+ "After the tool confirms the save, give a brief summary of what you found."
43
+ );
35
44
  return;
36
45
  }
37
46
 
38
47
  if (!arg) {
39
48
  pi.sendUserMessage(
40
- "Look at the content shared in this conversation and run a full 9-dimension extraction:\n\n" +
41
- "1. **Summary** - 2-3 sentences\n" +
42
- "2. **Claims & Arguments** - every claim including implicit ones\n" +
43
- "3. **Entities** - people, orgs, concepts, products, technologies\n" +
44
- "4. **Temporal** - only if time-sensitive\n" +
45
- "5. **Quotes** - direct quotes worth preserving\n" +
46
- "6. **Patterns & Themes** - patterns and how they connect to my work\n" +
47
- "7. **Relevance** - which areas this matters for and why\n" +
48
- "8. **Actionable** - content seeds, follow-ups, research threads\n" +
49
- "9. **Extraction Notes** - what is rich, what is thin\n\n" +
50
- "Be thorough. After completing, call seed_save_extraction to persist."
49
+ "Look at the content shared in this conversation and extract structured knowledge from it.\n\n" +
50
+ "IMPORTANT: Respond ONLY by calling the seed_save_extraction tool. " +
51
+ "Do not write the extraction as text. The tool call IS your response.\n\n" +
52
+ "Fill in all 9 dimensions as tool parameters:\n" +
53
+ "- sourceTitle, sourceType, summary (2-3 sentences)\n" +
54
+ "- claims (every claim with evidence, confidence, implicit flag)\n" +
55
+ "- entities (people, orgs, concepts, products, technologies)\n" +
56
+ "- quotes (direct quotes worth preserving)\n" +
57
+ "- patterns (how themes connect to broader work — highest value dimension)\n" +
58
+ "- relevance (which areas this matters for and why)\n" +
59
+ "- actionable (content seeds, follow-ups, research threads)\n" +
60
+ "- extractionNotes (observations, schema fitness, second pass candidate)\n\n" +
61
+ "Be thorough. After the tool confirms the save, give a brief summary of what you found."
51
62
  );
52
63
  return;
53
64
  }
54
65
 
55
66
  pi.sendUserMessage(
56
- "Explore and extract from the concept: \"" + arg + "\"\n\n" +
57
- "Run a full 9-dimension extraction (summary, claims, entities, temporal, quotes, patterns, relevance, actionable, extraction notes).\n\n" +
58
- "Be thorough. After completing, call seed_save_extraction to persist."
67
+ "Extract structured knowledge from the concept: \"" + arg + "\"\n\n" +
68
+ "IMPORTANT: Respond ONLY by calling the seed_save_extraction tool with your analysis as the parameters. " +
69
+ "Do not write the extraction as text first. The tool call IS your response. " +
70
+ "Fill in all 9 dimensions (sourceTitle, summary, claims, entities, quotes, patterns, relevance, actionable, extractionNotes). " +
71
+ "After the tool confirms the save, give a brief summary of what you found."
59
72
  );
60
73
  },
61
74
  });
@@ -71,7 +84,7 @@ export function registerExtractCommand(pi: ExtensionAPI) {
71
84
  description: "Browse your saved extractions",
72
85
  handler: async (_args, _ctx) => {
73
86
  pi.sendUserMessage(
74
- "List my recent extractions using seed_list_extractions. Show them in a clean format with title, date, and counts of patterns/claims/actionable items."
87
+ "Call seed_list_extractions now. Show results in a clean format with title, date, and counts of patterns/claims/actionable items."
75
88
  );
76
89
  },
77
90
  });
@@ -80,15 +93,18 @@ export function registerExtractCommand(pi: ExtensionAPI) {
80
93
  function runClubtone(pi: ExtensionAPI, context?: string) {
81
94
  const extra = context ? "\n\nAdditional context: " + context : "";
82
95
  pi.sendUserMessage(
83
- "Look at the most recent extraction in our conversation. Turn it into content directions.\n\n" +
84
- "Generate 3-5 content directions I could take. For each:\n" +
85
- "1. **Hook** - opening line that stops a scroll\n" +
86
- "2. **Core argument** - what I would actually say, grounded in the source\n" +
87
- "3. **Format** - tweet thread, short post, essay, newsletter, conversation starter\n" +
88
- "4. **Source grounding** - which claims, patterns, quotes feed this\n" +
89
- "5. **Voice note** - how I would explain this to a friend in 2 sentences\n\n" +
90
- "Do not be generic. My voice, not the source voice.\n\n" +
91
- "After generating, call seed_save_content_seeds to persist." + extra
96
+ "Look at the most recent extraction in our conversation. Generate 3-5 content directions.\n\n" +
97
+ "IMPORTANT: Respond ONLY by calling the seed_save_content_seeds tool. " +
98
+ "Do not write the directions as text first. The tool call IS your response.\n\n" +
99
+ "For each seed, fill in the tool parameters:\n" +
100
+ "- extractionId (from the most recent seed_save_extraction result)\n" +
101
+ "- hook (opening line that stops a scroll)\n" +
102
+ "- coreArgument (what I would actually say, grounded in the source)\n" +
103
+ "- format (tweet_thread, short_post, essay, newsletter, conversation_starter)\n" +
104
+ "- sourceGrounding (which claims, patterns, quotes feed this)\n" +
105
+ "- voiceNote (how I would explain this to a friend in 2 sentences)\n\n" +
106
+ "Do not be generic. My voice, not the source voice. " +
107
+ "After the tool confirms, give a brief summary of the directions." + extra
92
108
  );
93
109
  }
94
110
 
@@ -1,8 +1,12 @@
1
1
  /**
2
- * Extraction tools — save and retrieve extractions from the Seed Club API.
2
+ * Extraction tools — the full L0-L4 tool surface.
3
3
  *
4
- * These tools let the LLM persist extractions after running /extract,
5
- * and save content seeds after running /clubtone.
4
+ * L0: seed_register_source register a source before extraction
5
+ * L2: seed_save_extraction — save a 9-dimension extraction (tool call IS the save)
6
+ * L3: seed_synthesize — cross-source synthesis from 2+ extractions
7
+ * L4: seed_distill — promote a pattern to stable concept
8
+ * Content: seed_save_content_seeds — save content directions from extraction or synthesis
9
+ * Read: seed_list_extractions, seed_get_extraction, seed_list_sources, seed_list_syntheses
6
10
  */
7
11
 
8
12
  import { Text } from "@mariozechner/pi-tui";
@@ -13,10 +17,30 @@ import { wrapExecute } from "../tool-utils.js";
13
17
 
14
18
  // ── API handlers ────────────────────────────────────────────────────────
15
19
 
20
+ async function registerSource(args: {
21
+ url?: string;
22
+ title: string;
23
+ sourceType?: string;
24
+ author?: string;
25
+ provenance?: string;
26
+ tier?: string;
27
+ discoveryTrace?: string;
28
+ credibilityNotes?: string;
29
+ }) {
30
+ try {
31
+ const response = await api.post<any>("/extractions/sources", args);
32
+ return { id: response.source.id, title: response.source.title, status: response.source.status };
33
+ } catch (error) {
34
+ if (error instanceof ApiError) return { error: error.message, status: error.status };
35
+ throw error;
36
+ }
37
+ }
38
+
16
39
  async function saveExtraction(args: {
17
40
  sourceUrl?: string;
18
41
  sourceTitle: string;
19
42
  sourceType?: string;
43
+ sourceId?: string;
20
44
  rawContent?: string;
21
45
  fetchMethod?: string;
22
46
  summary?: string;
@@ -44,9 +68,48 @@ async function saveExtraction(args: {
44
68
  }
45
69
  }
46
70
 
71
+ async function synthesize(args: {
72
+ title: string;
73
+ thesis?: string;
74
+ extractionIds: string[];
75
+ patterns?: any[];
76
+ entityResolution?: any[];
77
+ themeConvergence?: any[];
78
+ contradictions?: any[];
79
+ bridges?: any[];
80
+ emergentInsights?: string[];
81
+ trigger?: string;
82
+ }) {
83
+ try {
84
+ const response = await api.post<any>("/extractions/syntheses", args);
85
+ return { id: response.synthesis.id, title: response.synthesis.title, extractionCount: response.synthesis.extractionCount };
86
+ } catch (error) {
87
+ if (error instanceof ApiError) return { error: error.message, status: error.status };
88
+ throw error;
89
+ }
90
+ }
91
+
92
+ async function distill(args: {
93
+ title: string;
94
+ concept: string;
95
+ evidenceSummary?: string;
96
+ synthesisIds: string[];
97
+ tags?: string[];
98
+ relatedEntities?: any[];
99
+ }) {
100
+ try {
101
+ const response = await api.post<any>("/extractions/distillations", args);
102
+ return { id: response.distillation.id, title: response.distillation.title, synthesisCount: response.distillation.synthesisCount };
103
+ } catch (error) {
104
+ if (error instanceof ApiError) return { error: error.message, status: error.status };
105
+ throw error;
106
+ }
107
+ }
108
+
47
109
  async function saveContentSeeds(args: {
48
110
  seeds: Array<{
49
- extractionId: string;
111
+ extractionId?: string;
112
+ synthesisId?: string;
50
113
  hook: string;
51
114
  coreArgument: string;
52
115
  format: string;
@@ -56,7 +119,7 @@ async function saveContentSeeds(args: {
56
119
  }>;
57
120
  }) {
58
121
  try {
59
- const response = await api.post<any>("/extractions", args);
122
+ const response = await api.post<any>("/extractions/seeds", args);
60
123
  return { count: response.count, seeds: response.seeds?.map((s: any) => s.id) };
61
124
  } catch (error) {
62
125
  if (error instanceof ApiError) return { error: error.message, status: error.status };
@@ -89,18 +152,96 @@ async function getExtraction(args: { id: string; seeds?: boolean }) {
89
152
  }
90
153
  }
91
154
 
155
+ async function listSources(args: { limit?: number; status?: string }) {
156
+ try {
157
+ const params: Record<string, string | number | undefined> = {};
158
+ if (args.limit) params.limit = args.limit;
159
+ if (args.status) params.status = args.status;
160
+ const response = await api.get<any>("/extractions/sources", params);
161
+ return { sources: response.sources, total: response.total };
162
+ } catch (error) {
163
+ if (error instanceof ApiError) return { error: error.message, status: error.status };
164
+ throw error;
165
+ }
166
+ }
167
+
168
+ async function listSyntheses(args: { limit?: number }) {
169
+ try {
170
+ const params: Record<string, string | number | undefined> = {};
171
+ if (args.limit) params.limit = args.limit;
172
+ const response = await api.get<any>("/extractions/syntheses", params);
173
+ return { syntheses: response.syntheses, total: response.total };
174
+ } catch (error) {
175
+ if (error instanceof ApiError) return { error: error.message, status: error.status };
176
+ throw error;
177
+ }
178
+ }
179
+
180
+ async function listSeeds(args: { limit?: number; unused?: boolean; extractionId?: string; synthesisId?: string }) {
181
+ try {
182
+ const params: Record<string, string | number | undefined> = {};
183
+ if (args.limit) params.limit = args.limit;
184
+ if (args.unused) params.unused = "true";
185
+ if (args.extractionId) params.extractionId = args.extractionId;
186
+ if (args.synthesisId) params.synthesisId = args.synthesisId;
187
+ const response = await api.get<any>("/extractions/seeds", params);
188
+ return { seeds: response.seeds, total: response.total };
189
+ } catch (error) {
190
+ if (error instanceof ApiError) return { error: error.message, status: error.status };
191
+ throw error;
192
+ }
193
+ }
194
+
195
+ async function markSeedUsed(args: { id: string }) {
196
+ try {
197
+ const response = await api.post<any>("/extractions/seeds/used", { id: args.id });
198
+ return { id: response.seed.id, used: true };
199
+ } catch (error) {
200
+ if (error instanceof ApiError) return { error: error.message, status: error.status };
201
+ throw error;
202
+ }
203
+ }
204
+
92
205
  // ── Tool registration ───────────────────────────────────────────────────
93
206
 
94
207
  export function registerExtractionTools(pi: ExtensionAPI) {
208
+
209
+ // ── L0: Source Registration ───────────────────────────────────────────
210
+
211
+ pi.registerTool({
212
+ name: "seed_register_source",
213
+ label: "Register Source",
214
+ description: "Register a source in the L0 registry before extraction. Records provenance, credibility, and discovery trace. Returns source ID for linking to extraction.",
215
+ parameters: Type.Object({
216
+ url: Type.Optional(Type.String({ description: "Source URL" })),
217
+ title: Type.String({ description: "Human-readable title" }),
218
+ sourceType: Type.Optional(Type.String({ description: "article, tweet, essay, conversation, voice_note, code, transcript" })),
219
+ author: Type.Optional(Type.String({ description: "Creator(s)" })),
220
+ provenance: Type.Optional(Type.String({ description: "authored, licensed, public, restricted" })),
221
+ tier: Type.Optional(Type.String({ description: "external, internal_external, internal" })),
222
+ discoveryTrace: Type.Optional(Type.String({ description: "How found, who referred, what context" })),
223
+ credibilityNotes: Type.Optional(Type.String({ description: "Credibility assessment" })),
224
+ }),
225
+ execute: wrapExecute(registerSource),
226
+ renderResult(result: any, _opts: any, theme: any) {
227
+ if (result.isError) return new Text(theme.fg("error", result.content?.[0]?.text || "Error"), 0, 0);
228
+ const d = result.details || {};
229
+ if (d.error) return new Text(theme.fg("error", d.error), 0, 0);
230
+ return new Text(theme.fg("success", "Registered: " + d.title + " [" + d.id + "]"), 0, 0);
231
+ },
232
+ });
233
+
234
+ // ── L2: Save Extraction ───────────────────────────────────────────────
235
+
95
236
  pi.registerTool({
96
237
  name: "seed_save_extraction",
97
238
  label: "Save Extraction",
98
- description:
99
- "Save a structured extraction to persistent memory. Call this after completing a /extract analysis to store the 9-dimension extraction. Returns the extraction ID needed for saving content seeds.",
239
+ description: "Save a structured 9-dimension extraction. The tool call IS the extraction — fill all dimensions as parameters. Returns extraction ID for content seeds and synthesis.",
100
240
  parameters: Type.Object({
101
241
  sourceUrl: Type.Optional(Type.String({ description: "Source URL if from web" })),
102
242
  sourceTitle: Type.String({ description: "Title of the source" }),
103
243
  sourceType: Type.Optional(Type.String({ description: "article, tweet, essay, conversation, concept" })),
244
+ sourceId: Type.Optional(Type.String({ description: "L0 source registry ID if registered" })),
104
245
  summary: Type.Optional(Type.String({ description: "2-3 sentence summary" })),
105
246
  claims: Type.Optional(Type.Array(Type.Object({
106
247
  claim: Type.String(),
@@ -145,23 +286,100 @@ export function registerExtractionTools(pi: ExtensionAPI) {
145
286
  if (result.isError) return new Text(theme.fg("error", result.content?.[0]?.text || "Error"), 0, 0);
146
287
  const d = result.details || {};
147
288
  if (d.error) return new Text(theme.fg("error", d.error), 0, 0);
148
- return new Text(
149
- theme.fg("success", `✓ Saved extraction: ${d.sourceTitle || d.id}`),
150
- 0, 0,
151
- );
289
+ return new Text(theme.fg("success", "Saved extraction: " + (d.sourceTitle || d.id)), 0, 0);
152
290
  },
153
291
  });
154
292
 
293
+ // ── L3: Synthesize ────────────────────────────────────────────────────
294
+
295
+ pi.registerTool({
296
+ name: "seed_synthesize",
297
+ label: "Synthesize",
298
+ description: "Create an L3 cross-source synthesis from 2+ extractions. Identifies patterns, contradictions, entity resolution, theme convergence, and emergent insights across sources. The tool call IS the synthesis.",
299
+ parameters: Type.Object({
300
+ title: Type.String({ description: "Synthesis title" }),
301
+ thesis: Type.Optional(Type.String({ description: "Core insight from crossing these sources" })),
302
+ extractionIds: Type.Array(Type.String({ description: "Extraction IDs to synthesize" })),
303
+ patterns: Type.Optional(Type.Array(Type.Object({
304
+ pattern: Type.String(),
305
+ sources: Type.Array(Type.String()),
306
+ strength: Type.String(),
307
+ connection: Type.String(),
308
+ }))),
309
+ entityResolution: Type.Optional(Type.Array(Type.Object({
310
+ entity: Type.String(),
311
+ appearances: Type.Array(Type.Object({ extractionId: Type.String(), role: Type.String() })),
312
+ mergedUnderstanding: Type.String(),
313
+ }))),
314
+ themeConvergence: Type.Optional(Type.Array(Type.Object({
315
+ theme: Type.String(),
316
+ supportingExtractions: Type.Array(Type.String()),
317
+ divergences: Type.String(),
318
+ synthesisNote: Type.String(),
319
+ }))),
320
+ contradictions: Type.Optional(Type.Array(Type.Object({
321
+ claimA: Type.String(),
322
+ sourceA: Type.String(),
323
+ claimB: Type.String(),
324
+ sourceB: Type.String(),
325
+ resolution: Type.String(),
326
+ }))),
327
+ bridges: Type.Optional(Type.Array(Type.Object({
328
+ fromExtraction: Type.String(),
329
+ toExtraction: Type.String(),
330
+ bridgeInsight: Type.String(),
331
+ }))),
332
+ emergentInsights: Type.Optional(Type.Array(Type.String({ description: "Insights only visible when crossing sources" }))),
333
+ trigger: Type.Optional(Type.String({ description: "manual, theme_cluster, project_need" })),
334
+ }),
335
+ execute: wrapExecute(synthesize),
336
+ renderResult(result: any, _opts: any, theme: any) {
337
+ if (result.isError) return new Text(theme.fg("error", result.content?.[0]?.text || "Error"), 0, 0);
338
+ const d = result.details || {};
339
+ if (d.error) return new Text(theme.fg("error", d.error), 0, 0);
340
+ return new Text(theme.fg("success", "Synthesis: " + d.title + " (" + d.extractionCount + " sources)"), 0, 0);
341
+ },
342
+ });
343
+
344
+ // ── L4: Distill ───────────────────────────────────────────────────────
345
+
346
+ pi.registerTool({
347
+ name: "seed_distill",
348
+ label: "Distill",
349
+ description: "Promote a pattern to an L4 distillation — a stable concept proven across 3+ syntheses. Returns distillation ID.",
350
+ parameters: Type.Object({
351
+ title: Type.String({ description: "Concept title" }),
352
+ concept: Type.String({ description: "The stable pattern or mental model" }),
353
+ evidenceSummary: Type.Optional(Type.String({ description: "Why this graduated" })),
354
+ synthesisIds: Type.Array(Type.String({ description: "Synthesis IDs that support this" })),
355
+ tags: Type.Optional(Type.Array(Type.String({ description: "Freeform tags for routing" }))),
356
+ relatedEntities: Type.Optional(Type.Array(Type.Object({
357
+ name: Type.String(),
358
+ type: Type.String(),
359
+ role: Type.String(),
360
+ }))),
361
+ }),
362
+ execute: wrapExecute(distill),
363
+ renderResult(result: any, _opts: any, theme: any) {
364
+ if (result.isError) return new Text(theme.fg("error", result.content?.[0]?.text || "Error"), 0, 0);
365
+ const d = result.details || {};
366
+ if (d.error) return new Text(theme.fg("error", d.error), 0, 0);
367
+ return new Text(theme.fg("success", "Distilled: " + d.title + " (" + d.synthesisCount + " syntheses)"), 0, 0);
368
+ },
369
+ });
370
+
371
+ // ── Content Seeds ─────────────────────────────────────────────────────
372
+
155
373
  pi.registerTool({
156
374
  name: "seed_save_content_seeds",
157
375
  label: "Save Content Seeds",
158
- description:
159
- "Save content seeds (content directions) generated from a /clubtone pass. Each seed needs the extractionId from a saved extraction.",
376
+ description: "Save content directions from an extraction or synthesis. Each seed needs either an extractionId or synthesisId (or both for cross-source seeds).",
160
377
  parameters: Type.Object({
161
378
  seeds: Type.Array(Type.Object({
162
- extractionId: Type.String({ description: "ID of the source extraction" }),
379
+ extractionId: Type.Optional(Type.String({ description: "Source extraction ID" })),
380
+ synthesisId: Type.Optional(Type.String({ description: "Source synthesis ID" })),
163
381
  hook: Type.String({ description: "Opening line / angle" }),
164
- coreArgument: Type.String({ description: "What you'd actually say" }),
382
+ coreArgument: Type.String({ description: "What you would actually say" }),
165
383
  format: Type.String({ description: "tweet_thread, short_post, essay, newsletter, etc." }),
166
384
  sourceGrounding: Type.String({ description: "Which extraction dimensions feed this" }),
167
385
  voiceNote: Type.String({ description: "2-sentence friend explanation" }),
@@ -173,18 +391,16 @@ export function registerExtractionTools(pi: ExtensionAPI) {
173
391
  if (result.isError) return new Text(theme.fg("error", result.content?.[0]?.text || "Error"), 0, 0);
174
392
  const d = result.details || {};
175
393
  if (d.error) return new Text(theme.fg("error", d.error), 0, 0);
176
- return new Text(
177
- theme.fg("success", `✓ Saved ${d.count || 0} content seeds`),
178
- 0, 0,
179
- );
394
+ return new Text(theme.fg("success", "Saved " + (d.count || 0) + " content seeds"), 0, 0);
180
395
  },
181
396
  });
182
397
 
398
+ // ── Read tools ────────────────────────────────────────────────────────
399
+
183
400
  pi.registerTool({
184
401
  name: "seed_list_extractions",
185
402
  label: "List Extractions",
186
- description:
187
- "List the user's saved extractions. Shows source, summary, and counts of patterns/claims/actionable items.",
403
+ description: "List saved extractions with summary and dimension counts.",
188
404
  parameters: Type.Object({
189
405
  limit: Type.Optional(Type.Number({ description: "Max results (default 20)" })),
190
406
  search: Type.Optional(Type.String({ description: "Search by title or summary" })),
@@ -194,14 +410,14 @@ export function registerExtractionTools(pi: ExtensionAPI) {
194
410
  if (result.isError) return new Text(theme.fg("error", result.content?.[0]?.text || "Error"), 0, 0);
195
411
  const d = result.details || {};
196
412
  if (!d.extractions?.length) return new Text(theme.fg("dim", "No extractions yet"), 0, 0);
197
- let text = theme.fg("muted", `${d.total} extractions`);
413
+ let text = theme.fg("muted", d.total + " extractions");
198
414
  for (const e of d.extractions.slice(0, expanded ? 50 : 10)) {
199
415
  const counts = [
200
- e.patternCount && `${e.patternCount}p`,
201
- e.claimCount && `${e.claimCount}c`,
202
- e.actionableCount && `${e.actionableCount}a`,
416
+ e.patternCount && e.patternCount + "p",
417
+ e.claimCount && e.claimCount + "c",
418
+ e.actionableCount && e.actionableCount + "a",
203
419
  ].filter(Boolean).join("/");
204
- text += `\n ${e.sourceTitle}${counts ? ` [${counts}]` : ""} ${e.status}`;
420
+ text += "\n " + e.sourceTitle + (counts ? " [" + counts + "]" : "") + " - " + e.status;
205
421
  }
206
422
  return new Text(text, 0, 0);
207
423
  },
@@ -210,12 +426,92 @@ export function registerExtractionTools(pi: ExtensionAPI) {
210
426
  pi.registerTool({
211
427
  name: "seed_get_extraction",
212
428
  label: "Get Extraction",
213
- description:
214
- "Get a specific extraction by ID, with full 9-dimension data. Optionally include content seeds.",
429
+ description: "Get a specific extraction by ID with full 9-dimension data. Optionally include content seeds.",
215
430
  parameters: Type.Object({
216
431
  id: Type.String({ description: "Extraction ID" }),
217
432
  seeds: Type.Optional(Type.Boolean({ description: "Include content seeds" })),
218
433
  }),
219
434
  execute: wrapExecute(getExtraction),
220
435
  });
436
+
437
+ pi.registerTool({
438
+ name: "seed_list_sources",
439
+ label: "List Sources",
440
+ description: "List registered sources from the L0 registry.",
441
+ parameters: Type.Object({
442
+ limit: Type.Optional(Type.Number({ description: "Max results (default 20)" })),
443
+ status: Type.Optional(Type.String({ description: "Filter by status: registered, captured, extracted, synthesized, distilled" })),
444
+ }),
445
+ execute: wrapExecute(listSources),
446
+ renderResult(result: any, _opts: any, theme: any) {
447
+ if (result.isError) return new Text(theme.fg("error", result.content?.[0]?.text || "Error"), 0, 0);
448
+ const d = result.details || {};
449
+ if (!d.sources?.length) return new Text(theme.fg("dim", "No sources registered"), 0, 0);
450
+ let text = theme.fg("muted", d.total + " sources");
451
+ for (const s of d.sources) {
452
+ text += "\n " + s.title + " [" + s.status + "] " + (s.provenance || "");
453
+ }
454
+ return new Text(text, 0, 0);
455
+ },
456
+ });
457
+
458
+ pi.registerTool({
459
+ name: "seed_list_syntheses",
460
+ label: "List Syntheses",
461
+ description: "List L3 cross-source syntheses.",
462
+ parameters: Type.Object({
463
+ limit: Type.Optional(Type.Number({ description: "Max results (default 20)" })),
464
+ }),
465
+ execute: wrapExecute(listSyntheses),
466
+ renderResult(result: any, _opts: any, theme: any) {
467
+ if (result.isError) return new Text(theme.fg("error", result.content?.[0]?.text || "Error"), 0, 0);
468
+ const d = result.details || {};
469
+ if (!d.syntheses?.length) return new Text(theme.fg("dim", "No syntheses yet"), 0, 0);
470
+ let text = theme.fg("muted", d.total + " syntheses");
471
+ for (const s of d.syntheses) {
472
+ text += "\n " + s.title + " (" + s.extractionCount + " sources)";
473
+ }
474
+ return new Text(text, 0, 0);
475
+ },
476
+ });
477
+
478
+ pi.registerTool({
479
+ name: "seed_list_seeds",
480
+ label: "List Content Seeds",
481
+ description: "List content seeds. Filter by unused, extraction, or synthesis.",
482
+ parameters: Type.Object({
483
+ limit: Type.Optional(Type.Number({ description: "Max results (default 20)" })),
484
+ unused: Type.Optional(Type.Boolean({ description: "Only unused seeds" })),
485
+ extractionId: Type.Optional(Type.String({ description: "Filter by extraction" })),
486
+ synthesisId: Type.Optional(Type.String({ description: "Filter by synthesis" })),
487
+ }),
488
+ execute: wrapExecute(listSeeds),
489
+ renderResult(result: any, _opts: any, theme: any) {
490
+ if (result.isError) return new Text(theme.fg("error", result.content?.[0]?.text || "Error"), 0, 0);
491
+ const d = result.details || {};
492
+ if (!d.seeds?.length) return new Text(theme.fg("dim", "No content seeds yet"), 0, 0);
493
+ let text = theme.fg("muted", d.total + " seeds");
494
+ for (const s of d.seeds) {
495
+ const status = s.used ? "used" : "fresh";
496
+ text += "\n [" + s.format + "] " + s.hook.slice(0, 60) + "... (" + status + ")";
497
+ }
498
+ return new Text(text, 0, 0);
499
+ },
500
+ });
501
+
502
+ pi.registerTool({
503
+ name: "seed_mark_used",
504
+ label: "Mark Seed Used",
505
+ description: "Mark a content seed as used after drafting content from it.",
506
+ parameters: Type.Object({
507
+ id: Type.String({ description: "Content seed ID" }),
508
+ }),
509
+ execute: wrapExecute(markSeedUsed),
510
+ renderResult(result: any, _opts: any, theme: any) {
511
+ if (result.isError) return new Text(theme.fg("error", result.content?.[0]?.text || "Error"), 0, 0);
512
+ const d = result.details || {};
513
+ if (d.error) return new Text(theme.fg("error", d.error), 0, 0);
514
+ return new Text(theme.fg("success", "Marked as used"), 0, 0);
515
+ },
516
+ });
221
517
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubnet/seedclub",
3
- "version": "0.2.11",
3
+ "version": "0.2.13",
4
4
  "description": "The Human+ Venture Network — AI agent for deal sourcing, research, and signal tracking",
5
5
  "license": "MIT",
6
6
  "repository": {