@zosmaai/pi-llm-wiki 0.2.0 → 0.2.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.
@@ -22,6 +22,36 @@ export interface CaptureResult {
22
22
  extracted: string;
23
23
  }
24
24
 
25
+ const DEFAULT_MARKITDOWN_TIMEOUT_MS = 180_000;
26
+ const DEFAULT_CURL_TIMEOUT_SECONDS = 30;
27
+
28
+ function markitdownTimeoutMs(): number {
29
+ return positiveIntegerFromEnv("WIKI_MARKITDOWN_TIMEOUT_MS", DEFAULT_MARKITDOWN_TIMEOUT_MS);
30
+ }
31
+
32
+ function positiveIntegerFromEnv(name: string, fallback: number): number {
33
+ const raw = process.env[name];
34
+ if (!raw) return fallback;
35
+ const parsed = Number.parseInt(raw, 10);
36
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
37
+ }
38
+
39
+ function isPdfUrl(url: string): boolean {
40
+ try {
41
+ return new URL(url).pathname.toLowerCase().endsWith(".pdf");
42
+ } catch {
43
+ return url.toLowerCase().split(/[?#]/, 1)[0].endsWith(".pdf");
44
+ }
45
+ }
46
+
47
+ function looksLikePdf(content: string): boolean {
48
+ return content.trimStart().startsWith("%PDF-");
49
+ }
50
+
51
+ function pdfExtractionFailureMessage(source: string): string {
52
+ return `_PDF content could not be converted to markdown from ${source}. Try increasing WIKI_MARKITDOWN_TIMEOUT_MS._\n`;
53
+ }
54
+
25
55
  /** Capture a URL into a source packet. */
26
56
  export async function captureUrl(
27
57
  pi: ExtensionAPI,
@@ -38,8 +68,9 @@ export async function captureUrl(
38
68
  // Try to fetch and extract content
39
69
  let extracted = "";
40
70
  let title = url;
71
+ const isPdf = isPdfUrl(url);
41
72
 
42
- // Try markitdown first
73
+ // Try MarkItDown first.
43
74
  const markitdown = await exec(
44
75
  pi,
45
76
  "sh",
@@ -53,7 +84,7 @@ export async function captureUrl(
53
84
  pi,
54
85
  "sh",
55
86
  ["-c", `uvx --from 'markitdown[pdf]' markitdown "${url}" 2>/dev/null || echo ""`],
56
- { signal, timeout: 30_000 },
87
+ { signal, timeout: markitdownTimeoutMs() },
57
88
  );
58
89
  if (mdResult.stdout.trim()) {
59
90
  extracted = mdResult.stdout;
@@ -66,21 +97,35 @@ export async function captureUrl(
66
97
  }
67
98
  }
68
99
 
69
- // Fallback: try fetch_content equivalent via curl
100
+ // Fallback: try fetch_content equivalent via curl for text/html sources.
101
+ // Do not write binary PDF bytes into extracted.md when PDF conversion fails.
70
102
  if (!extracted) {
71
- try {
72
- const curlResult = await exec(pi, "curl", ["-sL", "--max-time", "30", url], {
73
- signal,
74
- timeout: 35_000,
75
- });
76
- if (curlResult.stdout) {
77
- extracted = curlResult.stdout;
78
- // Try to extract title from HTML
79
- const titleMatch = extracted.match(/<title>([^<]*)<\/title>/i);
80
- if (titleMatch) title = titleMatch[1].trim();
103
+ if (isPdf) {
104
+ extracted = pdfExtractionFailureMessage(url);
105
+ } else {
106
+ try {
107
+ const curlResult = await exec(
108
+ pi,
109
+ "curl",
110
+ ["-sL", "--max-time", String(DEFAULT_CURL_TIMEOUT_SECONDS), url],
111
+ {
112
+ signal,
113
+ timeout: (DEFAULT_CURL_TIMEOUT_SECONDS + 5) * 1_000,
114
+ },
115
+ );
116
+ if (curlResult.stdout) {
117
+ if (looksLikePdf(curlResult.stdout)) {
118
+ extracted = pdfExtractionFailureMessage(url);
119
+ } else {
120
+ extracted = curlResult.stdout;
121
+ // Try to extract title from HTML
122
+ const titleMatch = extracted.match(/<title>([^<]*)<\/title>/i);
123
+ if (titleMatch) title = titleMatch[1].trim();
124
+ }
125
+ }
126
+ } catch {
127
+ // curl failed too
81
128
  }
82
- } catch {
83
- // curl failed too
84
129
  }
85
130
  }
86
131
 
@@ -126,12 +171,13 @@ export async function captureFile(
126
171
  mkdirSync(join(packetPath, "original"), { recursive: true });
127
172
  mkdirSync(join(packetPath, "attachments"), { recursive: true });
128
173
 
129
- const content = readText(filePath);
174
+ const isPdf = filePath.toLowerCase().endsWith(".pdf");
175
+ const content = isPdf ? "" : readText(filePath);
130
176
  const fileName = filePath.split("/").pop() || "unknown";
131
177
 
132
- // Try markitdown for PDFs
178
+ // Try MarkItDown for PDFs.
133
179
  let extracted = content;
134
- if (filePath.toLowerCase().endsWith(".pdf")) {
180
+ if (isPdf) {
135
181
  const markitdown = await exec(
136
182
  pi,
137
183
  "sh",
@@ -145,13 +191,14 @@ export async function captureFile(
145
191
  pi,
146
192
  "sh",
147
193
  ["-c", `uvx --from 'markitdown[pdf]' markitdown "${filePath}" 2>/dev/null || echo ""`],
148
- { signal, timeout: 30_000 },
194
+ { signal, timeout: markitdownTimeoutMs() },
149
195
  );
150
196
  if (mdResult.stdout.trim()) extracted = mdResult.stdout;
151
197
  } catch {
152
- // fallback to original
198
+ extracted = pdfExtractionFailureMessage(filePath);
153
199
  }
154
200
  }
201
+ if (!extracted) extracted = pdfExtractionFailureMessage(filePath);
155
202
  }
156
203
 
157
204
  // Copy original to packet
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zosmaai/pi-llm-wiki",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "LLM Wiki for Pi — self-maintaining knowledge base following Karpathy's pattern. Obsidian-friendly, auto-updating, personal & company wiki.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  description: Generate a daily or weekly digest of wiki changes — new sources, pages, insights, and gaps.
3
- args: [--period daily|weekly]
3
+ argument-hint: "[--period daily|weekly]"
4
4
  section: LLM Wiki
5
5
  topLevelCli: true
6
6
  ---
@@ -9,6 +9,10 @@ topLevelCli: true
9
9
 
10
10
  Generate a digest of recent wiki activity.
11
11
 
12
+ ## User Arguments
13
+
14
+ $ARGUMENTS
15
+
12
16
  ## Steps
13
17
 
14
18
  1. Read `wiki/LOG.md` — filter entries since last digest
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  description: Auto-discover new sources from the web. Searches based on config topics and known knowledge gaps.
3
- args: [--topic <topic>]
3
+ argument-hint: "[--topic <topic>]"
4
4
  section: LLM Wiki
5
5
  topLevelCli: true
6
6
  ---
@@ -9,6 +9,10 @@ topLevelCli: true
9
9
 
10
10
  Find new source material for the wiki by searching the web.
11
11
 
12
+ ## User Arguments
13
+
14
+ $ARGUMENTS
15
+
12
16
  Read the LLM Wiki skill at `.pi/skills/llm-wiki/SKILL.md` first. Also read `config.yaml` for topics and feeds.
13
17
 
14
18
  ## Steps
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  description: Process new source files in raw/ and update the wiki. Creates summaries, entities, concepts, and cross-references.
3
- args: [path]
3
+ argument-hint: "[path]"
4
4
  section: LLM Wiki
5
5
  topLevelCli: true
6
6
  ---
@@ -9,6 +9,10 @@ topLevelCli: true
9
9
 
10
10
  Process new files in `raw/` and integrate them into the wiki.
11
11
 
12
+ ## User Arguments
13
+
14
+ $ARGUMENTS
15
+
12
16
  Read the LLM Wiki skill at `.pi/skills/llm-wiki/SKILL.md` first to understand the full schema, page formats, and conventions.
13
17
 
14
18
  ## Steps
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  description: Initialize a new LLM Wiki in the current directory. Creates the full directory structure, config, and template files.
3
- args: <topic> [--mode personal|company]
3
+ argument-hint: "<topic> [--mode personal|company]"
4
4
  section: LLM Wiki
5
5
  topLevelCli: true
6
6
  ---
@@ -9,6 +9,10 @@ topLevelCli: true
9
9
 
10
10
  Initialize a new LLM Wiki in the current directory.
11
11
 
12
+ ## User Arguments
13
+
14
+ $ARGUMENTS
15
+
12
16
  Read the LLM Wiki skill at `.pi/skills/llm-wiki/SKILL.md` (or wherever the skill is installed) first to understand the full schema and conventions.
13
17
 
14
18
  ## Steps
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  description: Health check the wiki. Detects contradictions, orphans, missing pages, stale claims, and knowledge gaps.
3
- args: [--fix]
3
+ argument-hint: "[--fix]"
4
4
  section: LLM Wiki
5
5
  topLevelCli: true
6
6
  ---
@@ -9,6 +9,10 @@ topLevelCli: true
9
9
 
10
10
  Run a comprehensive health check on the wiki.
11
11
 
12
+ ## User Arguments
13
+
14
+ $ARGUMENTS
15
+
12
16
  Read the LLM Wiki skill at `.pi/skills/llm-wiki/SKILL.md` first to understand the full schema and conventions.
13
17
 
14
18
  ## Steps
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  description: Ask questions against the wiki. Synthesizes answers from wiki pages with cross-reference citations.
3
- args: <question>
3
+ argument-hint: "<question>"
4
4
  section: LLM Wiki
5
5
  topLevelCli: true
6
6
  ---
@@ -9,6 +9,10 @@ topLevelCli: true
9
9
 
10
10
  Ask a question and get an answer synthesized from wiki content.
11
11
 
12
+ ## User Question
13
+
14
+ $ARGUMENTS
15
+
12
16
  Read the LLM Wiki skill at `.pi/skills/llm-wiki/SKILL.md` first to understand the full schema and conventions.
13
17
 
14
18
  ## Steps
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  description: Run the full wiki cycle: discover → ingest → lint. Optionally schedule for auto-updates.
3
- args: [--schedule daily|weekly]
3
+ argument-hint: "[--schedule daily|weekly]"
4
4
  section: LLM Wiki
5
5
  topLevelCli: true
6
6
  ---
@@ -9,6 +9,10 @@ topLevelCli: true
9
9
 
10
10
  Run the complete wiki maintenance cycle: discover new sources, ingest them, and lint for health.
11
11
 
12
+ ## User Arguments
13
+
14
+ $ARGUMENTS
15
+
12
16
  Read the LLM Wiki skill at `.pi/skills/llm-wiki/SKILL.md` first.
13
17
 
14
18
  ## Steps
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  description: Show wiki health overview — source count, page stats, orphan count, last activity dates.
3
- args: []
3
+ argument-hint: ""
4
4
  section: LLM Wiki
5
5
  topLevelCli: true
6
6
  ---
@@ -111,11 +111,33 @@ describe("package structure", () => {
111
111
  expect(existsSync(path)).toBe(true);
112
112
  const content = readFile(path);
113
113
  expect(content).toContain("description:");
114
+ expect(content).toContain("argument-hint:");
114
115
  expect(content).toContain("section: LLM Wiki");
115
116
  expect(content).toContain("topLevelCli: true");
117
+ expect(content).not.toContain("\nargs:");
116
118
  }
117
119
  });
118
120
 
121
+ it("should include prompt arguments in templates that accept them", () => {
122
+ const promptsWithArgs = [
123
+ "wiki-init.md",
124
+ "wiki-ingest.md",
125
+ "wiki-query.md",
126
+ "wiki-lint.md",
127
+ "wiki-discover.md",
128
+ "wiki-run.md",
129
+ "wiki-digest.md",
130
+ ];
131
+ for (const prompt of promptsWithArgs) {
132
+ const content = readFile(join(rootDir, "prompts", prompt));
133
+ expect(content).toContain("$ARGUMENTS");
134
+ }
135
+
136
+ const query = readFile(join(rootDir, "prompts", "wiki-query.md"));
137
+ expect(query).toContain("## User Question");
138
+ expect(query).toContain("$ARGUMENTS");
139
+ });
140
+
119
141
  it("should have all wiki template files", () => {
120
142
  const t = join(rootDir, "skills", "llm-wiki", "templates");
121
143
  expect(existsSync(join(t, "INDEX.md"))).toBe(true);
@@ -157,6 +179,17 @@ describe("package structure", () => {
157
179
  }
158
180
  });
159
181
 
182
+ it("should keep MarkItDown timeout configurable and avoid PDF byte fallbacks", () => {
183
+ const sourcePacketPath = join(rootDir, "extensions", "llm-wiki", "lib", "source-packet.ts");
184
+ expect(existsSync(sourcePacketPath)).toBe(true);
185
+ const content = readFile(sourcePacketPath);
186
+ expect(content).toContain("WIKI_MARKITDOWN_TIMEOUT_MS");
187
+ expect(content).toContain("DEFAULT_MARKITDOWN_TIMEOUT_MS = 180_000");
188
+ expect(content).toContain("isPdfUrl(url)");
189
+ expect(content).toContain("looksLikePdf(curlResult.stdout)");
190
+ expect(content).toContain("pdfExtractionFailureMessage");
191
+ });
192
+
160
193
  it("should have a comprehensive README with install instructions", () => {
161
194
  const readme = readFile(join(rootDir, "README.md"));
162
195
  expect(readme).toContain("@zosmaai/pi-llm-wiki");