@clubnet/seedclub 0.2.10 → 0.2.11
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,169 +1,107 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* /extract
|
|
3
|
-
*
|
|
4
|
-
* Usage:
|
|
5
|
-
* /extract <url> — Capture + extract a URL into the 9-dimension schema
|
|
6
|
-
* /extract — Extract from content already in the conversation
|
|
7
|
-
* /extract clubtone — Turn the last extraction into content directions
|
|
8
|
-
*
|
|
9
|
-
* Flow:
|
|
10
|
-
* 1. User shares a URL or pastes content
|
|
11
|
-
* 2. /extract captures it (L1) and runs 9-dimension extraction (L2)
|
|
12
|
-
* 3. /extract clubtone takes the extraction and generates content seeds
|
|
2
|
+
* /extract, /clubtone, /extractions commands.
|
|
13
3
|
*/
|
|
14
4
|
|
|
15
5
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
16
6
|
import { capture } from "../extraction/capture.js";
|
|
17
|
-
import { buildExtractionPrompt
|
|
7
|
+
import { buildExtractionPrompt } from "../extraction/schema.js";
|
|
18
8
|
|
|
19
9
|
export function registerExtractCommand(pi: ExtensionAPI) {
|
|
20
|
-
// ── /extract command ──────────────────────────────────────────────
|
|
21
|
-
|
|
22
10
|
pi.registerCommand("extract", {
|
|
23
11
|
description: "Extract structured knowledge from a URL or conversation content",
|
|
24
12
|
handler: async (args, ctx) => {
|
|
25
13
|
const arg = args?.trim() || "";
|
|
26
14
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
pi.sendUserMessage(
|
|
30
|
-
`Look at the most recent extraction in our conversation. Now turn it into content directions using this approach:\n\n` +
|
|
31
|
-
`Generate 3-5 content directions I could take. For each:\n` +
|
|
32
|
-
`1. **Hook** — opening line that stops a scroll\n` +
|
|
33
|
-
`2. **Core argument** — what I'd actually say, grounded in the source\n` +
|
|
34
|
-
`3. **Format** — tweet thread, short post, essay, newsletter, conversation starter\n` +
|
|
35
|
-
`4. **Source grounding** — which claims, patterns, quotes feed this\n` +
|
|
36
|
-
`5. **Voice note** — how I'd explain this to a friend in 2 sentences\n\n` +
|
|
37
|
-
`Don't be generic. These should feel like things only someone who deeply read the source would write. My voice, not the source's voice.`,
|
|
38
|
-
);
|
|
15
|
+
if (arg === "clubtone" || arg === "club" || arg === "tone") {
|
|
16
|
+
runClubtone(pi);
|
|
39
17
|
return;
|
|
40
18
|
}
|
|
41
19
|
|
|
42
|
-
// /extract <url> — capture + extract
|
|
43
20
|
const url = extractUrl(arg);
|
|
44
21
|
if (url) {
|
|
45
|
-
ctx.ui.notify(
|
|
46
|
-
|
|
22
|
+
ctx.ui.notify("Capturing " + url + "...", "info");
|
|
47
23
|
const result = await capture(url, pi);
|
|
48
24
|
if (!result.complete || !result.content) {
|
|
49
|
-
ctx.ui.notify(
|
|
25
|
+
ctx.ui.notify("Failed to capture " + url + (result.note ? ": " + result.note : ""), "error");
|
|
50
26
|
return;
|
|
51
27
|
}
|
|
52
|
-
|
|
53
28
|
const title = extractTitle(result.content) || new URL(url).hostname;
|
|
54
|
-
ctx.ui.notify(
|
|
55
|
-
|
|
56
|
-
// Truncate if massive — keep first 40K chars for extraction
|
|
29
|
+
ctx.ui.notify("Captured (" + result.fetchMethod + "). Running extraction...", "info");
|
|
57
30
|
const content = result.content.length > 40000
|
|
58
31
|
? result.content.slice(0, 40000) + "\n\n[Content truncated at 40K chars]"
|
|
59
32
|
: result.content;
|
|
60
|
-
|
|
61
33
|
const prompt = buildExtractionPrompt(content, url, title);
|
|
62
|
-
pi.sendUserMessage(
|
|
63
|
-
prompt +
|
|
64
|
-
"
|
|
65
|
-
|
|
66
|
-
After completing the extraction, call seed_save_extraction with the structured results so they are saved to persistent memory. Include all 9 dimensions."
|
|
67
|
-
);
|
|
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.");
|
|
68
35
|
return;
|
|
69
36
|
}
|
|
70
37
|
|
|
71
|
-
// /extract (no args) — extract from conversation context
|
|
72
38
|
if (!arg) {
|
|
73
39
|
pi.sendUserMessage(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
After completing the extraction, call seed_save_extraction with the structured results so they are saved to persistent memory.`,
|
|
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."
|
|
87
51
|
);
|
|
88
52
|
return;
|
|
89
53
|
}
|
|
90
54
|
|
|
91
|
-
// /extract <something that's not a URL> — treat as a topic/concept
|
|
92
55
|
pi.sendUserMessage(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
`2. **Claims & Arguments** — key claims around this topic\n` +
|
|
97
|
-
`3. **Entities** — key people, orgs, frameworks\n` +
|
|
98
|
-
`4. **Temporal** — only if time-sensitive\n` +
|
|
99
|
-
`5. **Quotes** — notable quotes if you know them\n` +
|
|
100
|
-
`6. **Patterns & Themes** ⭐ — patterns and connections to broader work\n` +
|
|
101
|
-
`7. **Relevance** — where this matters\n` +
|
|
102
|
-
`8. **Actionable** — what can be done with this\n` +
|
|
103
|
-
`9. **Extraction Notes** — what's rich, what needs more research\n\n` +
|
|
104
|
-
`Be thorough and connect everything to real work context.
|
|
105
|
-
|
|
106
|
-
After completing the extraction, call seed_save_extraction with the structured results so they are saved to persistent memory.`,
|
|
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."
|
|
107
59
|
);
|
|
108
60
|
},
|
|
109
61
|
});
|
|
110
62
|
|
|
111
|
-
// ── /clubtone shorthand ───────────────────────────────────────────
|
|
112
|
-
|
|
113
63
|
pi.registerCommand("clubtone", {
|
|
114
64
|
description: "Turn extracted knowledge into content directions",
|
|
115
|
-
handler: async (args,
|
|
116
|
-
|
|
117
|
-
const contextLine = context
|
|
118
|
-
? `\n\nAdditional context for content direction: ${context}`
|
|
119
|
-
: "";
|
|
120
|
-
|
|
121
|
-
pi.sendUserMessage(
|
|
122
|
-
`Look at the most recent extraction or source material in our conversation. Turn it into content directions.\n\n` +
|
|
123
|
-
`Generate 3-5 content directions I could take. For each:\n` +
|
|
124
|
-
`1. **Hook** — opening line that stops a scroll\n` +
|
|
125
|
-
`2. **Core argument** — what I'd actually say, grounded in the source\n` +
|
|
126
|
-
`3. **Format** — tweet thread, short post, essay, newsletter, conversation starter\n` +
|
|
127
|
-
`4. **Source grounding** — which claims, patterns, quotes feed this\n` +
|
|
128
|
-
`5. **Voice note** — how I'd explain this to a friend in 2 sentences\n\n` +
|
|
129
|
-
`Don't be generic. My voice, not the source's voice.
|
|
130
|
-
|
|
131
|
-
After generating content directions, call seed_save_content_seeds to save them (you'll need the extraction ID from the most recent seed_save_extraction call).${contextLine}`,
|
|
132
|
-
);
|
|
65
|
+
handler: async (args, _ctx) => {
|
|
66
|
+
runClubtone(pi, args?.trim());
|
|
133
67
|
},
|
|
134
68
|
});
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// ── /extractions — list saved extractions ─────────────────────────────
|
|
138
69
|
|
|
139
70
|
pi.registerCommand("extractions", {
|
|
140
71
|
description: "Browse your saved extractions",
|
|
141
|
-
handler: async (
|
|
72
|
+
handler: async (_args, _ctx) => {
|
|
142
73
|
pi.sendUserMessage(
|
|
143
|
-
"List my recent extractions using seed_list_extractions. Show them in a clean format with title, date, and counts of patterns/claims/actionable items.
|
|
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."
|
|
144
75
|
);
|
|
145
76
|
},
|
|
146
77
|
});
|
|
78
|
+
}
|
|
147
79
|
|
|
148
|
-
|
|
80
|
+
function runClubtone(pi: ExtensionAPI, context?: string) {
|
|
81
|
+
const extra = context ? "\n\nAdditional context: " + context : "";
|
|
82
|
+
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
|
|
92
|
+
);
|
|
93
|
+
}
|
|
149
94
|
|
|
150
95
|
function extractUrl(text: string): string | null {
|
|
151
|
-
// Direct URL
|
|
152
96
|
if (text.match(/^https?:\/\//)) return text.split(/\s/)[0];
|
|
153
|
-
|
|
154
|
-
// URL somewhere in text
|
|
155
97
|
const match = text.match(/(https?:\/\/[^\s]+)/);
|
|
156
98
|
return match ? match[1] : null;
|
|
157
99
|
}
|
|
158
100
|
|
|
159
101
|
function extractTitle(markdown: string): string | null {
|
|
160
|
-
// Try to find a title from Jina Reader markdown output
|
|
161
102
|
const titleMatch = markdown.match(/^#\s+(.+)$/m);
|
|
162
103
|
if (titleMatch) return titleMatch[1].trim();
|
|
163
|
-
|
|
164
|
-
// Try Title: header
|
|
165
104
|
const metaMatch = markdown.match(/^Title:\s*(.+)$/m);
|
|
166
105
|
if (metaMatch) return metaMatch[1].trim();
|
|
167
|
-
|
|
168
106
|
return null;
|
|
169
107
|
}
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* L1 Raw Capture
|
|
3
|
-
*
|
|
4
|
-
* Uses Jina Reader (r.jina.ai) for articles/pages.
|
|
5
|
-
* Returns markdown content without LLM processing.
|
|
2
|
+
* L1 Raw Capture - fetch full-fidelity content from URLs.
|
|
3
|
+
* Uses Jina Reader for articles, FxTwitter for tweets.
|
|
6
4
|
*/
|
|
7
5
|
|
|
8
|
-
import type { ExtensionAPI } from "@mariozechner/
|
|
6
|
+
import type { ExtensionAPI } from "@mariozechner/Seed Club-coding-agent";
|
|
9
7
|
|
|
10
8
|
export interface CaptureResult {
|
|
11
9
|
content: string;
|
|
@@ -15,74 +13,32 @@ export interface CaptureResult {
|
|
|
15
13
|
note?: string;
|
|
16
14
|
}
|
|
17
15
|
|
|
18
|
-
/**
|
|
19
|
-
* Fetch full content from a URL via Jina Reader.
|
|
20
|
-
* Returns clean markdown — no summarization, no LLM processing.
|
|
21
|
-
*/
|
|
22
16
|
export async function captureUrl(url: string, pi: ExtensionAPI): Promise<CaptureResult> {
|
|
23
|
-
const jinaUrl =
|
|
24
|
-
|
|
25
|
-
const result = await pi.exec("curl", ["-sL", "-H", "Accept: text/markdown", jinaUrl], {
|
|
26
|
-
timeout: 30000,
|
|
27
|
-
});
|
|
17
|
+
const jinaUrl = "https://r.jina.ai/" + url;
|
|
18
|
+
const result = await pi.exec("curl", ["-sL", "-H", "Accept: text/markdown", jinaUrl], { timeout: 30000 });
|
|
28
19
|
|
|
29
20
|
if (result.code !== 0 || !result.stdout?.trim()) {
|
|
30
|
-
// Fallback: try direct fetch
|
|
31
21
|
const direct = await pi.exec("curl", ["-sL", url], { timeout: 15000 });
|
|
32
22
|
if (direct.code === 0 && direct.stdout?.trim()) {
|
|
33
|
-
return {
|
|
34
|
-
content: direct.stdout,
|
|
35
|
-
contentType: "html",
|
|
36
|
-
fetchMethod: "direct-curl",
|
|
37
|
-
complete: true,
|
|
38
|
-
note: "Jina Reader failed; raw HTML captured",
|
|
39
|
-
};
|
|
23
|
+
return { content: direct.stdout, contentType: "html", fetchMethod: "direct-curl", complete: true, note: "Jina Reader failed; raw HTML captured" };
|
|
40
24
|
}
|
|
41
|
-
|
|
42
|
-
return {
|
|
43
|
-
content: "",
|
|
44
|
-
contentType: "text",
|
|
45
|
-
fetchMethod: "failed",
|
|
46
|
-
complete: false,
|
|
47
|
-
note: `Fetch failed for ${url}`,
|
|
48
|
-
};
|
|
25
|
+
return { content: "", contentType: "text", fetchMethod: "failed", complete: false, note: "Fetch failed for " + url };
|
|
49
26
|
}
|
|
50
27
|
|
|
51
|
-
return {
|
|
52
|
-
content: result.stdout,
|
|
53
|
-
contentType: "markdown",
|
|
54
|
-
fetchMethod: "jina-reader",
|
|
55
|
-
complete: true,
|
|
56
|
-
};
|
|
28
|
+
return { content: result.stdout, contentType: "markdown", fetchMethod: "jina-reader", complete: true };
|
|
57
29
|
}
|
|
58
30
|
|
|
59
|
-
/**
|
|
60
|
-
* Capture a tweet via FxTwitter API (returns JSON with full tweet data).
|
|
61
|
-
*/
|
|
62
31
|
export async function captureTweet(url: string, pi: ExtensionAPI): Promise<CaptureResult> {
|
|
63
|
-
|
|
64
|
-
const fxUrl = url
|
|
65
|
-
.replace("x.com", "api.fxtwitter.com")
|
|
66
|
-
.replace("twitter.com", "api.fxtwitter.com");
|
|
67
|
-
|
|
32
|
+
const fxUrl = url.replace("x.com", "api.fxtwitter.com").replace("twitter.com", "api.fxtwitter.com");
|
|
68
33
|
const result = await pi.exec("curl", ["-sL", fxUrl], { timeout: 15000 });
|
|
69
34
|
|
|
70
35
|
if (result.code !== 0 || !result.stdout?.trim()) {
|
|
71
|
-
// Fallback to Jina
|
|
72
36
|
return captureUrl(url, pi);
|
|
73
37
|
}
|
|
74
38
|
|
|
75
|
-
return {
|
|
76
|
-
content: result.stdout,
|
|
77
|
-
contentType: "text",
|
|
78
|
-
fetchMethod: "fxtwitter",
|
|
79
|
-
complete: true,
|
|
80
|
-
};
|
|
39
|
+
return { content: result.stdout, contentType: "text", fetchMethod: "fxtwitter", complete: true };
|
|
81
40
|
}
|
|
82
41
|
|
|
83
|
-
/**
|
|
84
|
-
* Auto-detect content type and use the right capture method.
|
|
85
|
-
*/
|
|
86
42
|
export async function capture(url: string, pi: ExtensionAPI): Promise<CaptureResult> {
|
|
87
43
|
if (url.match(/(?:x\.com|twitter\.com)\/\w+\/status\/\d+/)) {
|
|
88
44
|
return captureTweet(url, pi);
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* L2 Extraction Schema
|
|
3
|
-
*
|
|
4
|
-
* Every source gets extracted into these dimensions.
|
|
5
|
-
* This is where value compounds — connecting source material to your context.
|
|
2
|
+
* L2 Extraction Schema - the 9-dimension extraction format.
|
|
6
3
|
*/
|
|
7
4
|
|
|
8
5
|
export interface Claim {
|
|
@@ -27,7 +24,7 @@ export interface Quote {
|
|
|
27
24
|
|
|
28
25
|
export interface Pattern {
|
|
29
26
|
pattern: string;
|
|
30
|
-
connection: string;
|
|
27
|
+
connection: string;
|
|
31
28
|
strength: "strong" | "moderate" | "emerging";
|
|
32
29
|
}
|
|
33
30
|
|
|
@@ -49,20 +46,15 @@ export interface ExtractionNotes {
|
|
|
49
46
|
secondPassCandidate: boolean;
|
|
50
47
|
}
|
|
51
48
|
|
|
52
|
-
/**
|
|
53
|
-
* The full L2 extraction output for a single source.
|
|
54
|
-
*/
|
|
55
49
|
export interface L2Extraction {
|
|
56
50
|
sourceId: string;
|
|
57
51
|
sourceUrl: string;
|
|
58
52
|
sourceTitle: string;
|
|
59
53
|
extractedAt: string;
|
|
60
|
-
|
|
61
|
-
// The 9 dimensions
|
|
62
54
|
summary: string;
|
|
63
55
|
claims: Claim[];
|
|
64
56
|
entities: Entity[];
|
|
65
|
-
temporal?: { events: string[]; note: string };
|
|
57
|
+
temporal?: { events: string[]; note: string };
|
|
66
58
|
quotes: Quote[];
|
|
67
59
|
patterns: Pattern[];
|
|
68
60
|
relevance: Relevance[];
|
|
@@ -70,115 +62,56 @@ export interface L2Extraction {
|
|
|
70
62
|
extractionNotes: ExtractionNotes;
|
|
71
63
|
}
|
|
72
64
|
|
|
73
|
-
/**
|
|
74
|
-
* Returns the extraction prompt for Claude.
|
|
75
|
-
* This is what gets injected when the user runs /extract.
|
|
76
|
-
*/
|
|
77
65
|
export function buildExtractionPrompt(sourceContent: string, sourceUrl: string, sourceTitle: string): string {
|
|
78
|
-
return
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
- **
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
{
|
|
130
|
-
"sourceId": "extract-${Date.now()}",
|
|
131
|
-
"sourceUrl": "${sourceUrl}",
|
|
132
|
-
"sourceTitle": "${sourceTitle}",
|
|
133
|
-
"extractedAt": "${new Date().toISOString()}",
|
|
134
|
-
"summary": "...",
|
|
135
|
-
"claims": [{ "claim": "...", "evidence": "...", "confidence": "high|medium|low", "implicit": false, "notes": "..." }],
|
|
136
|
-
"entities": [{ "name": "...", "type": "person|org|concept|product|technology", "role": "..." }],
|
|
137
|
-
"temporal": null,
|
|
138
|
-
"quotes": [{ "text": "...", "attribution": "...", "context": "..." }],
|
|
139
|
-
"patterns": [{ "pattern": "...", "connection": "...", "strength": "strong|moderate|emerging" }],
|
|
140
|
-
"relevance": [{ "area": "...", "rating": "high|medium|low", "why": "..." }],
|
|
141
|
-
"actionable": [{ "type": "content_seed|follow_up|design_audit|research|outreach", "description": "...", "priority": "high|medium|low" }],
|
|
142
|
-
"extractionNotes": { "observations": ["..."], "schemaFitness": "...", "secondPassCandidate": false }
|
|
143
|
-
}
|
|
144
|
-
\`\`\``;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Returns a prompt for the "clubtone" / content seed pass.
|
|
149
|
-
* Takes extraction output and turns it into usable content directions.
|
|
150
|
-
*/
|
|
151
|
-
export function buildContentSeedPrompt(extraction: L2Extraction, userContext?: string): string {
|
|
152
|
-
const context = userContext
|
|
153
|
-
? `\n## User Context\n${userContext}\n`
|
|
154
|
-
: "";
|
|
155
|
-
|
|
156
|
-
return `You have a structured extraction from "${extraction.sourceTitle}". Now turn it into content directions — things the user could write, post, discuss, or build on.
|
|
157
|
-
|
|
158
|
-
## Extraction Summary
|
|
159
|
-
${extraction.summary}
|
|
160
|
-
|
|
161
|
-
## Key Patterns
|
|
162
|
-
${extraction.patterns.map((p) => `- **${p.pattern}** (${p.strength}): ${p.connection}`).join("\n")}
|
|
163
|
-
|
|
164
|
-
## Notable Claims
|
|
165
|
-
${extraction.claims.slice(0, 5).map((c) => `- ${c.claim} [${c.confidence}]`).join("\n")}
|
|
166
|
-
|
|
167
|
-
## Actionable Items
|
|
168
|
-
${extraction.actionable.map((a) => `- [${a.type}] ${a.description} (${a.priority})`).join("\n")}
|
|
169
|
-
|
|
170
|
-
## Best Quotes
|
|
171
|
-
${extraction.quotes.slice(0, 3).map((q) => `- "${q.text}" — ${q.attribution}`).join("\n")}
|
|
172
|
-
${context}
|
|
173
|
-
## What to Produce
|
|
174
|
-
|
|
175
|
-
Generate 3-5 **content directions** the user could take. For each:
|
|
176
|
-
|
|
177
|
-
1. **Hook** — The opening line or angle (something that would stop a scroll)
|
|
178
|
-
2. **Core argument** — What you'd actually say, grounded in the source material
|
|
179
|
-
3. **Format** — Tweet thread, short post, essay section, newsletter bit, conversation starter
|
|
180
|
-
4. **Source grounding** — Which extraction dimensions feed this (cite specific claims, patterns, quotes)
|
|
181
|
-
5. **Voice note** — How you'd explain this to a friend in 2 sentences (this is the vibe check)
|
|
182
|
-
|
|
183
|
-
Don't be generic. These should feel like things only someone who deeply read this source would write. The user's voice, not the source's voice.`;
|
|
66
|
+
return [
|
|
67
|
+
"Extract structured knowledge from this source material using the 9-dimension schema below.",
|
|
68
|
+
"",
|
|
69
|
+
"## Source",
|
|
70
|
+
"- **URL:** " + sourceUrl,
|
|
71
|
+
"- **Title:** " + sourceTitle,
|
|
72
|
+
"",
|
|
73
|
+
"## Content",
|
|
74
|
+
"<source_content>",
|
|
75
|
+
sourceContent,
|
|
76
|
+
"</source_content>",
|
|
77
|
+
"",
|
|
78
|
+
"## Extraction Schema (9 Dimensions)",
|
|
79
|
+
"",
|
|
80
|
+
"Extract into these dimensions. Be thorough.",
|
|
81
|
+
"",
|
|
82
|
+
"### 1. Summary",
|
|
83
|
+
"2-3 sentences capturing the core contribution.",
|
|
84
|
+
"",
|
|
85
|
+
"### 2. Claims & Arguments",
|
|
86
|
+
"Every claim the source makes, including implicit ones.",
|
|
87
|
+
"For each: the claim, supporting evidence, confidence (high/medium/low), whether implicit, and any notes.",
|
|
88
|
+
"",
|
|
89
|
+
"### 3. Entities",
|
|
90
|
+
"People, organizations, concepts, products, technologies mentioned.",
|
|
91
|
+
"For each: name, type, and their role in the source.",
|
|
92
|
+
"",
|
|
93
|
+
"### 4. Temporal (only if time-sensitive)",
|
|
94
|
+
"Skip if the content is not date-dependent. If it is: key events and timeline notes.",
|
|
95
|
+
"",
|
|
96
|
+
"### 5. Quotes",
|
|
97
|
+
"Direct quotes worth preserving, with attribution and context.",
|
|
98
|
+
"",
|
|
99
|
+
"### 6. Patterns & Themes (highest compound value)",
|
|
100
|
+
"Patterns you see in this material and how they connect to broader work.",
|
|
101
|
+
"For each: the pattern, the connection to operator context, and strength (strong/moderate/emerging).",
|
|
102
|
+
"",
|
|
103
|
+
"### 7. Relevance",
|
|
104
|
+
"Which areas of work this is relevant to and why.",
|
|
105
|
+
"Rate each: high/medium/low with explanation.",
|
|
106
|
+
"",
|
|
107
|
+
"### 8. Actionable",
|
|
108
|
+
"Content seeds, follow-ups, research threads, outreach opportunities.",
|
|
109
|
+
"For each: type, description, priority.",
|
|
110
|
+
"",
|
|
111
|
+
"### 9. Extraction Notes",
|
|
112
|
+
"Your observations about the extraction process itself.",
|
|
113
|
+
"",
|
|
114
|
+
"## Output Format",
|
|
115
|
+
"Return a JSON object with all 9 dimensions. Be specific in patterns and relevance.",
|
|
116
|
+
].join("\n");
|
|
184
117
|
}
|