@llmkb/claude-code 0.1.0

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.
package/lib/skills.ts ADDED
@@ -0,0 +1,554 @@
1
+ /** Skill installer for ``llmkb init``.
2
+
3
+ Copies built-in SKILL.md files from the package into
4
+ ``.claude/skills/llmkb/<name>/`` and stamps each skill directory with
5
+ ``.llmkb-version`` so ``llmkb status`` can detect version mismatches.
6
+
7
+ For Cursor, writes ``.mdc`` rule files to ``.cursor/rules/llmkb/`` instead.
8
+ */
9
+
10
+ import { mkdir, writeFile, readFile } from "node:fs/promises";
11
+ import { existsSync } from "node:fs";
12
+ import { join } from "node:path";
13
+
14
+ import { PACKAGE_VERSION } from "./types.js";
15
+
16
+ export interface SkillDef {
17
+ name: string;
18
+ description: string;
19
+ body: string;
20
+ }
21
+
22
+ /** Built-in llmkb skill definitions. */
23
+ const SKILLS: SkillDef[] = [
24
+ {
25
+ name: "llmkb-guide",
26
+ description:
27
+ "Use when the user asks how llmkb works, how to get started, or what llmkb can do. Examples: 'How do I use llmkb?', 'What is llmkb?', 'Get started with llmkb', 'Show me the llmkb architecture'",
28
+ body: [
29
+ "## Overview",
30
+ "",
31
+ "llmkb is an AI-powered knowledge base that transforms documents into an organized, interlinked wiki and knowledge graph. It uses a three-layer model: raw sources (PDFs, code, markdown) → LLM-generated wiki pages → structured knowledge graph with entity relationships.",
32
+ "",
33
+ "## Quick Start",
34
+ "",
35
+ "```",
36
+ "llmkb status -- check your current configuration",
37
+ "llmkb whoami -- show active space and credentials",
38
+ "docker compose --profile mvp ps -- verify MCP server is running",
39
+ "```",
40
+ "",
41
+ "## Workflow",
42
+ "",
43
+ "### 1. Verify Setup",
44
+ "",
45
+ "Start by checking that everything is configured:",
46
+ "",
47
+ "- Run `llmkb status` to confirm version stamps match",
48
+ "- Run `llmkb whoami` to verify credentials are stored",
49
+ "- Check `docker compose --profile mvp ps` to confirm the API container is running",
50
+ "",
51
+ "### 2. Search the Wiki",
52
+ "",
53
+ "Use `space_search` with a specific query targeting your space:",
54
+ "",
55
+ "```",
56
+ "-- space_search example --",
57
+ `space_search(space_name: "my-project", query: "authentication flow")`,
58
+ "-- result: ranked list of wiki pages with snippets and confidence scores --",
59
+ "```",
60
+ "",
61
+ "### 3. Read a Page",
62
+ "",
63
+ "Get the full content of a wiki page:",
64
+ "",
65
+ "```",
66
+ "-- space_read example --",
67
+ `space_read(space_name: "my-project", path: "/docs/architecture")`,
68
+ "-- result: full wiki content with frontmatter, body, and metadata --",
69
+ "```",
70
+ "",
71
+ "### 4. Explore the Graph",
72
+ "",
73
+ "When you need to understand how concepts connect:",
74
+ "",
75
+ "```",
76
+ "-- space_graph example --",
77
+ `space_graph(space_name: "my-project", mode: "neighbours", entity: "AuthModule")`,
78
+ "-- result: entity nodes, relationship edges, and community clusters --",
79
+ "```",
80
+ "",
81
+ "### 5. Save New Information",
82
+ "",
83
+ "Write directly to a space when you discover something new:",
84
+ "",
85
+ "```",
86
+ "-- space_write example --",
87
+ `space_write(`,
88
+ ` space_name: "my-project",`,
89
+ ` path: "/notes/api-pattern",`,
90
+ ` content: "# API Pattern\\nThe project uses repository pattern with SQLAlchemy."`,
91
+ `)`,
92
+ "-- result: new wiki page created in the space --",
93
+ "```",
94
+ "",
95
+ "## Troubleshooting",
96
+ "",
97
+ "| Error | Cause | Fix |",
98
+ "|-------|-------|-----|",
99
+ '| "Space not found" | Wrong space name | Run `llmkb use` to see configured spaces |',
100
+ '| "Token required" | Private space needs auth | Run `llmkb login --space <name>` |',
101
+ "| MCP server timeout | Docker not running | `docker compose --profile mvp ps` to check |",
102
+ "| Version mismatch | Skills or config out of date | `llmkb init` to re-install |",
103
+ "",
104
+ "## Self-Check",
105
+ "",
106
+ "- [ ] Active space configured via `llmkb status`",
107
+ "- [ ] Tokens stored in keychain or env vars",
108
+ "- [ ] MCP server reachable: `docker compose --profile mvp ps`",
109
+ "- [ ] Version stamps match package version",
110
+ ].join("\n"),
111
+ },
112
+ {
113
+ name: "llmkb-query",
114
+ description:
115
+ "Use when searching or reading wiki content via MCP tools. Examples: 'Search the wiki for X', 'Read this wiki page', 'Find relevant documentation', 'Look up authentication patterns', 'What does the wiki say about pgvector?'",
116
+ body: [
117
+ "## Overview",
118
+ "",
119
+ "Search and read your llmkb wiki using hybrid full-text + vector search and knowledge graph navigation. Start with `space_search` to find relevant pages, then use `space_read` to get full content.",
120
+ "",
121
+ "## Quick Start",
122
+ "",
123
+ "```",
124
+ "-- search for information --",
125
+ `space_search(space_name: "my-project", query: "pgvector indexing")`,
126
+ "",
127
+ "-- read a specific page --",
128
+ `space_read(space_name: "my-project", path: "/docs/indexing-strategy")`,
129
+ "```",
130
+ "",
131
+ "## Workflow",
132
+ "",
133
+ "### 1. Find Relevant Content",
134
+ "",
135
+ "Start with `space_search` to locate relevant wiki pages:",
136
+ "",
137
+ "```",
138
+ "-- search with a specific query --",
139
+ `space_search(space_name: "my-project", query: "authentication flow")`,
140
+ "",
141
+ "-- search with confidence filter --",
142
+ `space_search(space_name: "my-project", query: "database schema", min_confidence: 0.8)`,
143
+ "```",
144
+ "",
145
+ "Returns ranked results with paths, snippets, confidence scores, and entity types.",
146
+ "",
147
+ "### 2. Read Full Content",
148
+ "",
149
+ "Once you have identified the right page, read it fully:",
150
+ "",
151
+ "```",
152
+ "-- read by path --",
153
+ `space_read(space_name: "my-project", path: "/docs/architecture")`,
154
+ "",
155
+ "-- result includes frontmatter (type, category, source_pages) and full body content --",
156
+ "```",
157
+ "",
158
+ "### 3. Get a Space Overview",
159
+ "",
160
+ "For a structured summary of a space's content:",
161
+ "",
162
+ "```",
163
+ "-- overview of all entities and pages --",
164
+ `space_guide(space_name: "my-project")`,
165
+ "-- result: summary of entity types, page categories, and top-level relationships --",
166
+ "```",
167
+ "",
168
+ "### 4. Explore Entity Relationships",
169
+ "",
170
+ "When search results surface an entity, dig into its connections:",
171
+ "",
172
+ "```",
173
+ "-- find neighbours of a specific entity --",
174
+ `space_graph(space_name: "my-project", mode: "neighbours", entity: "AuthModule")`,
175
+ "-- result: all entities directly connected to AuthModule with relationship types --",
176
+ "```",
177
+ "",
178
+ "## Troubleshooting",
179
+ "",
180
+ "| Error | Cause | Fix |",
181
+ "|-------|-------|-----|",
182
+ "| Empty results | Query too narrow | Broaden terms or check space has content with `space_guide` |",
183
+ '| "Space not found" | Wrong space name | `llmkb status` to list configured spaces |',
184
+ "| Path not found | Page path incorrect | Search first with `space_search` to find the exact path |",
185
+ "| Token error | Expired credentials | `llmkb login --space <name>` |",
186
+ "",
187
+ "## Self-Check",
188
+ "",
189
+ "- [ ] Used `space_search` first to find relevant pages",
190
+ "- [ ] Used `space_read` to get full context when snippet is insufficient",
191
+ "- [ ] Verified the space name matches a configured space",
192
+ "- [ ] Used `space_graph` for deeper relationship exploration when needed",
193
+ ].join("\n"),
194
+ },
195
+ {
196
+ name: "llmkb-sync",
197
+ description:
198
+ "Use when syncing local files to an llmkb space for ingestion. Examples: 'Upload these docs', 'Sync this folder to my wiki', 'Ingest this README', 'Watch this directory for changes', 'Upload my codebase'",
199
+ body: [
200
+ "## Overview",
201
+ "",
202
+ "Sync local files and codebases to your llmkb spaces for processing and wiki generation. Files are content-addressed (SHA256) so unchanged files skip re-processing. Supports PDF, Markdown, code files, images, and audio.",
203
+ "",
204
+ "## Quick Start",
205
+ "",
206
+ "```",
207
+ "llmkb sync -- sync current directory to active space",
208
+ "llmkb sync --space my-project -- sync to a specific space",
209
+ "llmkb sync --watch -- watch directory for changes",
210
+ "```",
211
+ "",
212
+ "## Workflow",
213
+ "",
214
+ "### 1. Sync Files to a Space",
215
+ "",
216
+ "Navigate to the directory containing files you want to ingest and run:",
217
+ "",
218
+ "```",
219
+ "cd /path/to/your/project",
220
+ "llmkb sync --space my-project",
221
+ "```",
222
+ "",
223
+ "The command will:",
224
+ "",
225
+ "1. Walk the directory recursively",
226
+ "2. Compute SHA256 hashes for each file",
227
+ "3. Skip unchanged files (cached in `.llmkb/sync-state.json`)",
228
+ "4. Upload new/changed files via TUS protocol",
229
+ "5. Report progress per file",
230
+ "",
231
+ "### 2. Watch for Changes",
232
+ "",
233
+ "Keep a directory synced automatically:",
234
+ "",
235
+ "```",
236
+ "llmkb sync --watch --space my-project",
237
+ "```",
238
+ "",
239
+ "Watcher behaviour:",
240
+ "",
241
+ "- Respects `.gitignore` and `.llmkbignore` patterns",
242
+ "- Debounces rapid file changes",
243
+ "- Detects file renames by content hash (not path)",
244
+ "- Press Ctrl+C to stop watching",
245
+ "",
246
+ "### 3. Check Sync Status",
247
+ "",
248
+ "After syncing, verify ingestion progress:",
249
+ "",
250
+ "```",
251
+ "llmkb status --space my-project",
252
+ "```",
253
+ "",
254
+ "### 4. Query Synced Content",
255
+ "",
256
+ "Once processing completes, search the newly ingested content:",
257
+ "",
258
+ "```",
259
+ `space_search(space_name: "my-project", query: "topics from the synced files")`,
260
+ "```",
261
+ "",
262
+ "## Troubleshooting",
263
+ "",
264
+ "| Error | Cause | Fix |",
265
+ "|-------|-------|-----|",
266
+ "| File skipped | Matches .gitignore or .llmkbignore | Check ignore files |",
267
+ "| Upload failed | TUS endpoint unreachable | Verify Docker is running |",
268
+ "| Hash mismatch | File changed during upload | Re-run `llmkb sync` |",
269
+ '| "No space configured" | No active space | `llmkb use <space-name>` |',
270
+ "| Sync slow | Large directory | Use `--verbose` to see progress; files over 5MB use chunked TUS uploads |",
271
+ "",
272
+ "## Self-Check",
273
+ "",
274
+ "- [ ] Files upload successfully (check terminal output for each file)",
275
+ "- [ ] Ingestion job progresses through phases",
276
+ "- [ ] Wiki pages generated after processing completes",
277
+ "- [ ] Unchanged files are skipped (cached by SHA256)",
278
+ ].join("\n"),
279
+ },
280
+ {
281
+ name: "llmkb-exploring",
282
+ description:
283
+ "Use when exploring architecture, codebase structure, and entity relationships. Examples: 'How does X work?', 'Show me the codebase architecture', 'What connections exist in this space?', 'Find relationships between these concepts', 'Map the auth module dependencies'",
284
+ body: [
285
+ "## Overview",
286
+ "",
287
+ "Explore entity relationships, knowledge graph structure, and how different pieces of information connect in your llmkb spaces. Use graph queries to discover neighbours, find paths between entities, and get structured space overviews.",
288
+ "",
289
+ "## Quick Start",
290
+ "",
291
+ "```",
292
+ "-- explore neighbours of an entity --",
293
+ `space_graph(space_name: "my-project", mode: "neighbours", entity: "AuthModule")`,
294
+ "",
295
+ "-- find path between two entities --",
296
+ `space_graph(space_name: "my-project", mode: "path", source: "ServiceA", target: "ServiceB")`,
297
+ "```",
298
+ "",
299
+ "## Workflow",
300
+ "",
301
+ "### 1. Get a Space Overview",
302
+ "",
303
+ "Start with `space_guide` for a high-level summary:",
304
+ "",
305
+ "```",
306
+ `space_guide(space_name: "my-project")`,
307
+ "-- result: entity count, relationship count, page categories, top-level clusters --",
308
+ "```",
309
+ "",
310
+ "### 2. Explore Neighbourhoods",
311
+ "",
312
+ "Drill into a specific entity to see its direct connections:",
313
+ "",
314
+ "```",
315
+ `space_graph(space_name: "my-project", mode: "neighbours", entity: "AuthModule")`,
316
+ "-- result: all entities directly connected to AuthModule with relationship types and confidence scores --",
317
+ "```",
318
+ "",
319
+ "### 3. Find Paths",
320
+ "",
321
+ "Discover how two entities connect through intermediate nodes:",
322
+ "",
323
+ "```",
324
+ `space_graph(space_name: "my-project", mode: "path", source: "Database", target: "UserService")`,
325
+ "-- result: shortest path showing each hop with relationship type --",
326
+ "```",
327
+ "",
328
+ "### 4. Search Within the Exploration",
329
+ "",
330
+ "When you find interesting entities, search for more details:",
331
+ "",
332
+ "```",
333
+ `space_search(space_name: "my-project", query: "user authentication")`,
334
+ "```",
335
+ "",
336
+ "## Troubleshooting",
337
+ "",
338
+ "| Error | Cause | Fix |",
339
+ "|-------|-------|-----|",
340
+ "| Empty graph | Space has no processed content | Run `llmkb sync` to upload files first |",
341
+ '| "Entity not found" | Wrong entity name | Use `space_search` to find the exact label |',
342
+ "| No path found | Entities not connected in the graph | Try broader search to find bridging entities |",
343
+ "| Graph too large | Too many neighbours | Narrow the entity or use `space_search` instead |",
344
+ "",
345
+ "## Self-Check",
346
+ "",
347
+ "- [ ] Started with `space_guide` for orientation",
348
+ "- [ ] Used `space_graph` with `neighbours` mode for direct connections",
349
+ "- [ ] Used `space_graph` with `path` mode for indirect connections",
350
+ "- [ ] Followed up with `space_search` for deeper context",
351
+ ].join("\n"),
352
+ },
353
+ {
354
+ name: "llmkb-admin",
355
+ description:
356
+ "Use when managing spaces, tokens, configuration, or credentials. Examples: 'Show my space members', 'Configure a new space', 'Manage API tokens', 'Switch active space', 'Check my credentials', 'Set up a new project with llmkb'",
357
+ body: [
358
+ "## Overview",
359
+ "",
360
+ "Manage llmkb spaces, API tokens, configuration settings, and credentials. The CLI provides commands for switching spaces, authenticating, and verifying setup.",
361
+ "",
362
+ "## Quick Start",
363
+ "",
364
+ "```",
365
+ "llmkb status -- check version stamps and active config",
366
+ "llmkb use my-project -- switch active space",
367
+ "llmkb login --space admin -- store credentials for a space",
368
+ "```",
369
+ "",
370
+ "## Workflow",
371
+ "",
372
+ "### 1. Initialise a Project",
373
+ "",
374
+ "Set up a new project with llmkb:",
375
+ "",
376
+ "```",
377
+ "cd /path/to/project",
378
+ "llmkb init --space my-project",
379
+ "```",
380
+ "",
381
+ "This creates:",
382
+ "",
383
+ "- `.claude-plugin/plugin.json` — plugin metadata for discovery",
384
+ "- `.mcp.json` — MCP server registration with Docker stdio transport",
385
+ "- `.llmkb/spaces.yml` — space configuration",
386
+ "- `.llmkb/config.yml` — plugin behaviour settings",
387
+ "- `.claude/skills/llmkb/` — 5 Claude Code skills",
388
+ "",
389
+ "For Cursor: `llmkb init --platform cursor` writes equivalent `.cursor/` files.",
390
+ "",
391
+ "To skip skill installation: `llmkb init --no-with-skills` (for config-only setup).",
392
+ "",
393
+ "### 2. Authenticate",
394
+ "",
395
+ "Store credentials for a space after init:",
396
+ "",
397
+ "```",
398
+ "llmkb login --space my-project",
399
+ "```",
400
+ "",
401
+ "This stores the space token in the OS keychain. For CI/headless environments, set the `LLMKB_TOKEN` environment variable instead.",
402
+ "",
403
+ "### 3. Switch Spaces",
404
+ "",
405
+ "When working with multiple spaces:",
406
+ "",
407
+ "```",
408
+ "llmkb use client-project",
409
+ "llmkb login --space client-project",
410
+ "llmkb status --space client-project",
411
+ "```",
412
+ "",
413
+ "### 4. Verify Setup",
414
+ "",
415
+ "Check that everything is configured correctly:",
416
+ "",
417
+ "```",
418
+ "llmkb status",
419
+ "llmkb whoami",
420
+ "```",
421
+ "",
422
+ "The `status` command warns on:",
423
+ "",
424
+ "- Config version mismatch: `llmkb init` to update",
425
+ "- Skill version mismatch: `llmkb init` to re-install skills",
426
+ "- Missing or expired tokens",
427
+ "",
428
+ "### 5. Token Management",
429
+ "",
430
+ "- Space tokens grant read access to a specific space",
431
+ "- Admin tokens grant write/admin access",
432
+ "- Tokens are stored in the OS keychain (requires `keychain` package)",
433
+ "- Set `LLMKB_TOKEN` / `LLMKB_ADMIN_TOKEN` env vars for CI",
434
+ "- Token groups allow sharing a single token across multiple spaces",
435
+ "",
436
+ "## Troubleshooting",
437
+ "",
438
+ "| Error | Cause | Fix |",
439
+ "|-------|-------|-----|",
440
+ '| "Version mismatch" | Config or skills out of date | `llmkb init` to update |',
441
+ '| "Not found: .llmkb/" | Project not initialised | `llmkb init` first |',
442
+ "| Keychain access denied | Missing keychain permission | Fall back to `LLMKB_TOKEN` env var |",
443
+ "| Token expired | Credentials rotated | `llmkb login --space <name>` |",
444
+ "| Multiple spaces confusing | Wrong active space | `llmkb use <name>` to switch |",
445
+ "",
446
+ "## Self-Check",
447
+ "",
448
+ "- [ ] Project initialised with `llmkb init`",
449
+ "- [ ] Tokens stored and accessible (keychain or env vars)",
450
+ "- [ ] Correct space is active via `llmkb use`",
451
+ "- [ ] Version stamps match package version (`llmkb status`)",
452
+ "- [ ] MCP server is running: `docker compose --profile mvp ps`",
453
+ ].join("\n"),
454
+ },
455
+ ];
456
+
457
+ /**
458
+ * Install all built-in skills to the appropriate platform path.
459
+ *
460
+ * For Claude Code: writes SKILL.md files to `.claude/skills/llmkb/<name>/`.
461
+ * For Cursor: writes `.mdc` rule files to `.cursor/rules/llmkb/`.
462
+ *
463
+ * Creates idempotent copies — safe to re-run.
464
+ *
465
+ * @param platform Target platform: "claude" (default) or "cursor".
466
+ * @returns The number of skills installed.
467
+ */
468
+ export async function installSkills(
469
+ platform: "claude" | "cursor" = "claude",
470
+ projectDir?: string,
471
+ ): Promise<number> {
472
+ if (platform === "cursor") {
473
+ // Cursor skills are handled by writer.ts writeCursorRules()
474
+ // This function only handles Claude Code SKILL.md installation
475
+ return 0;
476
+ }
477
+
478
+ const projectRoot = projectDir ?? process.cwd();
479
+ const targetDir = join(projectRoot, ".claude", "skills", "llmkb");
480
+
481
+ for (const skill of SKILLS) {
482
+ const skillDir = join(targetDir, skill.name);
483
+ await mkdir(skillDir, { recursive: true });
484
+
485
+ const trigger = skill.name.replace("llmkb-", "/llmkb-");
486
+
487
+ const frontmatter = [
488
+ "---",
489
+ `name: ${skill.name}`,
490
+ `description: ${skill.description}`,
491
+ `trigger: ${trigger}`,
492
+ "---",
493
+ "",
494
+ ].join("\n");
495
+
496
+ await writeFile(
497
+ join(skillDir, "SKILL.md"),
498
+ frontmatter + skill.body + "\n",
499
+ "utf-8",
500
+ );
501
+
502
+ // Version stamp
503
+ await writeFile(
504
+ join(skillDir, ".llmkb-version"),
505
+ PACKAGE_VERSION + "\n",
506
+ "utf-8",
507
+ );
508
+ }
509
+
510
+ return SKILLS.length;
511
+ }
512
+
513
+ /**
514
+ * Check version stamps of all installed skills against the current package version.
515
+ *
516
+ * @param skillsDir Absolute path to `.claude/skills/llmkb/`.
517
+ * @returns Array of version mismatches, one per skill directory.
518
+ */
519
+ export async function checkSkillVersions(
520
+ skillsDir: string,
521
+ ): Promise<{ name: string; installed: string; current: string }[]> {
522
+ const mismatches: { name: string; installed: string; current: string }[] = [];
523
+
524
+ if (!existsSync(skillsDir)) return mismatches;
525
+
526
+ const { readdir } = await import("node:fs/promises");
527
+ const entries = await readdir(skillsDir, { withFileTypes: true });
528
+
529
+ for (const entry of entries) {
530
+ if (!entry.isDirectory()) continue;
531
+
532
+ const versionPath = join(skillsDir, entry.name, ".llmkb-version");
533
+ try {
534
+ const content = await readFile(versionPath, "utf-8");
535
+ const installedVersion = content.split("\n")[0]?.trim();
536
+ if (installedVersion && installedVersion !== PACKAGE_VERSION) {
537
+ mismatches.push({
538
+ name: entry.name,
539
+ installed: installedVersion,
540
+ current: PACKAGE_VERSION,
541
+ });
542
+ }
543
+ } catch {
544
+ // No version stamp file — treat as mismatch
545
+ mismatches.push({
546
+ name: entry.name,
547
+ installed: "(none)",
548
+ current: PACKAGE_VERSION,
549
+ });
550
+ }
551
+ }
552
+
553
+ return mismatches;
554
+ }