@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(
|
|
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
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
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
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"
|
|
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
|
-
"
|
|
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.
|
|
84
|
-
"
|
|
85
|
-
"
|
|
86
|
-
"
|
|
87
|
-
"
|
|
88
|
-
"
|
|
89
|
-
"
|
|
90
|
-
"
|
|
91
|
-
"
|
|
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 —
|
|
2
|
+
* Extraction tools — the full L0-L4 tool surface.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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
|
|
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: "
|
|
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
|
|
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",
|
|
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 &&
|
|
201
|
-
e.claimCount &&
|
|
202
|
-
e.actionableCount &&
|
|
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 +=
|
|
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
|
}
|