@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/writer.ts ADDED
@@ -0,0 +1,409 @@
1
+ /** Idempotent config-file scaffolder.
2
+
3
+ Creates the files that ``llmkb init`` places into the project root:
4
+ ``.claude-plugin/plugin.json``, ``.mcp.json``, ``.llmkb/spaces.yml``,
5
+ ``.llmkb/config.yml``, and a version stamp at ``.llmkb/.llmkb-version``.
6
+
7
+ Supports ``--platform cursor`` to write Cursor-specific paths instead.
8
+ */
9
+
10
+ import { mkdir, writeFile } from "node:fs/promises";
11
+ import { existsSync } from "node:fs";
12
+ import { join, resolve } from "node:path";
13
+ import { stringify } from "yaml";
14
+ import {
15
+ PACKAGE_VERSION,
16
+ getDefaultPluginConfig,
17
+ type SpaceConfig,
18
+ type SpaceDef,
19
+ } from "./types.js";
20
+ import { readVersionStamp } from "./parser.js";
21
+ import { writeHooks } from "../src/commands/hooks.js";
22
+
23
+ export interface ScaffoldOptions {
24
+ /** Project root directory (defaults to ``process.cwd()``). */
25
+ projectDir?: string;
26
+ /** Target platform for config file paths. */
27
+ platform?: "claude" | "cursor";
28
+ /** Optional space name to pre-fill in the config. */
29
+ spaceName?: string;
30
+ /** Optional list of spaces for interactive multi-space setup. */
31
+ spaceDefs?: SpaceDef[];
32
+ /** Force overwrite even if version stamp matches. */
33
+ force?: boolean;
34
+ /** Also scaffold hook files. */
35
+ withHooks?: boolean;
36
+ }
37
+
38
+ function projectPath(projectDir: string, ...parts: string[]): string {
39
+ return resolve(projectDir, ...parts);
40
+ }
41
+
42
+ async function ensureDir(filePath: string): Promise<void> {
43
+ await mkdir(filePath, { recursive: true });
44
+ }
45
+
46
+ /** Plugin metadata file (required by Claude Code for discovery). */
47
+ const PLUGIN_JSON = {
48
+ name: "llmkb",
49
+ description:
50
+ "LLM-powered knowledge base with wiki generation, semantic search, and knowledge graphs. Sync codebases, query spaces, and explore architecture from Claude.",
51
+ version: PACKAGE_VERSION,
52
+ author: { name: "llmkb" },
53
+ homepage: "https://llmkb.ai",
54
+ keywords: ["knowledge-base", "wiki", "mcp"],
55
+ };
56
+
57
+ /** MCP server registration (Docker stdio transport via --profile mvp). */
58
+ const MCP_JSON_CLAUDE = {
59
+ mcpServers: {
60
+ llmkb: {
61
+ description:
62
+ "llmkb wiki — search, read, and write your knowledge base via MCP tools",
63
+ command: "docker",
64
+ args: [
65
+ "compose",
66
+ "--profile",
67
+ "mvp",
68
+ "exec",
69
+ "-i",
70
+ "api",
71
+ "python",
72
+ "-m",
73
+ "app.mcp.stdio",
74
+ ],
75
+ },
76
+ },
77
+ };
78
+
79
+ const MCP_JSON_CURSOR = {
80
+ mcpServers: {
81
+ llmkb: {
82
+ description:
83
+ "llmkb wiki — search, read, and write your knowledge base via MCP tools",
84
+ command: "docker",
85
+ args: [
86
+ "compose",
87
+ "--profile",
88
+ "mvp",
89
+ "exec",
90
+ "-i",
91
+ "api",
92
+ "python",
93
+ "-m",
94
+ "app.mcp.stdio",
95
+ ],
96
+ },
97
+ },
98
+ };
99
+
100
+ /** Build the ``spaces.yml`` content as a typed YAML document. */
101
+ function buildSpaceConfig(spaceDefs: SpaceDef[]): SpaceConfig {
102
+ const config: SpaceConfig = {};
103
+ if (spaceDefs.length > 0) {
104
+ config.project_space = [{ id: spaceDefs[0]!.id }];
105
+ config.spaces = spaceDefs;
106
+ }
107
+ return config;
108
+ }
109
+
110
+ /** Build the default ``spaces.yml`` content with optional single-space pre-fill. */
111
+ function spacesYml(projectSpaceId?: string): string {
112
+ if (!projectSpaceId) {
113
+ // Emit a fully commented-out template
114
+ return [
115
+ `# llmkb space configuration`,
116
+ `# Managed by \`llmkb init\`. Edit to add or remove spaces.`,
117
+ `#`,
118
+ `# Example:`,
119
+ `# project_space:`,
120
+ `# - id: b6087faa-0bf0-4935-98b2-f3100905b99c`,
121
+ `# slug: my_project`,
122
+ `# name: "My Project"`,
123
+ `# dirs: ['app', 'src']`,
124
+ `# spaces:`,
125
+ `# - id: c599f13a-2d75-4e4e-8fb3-03afbac7e115`,
126
+ `# name: "Reference Library"`,
127
+ `# slug: reference_library`,
128
+ ``,
129
+ ].join("\n");
130
+ }
131
+
132
+ const config: SpaceConfig = {
133
+ project_space: [{ id: projectSpaceId }],
134
+ };
135
+ return (
136
+ `# llmkb space configuration\n` +
137
+ `# Managed by \`llmkb init\`. Edit to add or remove spaces.\n\n` +
138
+ stringify(config, { lineWidth: 120 })
139
+ );
140
+ }
141
+
142
+ /** Build ``.llmkb/config.yml`` with commented-out defaults. */
143
+ function configYml(): string {
144
+ const defaults = getDefaultPluginConfig();
145
+
146
+ return [
147
+ `# .llmkb/config.yml — plugin behavior settings`,
148
+ `# Managed by \`llmkb init\`. Uncomment fields to override defaults.`,
149
+ ``,
150
+ `# llmkb_base_url: ${defaults.llmkb_base_url}`,
151
+ `#`,
152
+ `# watch:`,
153
+ `# enabled: ${defaults.watch!.enabled}`,
154
+ `# debounce_ms: ${defaults.watch!.debounce_ms}`,
155
+ `# gitignore: ${defaults.watch!.gitignore}`,
156
+ `# llmkbignore: ${defaults.watch!.llmkbignore}`,
157
+ `# default_verbosity: ${defaults.watch!.default_verbosity}`,
158
+ `#`,
159
+ `# sync:`,
160
+ `# concurrency: ${defaults.sync!.concurrency}`,
161
+ `# retry_attempts: ${defaults.sync!.retry_attempts}`,
162
+ `# retry_delay_ms: ${defaults.sync!.retry_delay_ms}`,
163
+ `#`,
164
+ `# hook:`,
165
+ `# timeout_ms: ${defaults.hook!.timeout_ms}`,
166
+ `# skip: ${defaults.hook!.skip}`,
167
+ `#`,
168
+ `# debug: ${defaults.debug}`,
169
+ ``,
170
+ ].join("\n");
171
+ }
172
+
173
+ /** Scaffold .claude-plugin/plugin.json */
174
+ async function writePluginJson(projectDir: string): Promise<void> {
175
+ const dir = projectPath(projectDir, ".claude-plugin");
176
+ await ensureDir(dir);
177
+ await writeFile(
178
+ join(dir, "plugin.json"),
179
+ JSON.stringify(PLUGIN_JSON, null, 2) + "\n",
180
+ "utf-8",
181
+ );
182
+ }
183
+
184
+ /** Scaffold .mcp.json (Claude) or .cursor/mcp.json (Cursor). */
185
+ async function writeMcpJson(
186
+ projectDir: string,
187
+ platform: "claude" | "cursor",
188
+ ): Promise<void> {
189
+ if (platform === "cursor") {
190
+ const dir = projectPath(projectDir, ".cursor");
191
+ await ensureDir(dir);
192
+ await writeFile(
193
+ join(dir, "mcp.json"),
194
+ JSON.stringify(MCP_JSON_CURSOR, null, 2) + "\n",
195
+ "utf-8",
196
+ );
197
+ } else {
198
+ await writeFile(
199
+ projectPath(projectDir, ".mcp.json"),
200
+ JSON.stringify(MCP_JSON_CLAUDE, null, 2) + "\n",
201
+ "utf-8",
202
+ );
203
+ }
204
+ }
205
+
206
+ /** Scaffold .llmkb/spaces.yml, .llmkb/config.yml, and .llmkb/.llmkb-version.
207
+
208
+ Idempotent: if ``.llmkb/.llmkb-version`` exists and matches the current
209
+ package version, all llmkb writes are skipped — the user's customizations
210
+ are preserved. Call ``scaffoldConfig`` with ``force: true`` to bypass.
211
+
212
+ @returns The list of files actually written (empty if skipped by idempotency).
213
+ */
214
+ async function writeLlmkbDir(
215
+ projectDir: string,
216
+ spaceName?: string,
217
+ spaceDefs?: SpaceDef[],
218
+ force?: boolean,
219
+ ): Promise<string[]> {
220
+ const dir = projectPath(projectDir, ".llmkb");
221
+ const written: string[] = [];
222
+
223
+ // Idempotency check: skip if version stamp matches
224
+ if (!force) {
225
+ const stamp = await readVersionStamp(projectDir);
226
+ if (stamp === PACKAGE_VERSION) return written;
227
+ }
228
+
229
+ await ensureDir(dir);
230
+
231
+ // Write spaces.yml
232
+ if (spaceDefs && spaceDefs.length > 0) {
233
+ const config = buildSpaceConfig(spaceDefs);
234
+ await writeFile(
235
+ join(dir, "spaces.yml"),
236
+ stringify(config, { lineWidth: 120 }),
237
+ "utf-8",
238
+ );
239
+ } else {
240
+ await writeFile(
241
+ join(dir, "spaces.yml"),
242
+ spacesYml(spaceName) + "\n",
243
+ "utf-8",
244
+ );
245
+ }
246
+ written.push(".llmkb/spaces.yml");
247
+
248
+ // Write config.yml with commented-out defaults
249
+ await writeFile(join(dir, "config.yml"), configYml(), "utf-8");
250
+ written.push(".llmkb/config.yml");
251
+
252
+ // Write version stamp
253
+ await writeFile(join(dir, ".llmkb-version"), PACKAGE_VERSION + "\n", "utf-8");
254
+ written.push(".llmkb/.llmkb-version");
255
+
256
+ return written;
257
+ }
258
+
259
+ /** All cursor rule files for the llmkb knowledge base. */
260
+ const CURSOR_RULES: Array<{
261
+ name: string;
262
+ description: string;
263
+ globs: string[];
264
+ content: string;
265
+ }> = [
266
+ {
267
+ name: "llmkb-guide.mdc",
268
+ description:
269
+ "llmkb overview, architecture, getting started, and how to use llmkb spaces",
270
+ globs: ["*"],
271
+ content: [
272
+ "llmkb is an AI-powered knowledge base. It uses a three-layer model: raw sources (PDFs, code, markdown) -> LLM-generated wiki pages -> knowledge graph.",
273
+ "",
274
+ "- Run `docker compose --profile mvp ps` to verify the MCP server is running",
275
+ "- Use `space_search(space_name, query)` to find relevant wiki pages",
276
+ "- Use `space_read(space_name, path)` to read a specific wiki page",
277
+ "- Use `space_graph(space_name, mode, entity)` to explore entity relationships",
278
+ "- Check `llmkb status` to verify setup is correct",
279
+ ].join("\n"),
280
+ },
281
+ {
282
+ name: "llmkb-query.mdc",
283
+ description:
284
+ "Search and read wiki content via MCP tools — use space_search and space_read",
285
+ globs: ["*"],
286
+ content: [
287
+ "When asked to find information, always:",
288
+ "",
289
+ "1. Start with `space_search` to locate relevant wiki pages:",
290
+ ` space_search(space_name: "my-project", query: "<user's question>")`,
291
+ "2. Read the full content of the best matching page:",
292
+ ` space_read(space_name: "my-project", path: "/path/to/page")`,
293
+ "3. For relationship context, use:",
294
+ ` space_graph(space_name: "my-project", mode: "neighbours", entity: "<entity name>")`,
295
+ ].join("\n"),
296
+ },
297
+ {
298
+ name: "llmkb-sync.mdc",
299
+ description:
300
+ "File sync and upload rules — run llmkb sync after making significant changes",
301
+ globs: ["*"],
302
+ content: [
303
+ "Files modified under this project should be synced to the configured llmkb space.",
304
+ "",
305
+ "- Run `llmkb sync` from the project root after significant changes",
306
+ "- For continuous syncing: `llmkb sync --watch`",
307
+ "- Files are content-addressed (SHA256); unchanged files skip re-processing",
308
+ "- Respects .gitignore and .llmkbignore patterns",
309
+ "- After syncing, use `llmkb status` to check ingestion progress",
310
+ ].join("\n"),
311
+ },
312
+ {
313
+ name: "llmkb-exploring.mdc",
314
+ description:
315
+ "Architecture and graph exploration — use space_graph for entity relationships",
316
+ globs: ["*"],
317
+ content: [
318
+ "When asked about code architecture or entity relationships:",
319
+ "",
320
+ "1. Start with `space_guide` for a structured overview:",
321
+ ` space_guide(space_name: "my-project")`,
322
+ "2. Explore entity neighbourhoods:",
323
+ ` space_graph(space_name: "my-project", mode: "neighbours", entity: "<entity>")`,
324
+ "3. Find paths between entities:",
325
+ ` space_graph(space_name: "my-project", mode: "path", source: "<source>", target: "<target>")`,
326
+ "4. Follow up with `space_search` for deeper context",
327
+ ].join("\n"),
328
+ },
329
+ {
330
+ name: "llmkb-admin.mdc",
331
+ description:
332
+ "Space management, token setup, credentials, configuration, and llmkb CLI commands",
333
+ globs: ["*"],
334
+ content: [
335
+ "When setting up or managing llmkb:",
336
+ "",
337
+ "- `llmkb init` — scaffold project config files and skills",
338
+ "- `llmkb init --no-with-skills` — config-only setup (skip skills)",
339
+ "- `llmkb init --platform cursor` — use Cursor-compatible paths",
340
+ "- `llmkb login --project-space <id>` — store access token in keychain",
341
+ "- `llmkb whoami` — show current user identity and access token status",
342
+ "- `llmkb status` — verify versions, config, and installed skills",
343
+ "- Set LLMKB_ACCESS_TOKEN env var for CI/headless environments",
344
+ ].join("\n"),
345
+ },
346
+ ];
347
+
348
+ /** Scaffold .cursor/rules/llmkb/ with all rule files. */
349
+ async function writeCursorRules(projectDir: string): Promise<void> {
350
+ const rulesDir = projectPath(projectDir, ".cursor", "rules", "llmkb");
351
+ await ensureDir(rulesDir);
352
+ for (const rule of CURSOR_RULES) {
353
+ const frontmatter = [
354
+ "---",
355
+ `name: ${rule.name}`,
356
+ `description: ${rule.description}`,
357
+ `globs: ${JSON.stringify(rule.globs)}`,
358
+ "---",
359
+ "",
360
+ ].join("\n");
361
+ await writeFile(
362
+ join(rulesDir, rule.name),
363
+ frontmatter + rule.content + "\n",
364
+ "utf-8",
365
+ );
366
+ }
367
+ }
368
+
369
+ /** Run the full scaffold. Creates all config files idempotently. */
370
+ export async function scaffoldConfig(
371
+ opts: ScaffoldOptions,
372
+ ): Promise<{ filesWritten: string[] }> {
373
+ const projectDir = opts.projectDir ?? process.cwd();
374
+ const platform = opts.platform ?? "claude";
375
+ const spaceName = opts.spaceName;
376
+ const spaceDefs = opts.spaceDefs;
377
+
378
+ const filesWritten: string[] = [];
379
+
380
+ await writePluginJson(projectDir);
381
+ filesWritten.push(".claude-plugin/plugin.json");
382
+
383
+ await writeMcpJson(projectDir, platform);
384
+ filesWritten.push(platform === "cursor" ? ".cursor/mcp.json" : ".mcp.json");
385
+
386
+ if (platform === "cursor") {
387
+ await writeCursorRules(projectDir);
388
+ filesWritten.push(".cursor/rules/llmkb/ (5 rule files)");
389
+ }
390
+
391
+ const llmkbFiles = await writeLlmkbDir(
392
+ projectDir,
393
+ spaceName,
394
+ spaceDefs,
395
+ opts.force,
396
+ );
397
+ for (const f of llmkbFiles) {
398
+ filesWritten.push(f);
399
+ }
400
+
401
+ if (opts.withHooks) {
402
+ const hookFiles = await writeHooks(projectDir, opts.force);
403
+ for (const f of hookFiles) {
404
+ filesWritten.push(f);
405
+ }
406
+ }
407
+
408
+ return { filesWritten };
409
+ }
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@llmkb/claude-code",
3
+ "version": "0.1.0",
4
+ "description": "Claude Code plugin for llmkb — search, read, and write your knowledge base via MCP tools",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "bin": {
8
+ "llmkb": "./dist/cli.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "lib",
13
+ "skills"
14
+ ],
15
+ "scripts": {
16
+ "build": "tsup",
17
+ "dev": "tsup --watch",
18
+ "start": "tsx bin/cli.ts",
19
+ "test": "vitest run",
20
+ "test:watch": "vitest",
21
+ "typecheck": "tsc --noEmit"
22
+ },
23
+ "dependencies": {
24
+ "chokidar": "^4.0.3",
25
+ "cli-progress": "^3.12.0",
26
+ "commander": "^13.1.0",
27
+ "conf": "^13.1.0",
28
+ "ignore": "^7.0.3",
29
+ "keychain": "^1.5.0",
30
+ "picocolors": "^1.1.1",
31
+ "tus-js-client": "^4.3.0",
32
+ "yaml": "^2.7.0"
33
+ },
34
+ "devDependencies": {
35
+ "@types/cli-progress": "^3.11.6",
36
+ "@types/node": "^22.0.0",
37
+ "tsup": "^8.4.0",
38
+ "tsx": "^4.19.0",
39
+ "typescript": "^5.7.0",
40
+ "vitest": "^3.0.0"
41
+ },
42
+ "publishConfig": {
43
+ "access": "public"
44
+ },
45
+ "keywords": [
46
+ "llmkb",
47
+ "claude-code",
48
+ "mcp",
49
+ "knowledge-base",
50
+ "wiki"
51
+ ],
52
+ "engines": {
53
+ "node": ">=20"
54
+ }
55
+ }