@theglitchking/semantic-pages 0.8.0 → 0.9.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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/dist/cli/index.js +13 -118
- package/dist/cli/index.js.map +1 -1
- package/hooks/reconcile.js +90 -0
- package/hooks/session-start.js +12 -281
- package/package.json +3 -2
- package/scripts/link-skills.js +17 -197
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
},
|
|
7
7
|
"metadata": {
|
|
8
8
|
"description": "Official marketplace for semantic-pages - Semantic search + knowledge graph MCP server with auto-wiring for .claude/.vault and hit-em-with-the-docs companion",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.9.0"
|
|
10
10
|
},
|
|
11
11
|
"plugins": [
|
|
12
12
|
{
|
|
13
13
|
"name": "semantic-pages",
|
|
14
14
|
"description": "Semantic search + knowledge graph MCP server for markdown vaults. Auto-wires a read/write vault at .claude/.vault, and a read-only docs index at .documentation when hit-em-with-the-docs is also installed.",
|
|
15
|
-
"version": "0.
|
|
15
|
+
"version": "0.9.0",
|
|
16
16
|
"author": {
|
|
17
17
|
"name": "TheGlitchKing"
|
|
18
18
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "semantic-pages",
|
|
3
3
|
"description": "Semantic search + knowledge graph MCP server for markdown vaults. Auto-wires a read/write vault at .claude/.vault, and a read-only docs index at .documentation when hit-em-with-the-docs is also installed.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.9.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "TheGlitchKing",
|
|
7
7
|
"email": "theglitchking@users.noreply.github.com"
|
package/dist/cli/index.js
CHANGED
|
@@ -2,85 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli/index.ts
|
|
4
4
|
import { program } from "commander";
|
|
5
|
-
import { resolve,
|
|
6
|
-
import { existsSync
|
|
5
|
+
import { resolve, join } from "path";
|
|
6
|
+
import { existsSync } from "fs";
|
|
7
7
|
import { createRequire } from "module";
|
|
8
8
|
import { spawnSync } from "child_process";
|
|
9
|
-
import {
|
|
9
|
+
import { registerUpdateCommands } from "@theglitchking/claude-plugin-runtime";
|
|
10
10
|
var require_ = createRequire(import.meta.url);
|
|
11
11
|
var { version } = require_("../../package.json");
|
|
12
12
|
var PKG_NAME = "@theglitchking/semantic-pages";
|
|
13
|
-
var VALID_POLICIES = ["auto", "nudge", "off"];
|
|
14
|
-
function readJsonSafe(p, fallback = null) {
|
|
15
|
-
try {
|
|
16
|
-
const raw = readFileSync(p, "utf8");
|
|
17
|
-
return raw.trim() ? JSON.parse(raw) : fallback;
|
|
18
|
-
} catch {
|
|
19
|
-
return fallback;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
function writeJsonFile(p, value) {
|
|
23
|
-
mkdirSync(dirname(p), { recursive: true });
|
|
24
|
-
writeFileSync(p, JSON.stringify(value, null, 2) + "\n");
|
|
25
|
-
}
|
|
26
|
-
function configPath(cwd = process.cwd()) {
|
|
27
|
-
return join(cwd, ".claude", "semantic-pages.json");
|
|
28
|
-
}
|
|
29
|
-
function currentPolicy(cwd = process.cwd()) {
|
|
30
|
-
const env = process.env.SEMANTIC_PAGES_UPDATE_POLICY;
|
|
31
|
-
if (env && VALID_POLICIES.includes(env)) return env;
|
|
32
|
-
const cfg = readJsonSafe(configPath(cwd));
|
|
33
|
-
const p = cfg?.updatePolicy;
|
|
34
|
-
if (p && VALID_POLICIES.includes(p)) return p;
|
|
35
|
-
return "nudge";
|
|
36
|
-
}
|
|
37
|
-
function installedVersion(cwd = process.cwd()) {
|
|
38
|
-
const localPkg = join(cwd, "node_modules", "@theglitchking", "semantic-pages", "package.json");
|
|
39
|
-
const pkg = readJsonSafe(localPkg);
|
|
40
|
-
if (pkg?.version) return pkg.version;
|
|
41
|
-
return version;
|
|
42
|
-
}
|
|
43
|
-
async function fetchLatestVersion(timeoutMs = 5e3) {
|
|
44
|
-
const ctrl = new AbortController();
|
|
45
|
-
const timer = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
46
|
-
try {
|
|
47
|
-
const res = await fetch(`https://registry.npmjs.org/${PKG_NAME}/latest`, {
|
|
48
|
-
signal: ctrl.signal,
|
|
49
|
-
headers: { accept: "application/json" }
|
|
50
|
-
});
|
|
51
|
-
if (!res.ok) return null;
|
|
52
|
-
const json = await res.json();
|
|
53
|
-
return json.version ?? null;
|
|
54
|
-
} catch {
|
|
55
|
-
return null;
|
|
56
|
-
} finally {
|
|
57
|
-
clearTimeout(timer);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
function runNpmUpdate(cwd) {
|
|
61
|
-
return spawnSync("npm", ["update", PKG_NAME], { cwd, stdio: "inherit" });
|
|
62
|
-
}
|
|
63
13
|
function runRelink(cwd) {
|
|
64
14
|
const linker = join(cwd, "node_modules", "@theglitchking", "semantic-pages", "scripts", "link-skills.js");
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
return 1;
|
|
70
|
-
}
|
|
71
|
-
const r2 = spawnSync(process.execPath, [localLinker], {
|
|
72
|
-
cwd,
|
|
73
|
-
env: { ...process.env, INIT_CWD: cwd },
|
|
74
|
-
stdio: "inherit"
|
|
75
|
-
});
|
|
76
|
-
return r2.status ?? 1;
|
|
15
|
+
const script = existsSync(linker) ? linker : resolve(process.cwd(), "scripts", "link-skills.js");
|
|
16
|
+
if (!existsSync(script)) {
|
|
17
|
+
console.error("link-skills.js not found \u2014 is the package installed?");
|
|
18
|
+
return;
|
|
77
19
|
}
|
|
78
|
-
|
|
20
|
+
spawnSync(process.execPath, [script], {
|
|
79
21
|
cwd,
|
|
80
22
|
env: { ...process.env, INIT_CWD: cwd },
|
|
81
23
|
stdio: "inherit"
|
|
82
24
|
});
|
|
83
|
-
return r.status ?? 1;
|
|
84
25
|
}
|
|
85
26
|
var TOOL_HELP = {
|
|
86
27
|
// Search
|
|
@@ -330,57 +271,11 @@ program.command("serve", { isDefault: true }).description("Start the MCP server
|
|
|
330
271
|
readOnly: opts.readOnly
|
|
331
272
|
});
|
|
332
273
|
});
|
|
333
|
-
program
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
if (r.status !== 0) {
|
|
339
|
-
console.error("npm update failed.");
|
|
340
|
-
process.exit(r.status ?? 1);
|
|
341
|
-
}
|
|
342
|
-
const after = installedVersion(cwd);
|
|
343
|
-
console.log(`Now: ${after ?? "(not installed)"}`);
|
|
344
|
-
if (after && before && after !== before) runRelink(cwd);
|
|
345
|
-
process.exit(0);
|
|
346
|
-
});
|
|
347
|
-
program.command("policy [mode]").description(`Show or set the update policy (${VALID_POLICIES.join(" | ")})`).action((mode) => {
|
|
348
|
-
const cwd = process.cwd();
|
|
349
|
-
if (!mode) {
|
|
350
|
-
console.log(`updatePolicy = ${currentPolicy(cwd)} (${configPath(cwd)})`);
|
|
351
|
-
process.exit(0);
|
|
352
|
-
}
|
|
353
|
-
if (!VALID_POLICIES.includes(mode)) {
|
|
354
|
-
console.error(`Invalid policy: ${mode}. Valid: ${VALID_POLICIES.join(", ")}`);
|
|
355
|
-
process.exit(1);
|
|
356
|
-
}
|
|
357
|
-
const p = configPath(cwd);
|
|
358
|
-
const cfg = readJsonSafe(p) ?? {};
|
|
359
|
-
cfg.updatePolicy = mode;
|
|
360
|
-
writeJsonFile(p, cfg);
|
|
361
|
-
console.log(`updatePolicy = ${mode} (${p})`);
|
|
362
|
-
process.exit(0);
|
|
363
|
-
});
|
|
364
|
-
program.command("status").description("Show installed version, latest available, current policy, and hook registration state").action(async () => {
|
|
365
|
-
const cwd = process.cwd();
|
|
366
|
-
const current = installedVersion(cwd);
|
|
367
|
-
const latest = await fetchLatestVersion();
|
|
368
|
-
const policy = currentPolicy(cwd);
|
|
369
|
-
const settings = readJsonSafe(
|
|
370
|
-
join(cwd, ".claude", "settings.json")
|
|
371
|
-
);
|
|
372
|
-
const hookRegistered = !!settings?.hooks?.SessionStart?.some(
|
|
373
|
-
(g) => (g?.hooks || []).some((h) => typeof h?.command === "string" && h.command.includes("semantic-pages"))
|
|
374
|
-
);
|
|
375
|
-
console.log(`semantic-pages status`);
|
|
376
|
-
console.log(` installed: ${current ?? "(not installed)"}`);
|
|
377
|
-
console.log(` latest: ${latest ?? "(unknown)"}`);
|
|
378
|
-
console.log(` policy: ${policy}`);
|
|
379
|
-
console.log(` hook: ${hookRegistered ? "registered in .claude/settings.json" : "not in .claude/settings.json"}`);
|
|
380
|
-
process.exit(0);
|
|
381
|
-
});
|
|
382
|
-
program.command("relink").description("Re-run the skill linker (symlinks bundled skills into .claude/skills/)").action(() => {
|
|
383
|
-
process.exit(runRelink(process.cwd()));
|
|
274
|
+
registerUpdateCommands(program, {
|
|
275
|
+
packageName: PKG_NAME,
|
|
276
|
+
pluginName: "semantic-pages",
|
|
277
|
+
configFile: "semantic-pages.json",
|
|
278
|
+
onAfterUpdate: (cwd) => runRelink(cwd)
|
|
384
279
|
});
|
|
385
280
|
program.parse();
|
|
386
281
|
//# sourceMappingURL=index.js.map
|
package/dist/cli/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/cli/index.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { program } from \"commander\";\nimport { resolve, dirname, join } from \"node:path\";\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { spawnSync } from \"node:child_process\";\nimport { fileURLToPath } from \"node:url\";\n\nconst require_ = createRequire(import.meta.url);\nconst { version } = require_(\"../../package.json\") as { version: string };\n\nconst PKG_NAME = \"@theglitchking/semantic-pages\";\nconst VALID_POLICIES = [\"auto\", \"nudge\", \"off\"] as const;\ntype Policy = (typeof VALID_POLICIES)[number];\n\nfunction readJsonSafe<T = unknown>(p: string, fallback: T | null = null): T | null {\n try {\n const raw = readFileSync(p, \"utf8\");\n return raw.trim() ? (JSON.parse(raw) as T) : fallback;\n } catch {\n return fallback;\n }\n}\n\nfunction writeJsonFile(p: string, value: unknown) {\n mkdirSync(dirname(p), { recursive: true });\n writeFileSync(p, JSON.stringify(value, null, 2) + \"\\n\");\n}\n\nfunction configPath(cwd = process.cwd()) {\n return join(cwd, \".claude\", \"semantic-pages.json\");\n}\n\nfunction currentPolicy(cwd = process.cwd()): Policy {\n const env = process.env.SEMANTIC_PAGES_UPDATE_POLICY as Policy | undefined;\n if (env && (VALID_POLICIES as readonly string[]).includes(env)) return env;\n const cfg = readJsonSafe<{ updatePolicy?: Policy }>(configPath(cwd));\n const p = cfg?.updatePolicy;\n if (p && (VALID_POLICIES as readonly string[]).includes(p)) return p;\n return \"nudge\";\n}\n\nfunction installedVersion(cwd = process.cwd()): string | null {\n const localPkg = join(cwd, \"node_modules\", \"@theglitchking\", \"semantic-pages\", \"package.json\");\n const pkg = readJsonSafe<{ version?: string }>(localPkg);\n if (pkg?.version) return pkg.version;\n // fallback: our own package.json (when running via plugin dir)\n return version;\n}\n\nasync function fetchLatestVersion(timeoutMs = 5000): Promise<string | null> {\n const ctrl = new AbortController();\n const timer = setTimeout(() => ctrl.abort(), timeoutMs);\n try {\n const res = await fetch(`https://registry.npmjs.org/${PKG_NAME}/latest`, {\n signal: ctrl.signal,\n headers: { accept: \"application/json\" },\n });\n if (!res.ok) return null;\n const json = (await res.json()) as { version?: string };\n return json.version ?? null;\n } catch {\n return null;\n } finally {\n clearTimeout(timer);\n }\n}\n\nfunction runNpmUpdate(cwd: string) {\n return spawnSync(\"npm\", [\"update\", PKG_NAME], { cwd, stdio: \"inherit\" });\n}\n\nfunction runRelink(cwd: string) {\n const linker = join(cwd, \"node_modules\", \"@theglitchking\", \"semantic-pages\", \"scripts\", \"link-skills.js\");\n if (!existsSync(linker)) {\n // Running against our own repo — point at the source linker.\n const localLinker = resolve(fileURLToPath(import.meta.url), \"..\", \"..\", \"..\", \"scripts\", \"link-skills.js\");\n if (!existsSync(localLinker)) {\n console.error(\"link-skills.js not found — is the package installed?\");\n return 1;\n }\n const r = spawnSync(process.execPath, [localLinker], {\n cwd,\n env: { ...process.env, INIT_CWD: cwd },\n stdio: \"inherit\",\n });\n return r.status ?? 1;\n }\n const r = spawnSync(process.execPath, [linker], {\n cwd,\n env: { ...process.env, INIT_CWD: cwd },\n stdio: \"inherit\",\n });\n return r.status ?? 1;\n}\n\nconst TOOL_HELP: Record<string, { description: string; args: string; examples: string[] }> = {\n // Search\n search_semantic: {\n description: \"Vector similarity search — find notes by meaning, not just keywords\",\n args: '{ \"query\": \"string\", \"limit?\": 10 }',\n examples: [\n '{ \"query\": \"microservices architecture\", \"limit\": 5 }',\n '{ \"query\": \"how to deploy to production\" }',\n ],\n },\n search_text: {\n description: \"Full-text keyword or regex search with optional filters\",\n args: '{ \"pattern\": \"string\", \"regex?\": false, \"caseSensitive?\": false, \"pathGlob?\": \"string\", \"tagFilter?\": [\"string\"], \"limit?\": 20 }',\n examples: [\n '{ \"pattern\": \"RabbitMQ\" }',\n '{ \"pattern\": \"OAuth\\\\\\\\d\", \"regex\": true }',\n '{ \"pattern\": \"deploy\", \"pathGlob\": \"devops/**\", \"tagFilter\": [\"kubernetes\"] }',\n ],\n },\n search_graph: {\n description: \"Graph traversal — find notes connected to a concept via wikilinks and tags\",\n args: '{ \"concept\": \"string\", \"maxDepth?\": 2 }',\n examples: [\n '{ \"concept\": \"microservices\" }',\n '{ \"concept\": \"auth\", \"maxDepth\": 3 }',\n ],\n },\n search_hybrid: {\n description: \"Combined semantic + graph search — vector results re-ranked by graph proximity\",\n args: '{ \"query\": \"string\", \"limit?\": 10 }',\n examples: [\n '{ \"query\": \"event driven architecture\", \"limit\": 5 }',\n ],\n },\n\n // Read\n read_note: {\n description: \"Read the full content of a specific note by path\",\n args: '{ \"path\": \"string\" }',\n examples: [\n '{ \"path\": \"project-overview.md\" }',\n '{ \"path\": \"notes/meeting-2024-01-15.md\" }',\n ],\n },\n read_multiple_notes: {\n description: \"Batch read multiple notes in one call\",\n args: '{ \"paths\": [\"string\"] }',\n examples: [\n '{ \"paths\": [\"overview.md\", \"architecture.md\", \"deployment.md\"] }',\n ],\n },\n list_notes: {\n description: \"List all indexed notes with metadata (title, tags, link count)\",\n args: \"{}\",\n examples: [\"{}\"],\n },\n\n // Write\n create_note: {\n description: \"Create a new markdown note with optional YAML frontmatter\",\n args: '{ \"path\": \"string\", \"content\": \"string\", \"frontmatter?\": {} }',\n examples: [\n '{ \"path\": \"new-guide.md\", \"content\": \"# Guide\\\\n\\\\nContent here.\" }',\n '{ \"path\": \"tagged.md\", \"content\": \"Content.\", \"frontmatter\": { \"title\": \"Tagged Note\", \"tags\": [\"test\"] } }',\n ],\n },\n update_note: {\n description: \"Edit note content — overwrite, append, prepend, or patch by heading\",\n args: '{ \"path\": \"string\", \"content\": \"string\", \"mode\": \"overwrite|append|prepend|patch-by-heading\", \"heading?\": \"string\" }',\n examples: [\n '{ \"path\": \"guide.md\", \"content\": \"New content.\", \"mode\": \"overwrite\" }',\n '{ \"path\": \"guide.md\", \"content\": \"\\\\n## Appendix\\\\nExtra info.\", \"mode\": \"append\" }',\n '{ \"path\": \"guide.md\", \"content\": \"Updated architecture section.\", \"mode\": \"patch-by-heading\", \"heading\": \"Architecture\" }',\n ],\n },\n delete_note: {\n description: \"Delete a note permanently (requires confirm=true)\",\n args: '{ \"path\": \"string\", \"confirm\": true }',\n examples: [\n '{ \"path\": \"old-note.md\", \"confirm\": true }',\n '{ \"path\": \"old-note.md\", \"confirm\": false } // returns warning, does not delete',\n ],\n },\n move_note: {\n description: \"Move or rename a note — automatically updates wikilinks across the vault\",\n args: '{ \"from\": \"string\", \"to\": \"string\" }',\n examples: [\n '{ \"from\": \"user-service.md\", \"to\": \"auth-service.md\" }',\n '{ \"from\": \"old/note.md\", \"to\": \"new/location/note.md\" }',\n ],\n },\n\n // Metadata\n get_frontmatter: {\n description: \"Read parsed YAML frontmatter from a note as JSON\",\n args: '{ \"path\": \"string\" }',\n examples: ['{ \"path\": \"project-overview.md\" }'],\n },\n update_frontmatter: {\n description: \"Set or delete YAML frontmatter keys — pass null to delete a key\",\n args: '{ \"path\": \"string\", \"fields\": {} }',\n examples: [\n '{ \"path\": \"note.md\", \"fields\": { \"status\": \"active\", \"priority\": 1 } }',\n '{ \"path\": \"note.md\", \"fields\": { \"deprecated_field\": null } } // deletes the key',\n ],\n },\n manage_tags: {\n description: \"Add, remove, or list tags on a note (frontmatter and inline)\",\n args: '{ \"path\": \"string\", \"action\": \"add|remove|list\", \"tags?\": [\"string\"] }',\n examples: [\n '{ \"path\": \"note.md\", \"action\": \"list\" }',\n '{ \"path\": \"note.md\", \"action\": \"add\", \"tags\": [\"important\", \"reviewed\"] }',\n '{ \"path\": \"note.md\", \"action\": \"remove\", \"tags\": [\"draft\"] }',\n ],\n },\n rename_tag: {\n description: \"Rename a tag across all notes in the vault (frontmatter + inline)\",\n args: '{ \"oldTag\": \"string\", \"newTag\": \"string\" }',\n examples: ['{ \"oldTag\": \"architecture\", \"newTag\": \"arch\" }'],\n },\n\n // Graph\n backlinks: {\n description: \"Find all notes that link TO a given note via [[wikilinks]]\",\n args: '{ \"path\": \"string\" }',\n examples: ['{ \"path\": \"microservices.md\" }'],\n },\n forwardlinks: {\n description: \"Find all notes linked FROM a given note\",\n args: '{ \"path\": \"string\" }',\n examples: ['{ \"path\": \"project-overview.md\" }'],\n },\n graph_path: {\n description: \"Find the shortest path between two notes in the knowledge graph\",\n args: '{ \"from\": \"string\", \"to\": \"string\" }',\n examples: ['{ \"from\": \"project-overview.md\", \"to\": \"user-service.md\" }'],\n },\n graph_statistics: {\n description: \"Knowledge graph stats — most connected nodes, orphans, density\",\n args: \"{}\",\n examples: [\"{}\"],\n },\n\n // System\n get_stats: {\n description: \"Vault and index statistics — note count, chunks, embeddings, graph density\",\n args: \"{}\",\n examples: [\"{}\"],\n },\n reindex: {\n description: \"Force a full reindex of the vault\",\n args: \"{}\",\n examples: [\"{}\"],\n },\n};\n\nconst TOOL_CATEGORIES: Record<string, string[]> = {\n Search: [\"search_semantic\", \"search_text\", \"search_graph\", \"search_hybrid\"],\n Read: [\"read_note\", \"read_multiple_notes\", \"list_notes\"],\n Write: [\"create_note\", \"update_note\", \"delete_note\", \"move_note\"],\n Metadata: [\"get_frontmatter\", \"update_frontmatter\", \"manage_tags\", \"rename_tag\"],\n Graph: [\"backlinks\", \"forwardlinks\", \"graph_path\", \"graph_statistics\"],\n System: [\"get_stats\", \"reindex\"],\n};\n\nfunction printToolList() {\n console.log(\"\\nSemantic Pages — 21 MCP Tools\\n\");\n console.log(\"Usage: These tools are available via MCP when the server is running.\");\n console.log(\" Run `semantic-pages tools <name>` for details on a specific tool.\\n\");\n\n for (const [category, tools] of Object.entries(TOOL_CATEGORIES)) {\n console.log(` ${category}:`);\n for (const name of tools) {\n const tool = TOOL_HELP[name];\n console.log(` ${name.padEnd(24)} ${tool.description}`);\n }\n console.log();\n }\n\n console.log(\"Run `semantic-pages tools <tool-name>` for arguments and examples.\");\n}\n\nfunction printToolDetail(name: string) {\n const tool = TOOL_HELP[name];\n if (!tool) {\n console.error(`Unknown tool: ${name}`);\n console.error(`Run \\`semantic-pages tools\\` to see all available tools.`);\n process.exit(1);\n }\n\n console.log(`\\n ${name}`);\n console.log(` ${\"─\".repeat(name.length)}`);\n console.log(` ${tool.description}\\n`);\n console.log(` Arguments:`);\n console.log(` ${tool.args}\\n`);\n console.log(` Examples:`);\n for (const ex of tool.examples) {\n console.log(` ${ex}`);\n }\n console.log();\n}\n\nprogram\n .name(\"semantic-pages\")\n .description(\n \"Semantic search + knowledge graph MCP server for markdown files\\n\\n\" +\n \" Start MCP server: semantic-pages --notes ./vault\\n\" +\n \" Show vault stats: semantic-pages --notes ./vault --stats\\n\" +\n \" Force reindex: semantic-pages --notes ./vault --reindex\\n\" +\n \" List MCP tools: semantic-pages tools\\n\" +\n \" Tool details: semantic-pages tools search_semantic\"\n )\n .version(version);\n\nprogram\n .command(\"tools [name]\")\n .description(\"List all MCP tools, or show details for a specific tool\")\n .action((name?: string) => {\n if (name) {\n printToolDetail(name);\n } else {\n printToolList();\n }\n process.exit(0);\n });\n\nprogram\n .command(\"serve\", { isDefault: true })\n .description(\"Start the MCP server (default command)\")\n .requiredOption(\"--notes <path>\", \"Path to markdown notes directory\")\n .option(\"--reindex\", \"Force full reindex and exit\")\n .option(\"--stats\", \"Show vault statistics and exit\")\n .option(\"--wait-for-ready\", \"Block startup until index is fully built before serving (default: index in background; tools return 'Indexing in progress' until ready)\")\n .option(\"--read-only\", \"Suppress write tools (create_note, update_note, delete_note, move_note, update_frontmatter, manage_tags, rename_tag) — use for shared docs vaults owned by another tool\")\n .option(\"--model <name>\", \"Embedding model to use (default: all-MiniLM-L6-v2, fast; use nomic-ai/nomic-embed-text-v1.5 for higher quality)\")\n .option(\"--workers <n>\", \"Number of worker threads for parallel embedding\", parseInt)\n .option(\"--batch-size <n>\", \"Texts per ONNX forward pass (default: 8)\", parseInt)\n .option(\"--no-quantized\", \"Use full-precision model instead of quantized (slower, slightly higher quality)\")\n .option(\"--no-watch\", \"Disable file watcher\")\n .action(async (opts) => {\n const notesPath = resolve(opts.notes);\n\n if (!existsSync(notesPath)) {\n console.error(`Error: notes directory not found: ${notesPath}`);\n process.exit(1);\n }\n\n if (opts.stats) {\n const { Indexer } = await import(\"../core/indexer.js\");\n const indexer = new Indexer(notesPath);\n const docs = await indexer.indexAll();\n console.log(`Notes: ${docs.length}`);\n console.log(`Chunks: ${docs.reduce((n: number, d: any) => n + d.chunks.length, 0)}`);\n console.log(`Wikilinks: ${docs.reduce((n: number, d: any) => n + d.wikilinks.length, 0)}`);\n console.log(`Tags: ${new Set(docs.flatMap((d: any) => d.tags)).size} unique`);\n process.exit(0);\n }\n\n if (opts.reindex) {\n const { createServer } = await import(\"../mcp/server.js\");\n await createServer(notesPath, {\n watch: false,\n waitForReady: true,\n model: opts.model,\n workers: opts.workers,\n batchSize: opts.batchSize,\n quantized: opts.quantized,\n onProgress: (embedded, total) => {\n process.stderr.write(`\\rEmbedding ${embedded}/${total} chunks...`);\n },\n });\n process.stderr.write(\"\\n\");\n console.log(\"Reindex complete.\");\n process.exit(0);\n }\n\n // Default: start MCP server on stdio\n const { startServer } = await import(\"../mcp/server.js\");\n await startServer(notesPath, {\n watch: opts.watch,\n waitForReady: opts.waitForReady,\n model: opts.model,\n workers: opts.workers,\n batchSize: opts.batchSize,\n quantized: opts.quantized,\n readOnly: opts.readOnly,\n });\n });\n\nprogram\n .command(\"update\")\n .description(\"Update semantic-pages to the latest version (project-local install)\")\n .action(async () => {\n const cwd = process.cwd();\n const before = installedVersion(cwd);\n console.log(`Current: ${before ?? \"(not installed)\"}`);\n const r = runNpmUpdate(cwd);\n if (r.status !== 0) {\n console.error(\"npm update failed.\");\n process.exit(r.status ?? 1);\n }\n const after = installedVersion(cwd);\n console.log(`Now: ${after ?? \"(not installed)\"}`);\n if (after && before && after !== before) runRelink(cwd);\n process.exit(0);\n });\n\nprogram\n .command(\"policy [mode]\")\n .description(`Show or set the update policy (${VALID_POLICIES.join(\" | \")})`)\n .action((mode?: string) => {\n const cwd = process.cwd();\n if (!mode) {\n console.log(`updatePolicy = ${currentPolicy(cwd)} (${configPath(cwd)})`);\n process.exit(0);\n }\n if (!(VALID_POLICIES as readonly string[]).includes(mode)) {\n console.error(`Invalid policy: ${mode}. Valid: ${VALID_POLICIES.join(\", \")}`);\n process.exit(1);\n }\n const p = configPath(cwd);\n const cfg = readJsonSafe<Record<string, unknown>>(p) ?? {};\n cfg.updatePolicy = mode;\n writeJsonFile(p, cfg);\n console.log(`updatePolicy = ${mode} (${p})`);\n process.exit(0);\n });\n\nprogram\n .command(\"status\")\n .description(\"Show installed version, latest available, current policy, and hook registration state\")\n .action(async () => {\n const cwd = process.cwd();\n const current = installedVersion(cwd);\n const latest = await fetchLatestVersion();\n const policy = currentPolicy(cwd);\n const settings = readJsonSafe<{ hooks?: { SessionStart?: Array<{ hooks?: Array<{ command?: string }> }> } }>(\n join(cwd, \".claude\", \"settings.json\"),\n );\n const hookRegistered = !!settings?.hooks?.SessionStart?.some((g) =>\n (g?.hooks || []).some((h) => typeof h?.command === \"string\" && h.command.includes(\"semantic-pages\")),\n );\n console.log(`semantic-pages status`);\n console.log(` installed: ${current ?? \"(not installed)\"}`);\n console.log(` latest: ${latest ?? \"(unknown)\"}`);\n console.log(` policy: ${policy}`);\n console.log(` hook: ${hookRegistered ? \"registered in .claude/settings.json\" : \"not in .claude/settings.json\"}`);\n process.exit(0);\n });\n\nprogram\n .command(\"relink\")\n .description(\"Re-run the skill linker (symlinks bundled skills into .claude/skills/)\")\n .action(() => {\n process.exit(runRelink(process.cwd()));\n });\n\nprogram.parse();\n"],"mappings":"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","names":["r"]}
|
|
1
|
+
{"version":3,"sources":["../../src/cli/index.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { program } from \"commander\";\nimport { resolve, join } from \"node:path\";\nimport { existsSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { spawnSync } from \"node:child_process\";\nimport { registerUpdateCommands } from \"@theglitchking/claude-plugin-runtime\";\n\nconst require_ = createRequire(import.meta.url);\nconst { version } = require_(\"../../package.json\") as { version: string };\n\nconst PKG_NAME = \"@theglitchking/semantic-pages\";\n\nfunction runRelink(cwd: string) {\n const linker = join(cwd, \"node_modules\", \"@theglitchking\", \"semantic-pages\", \"scripts\", \"link-skills.js\");\n const script = existsSync(linker) ? linker : resolve(process.cwd(), \"scripts\", \"link-skills.js\");\n if (!existsSync(script)) {\n console.error(\"link-skills.js not found — is the package installed?\");\n return;\n }\n spawnSync(process.execPath, [script], {\n cwd,\n env: { ...process.env, INIT_CWD: cwd },\n stdio: \"inherit\",\n });\n}\n\nconst TOOL_HELP: Record<string, { description: string; args: string; examples: string[] }> = {\n // Search\n search_semantic: {\n description: \"Vector similarity search — find notes by meaning, not just keywords\",\n args: '{ \"query\": \"string\", \"limit?\": 10 }',\n examples: [\n '{ \"query\": \"microservices architecture\", \"limit\": 5 }',\n '{ \"query\": \"how to deploy to production\" }',\n ],\n },\n search_text: {\n description: \"Full-text keyword or regex search with optional filters\",\n args: '{ \"pattern\": \"string\", \"regex?\": false, \"caseSensitive?\": false, \"pathGlob?\": \"string\", \"tagFilter?\": [\"string\"], \"limit?\": 20 }',\n examples: [\n '{ \"pattern\": \"RabbitMQ\" }',\n '{ \"pattern\": \"OAuth\\\\\\\\d\", \"regex\": true }',\n '{ \"pattern\": \"deploy\", \"pathGlob\": \"devops/**\", \"tagFilter\": [\"kubernetes\"] }',\n ],\n },\n search_graph: {\n description: \"Graph traversal — find notes connected to a concept via wikilinks and tags\",\n args: '{ \"concept\": \"string\", \"maxDepth?\": 2 }',\n examples: [\n '{ \"concept\": \"microservices\" }',\n '{ \"concept\": \"auth\", \"maxDepth\": 3 }',\n ],\n },\n search_hybrid: {\n description: \"Combined semantic + graph search — vector results re-ranked by graph proximity\",\n args: '{ \"query\": \"string\", \"limit?\": 10 }',\n examples: [\n '{ \"query\": \"event driven architecture\", \"limit\": 5 }',\n ],\n },\n\n // Read\n read_note: {\n description: \"Read the full content of a specific note by path\",\n args: '{ \"path\": \"string\" }',\n examples: [\n '{ \"path\": \"project-overview.md\" }',\n '{ \"path\": \"notes/meeting-2024-01-15.md\" }',\n ],\n },\n read_multiple_notes: {\n description: \"Batch read multiple notes in one call\",\n args: '{ \"paths\": [\"string\"] }',\n examples: [\n '{ \"paths\": [\"overview.md\", \"architecture.md\", \"deployment.md\"] }',\n ],\n },\n list_notes: {\n description: \"List all indexed notes with metadata (title, tags, link count)\",\n args: \"{}\",\n examples: [\"{}\"],\n },\n\n // Write\n create_note: {\n description: \"Create a new markdown note with optional YAML frontmatter\",\n args: '{ \"path\": \"string\", \"content\": \"string\", \"frontmatter?\": {} }',\n examples: [\n '{ \"path\": \"new-guide.md\", \"content\": \"# Guide\\\\n\\\\nContent here.\" }',\n '{ \"path\": \"tagged.md\", \"content\": \"Content.\", \"frontmatter\": { \"title\": \"Tagged Note\", \"tags\": [\"test\"] } }',\n ],\n },\n update_note: {\n description: \"Edit note content — overwrite, append, prepend, or patch by heading\",\n args: '{ \"path\": \"string\", \"content\": \"string\", \"mode\": \"overwrite|append|prepend|patch-by-heading\", \"heading?\": \"string\" }',\n examples: [\n '{ \"path\": \"guide.md\", \"content\": \"New content.\", \"mode\": \"overwrite\" }',\n '{ \"path\": \"guide.md\", \"content\": \"\\\\n## Appendix\\\\nExtra info.\", \"mode\": \"append\" }',\n '{ \"path\": \"guide.md\", \"content\": \"Updated architecture section.\", \"mode\": \"patch-by-heading\", \"heading\": \"Architecture\" }',\n ],\n },\n delete_note: {\n description: \"Delete a note permanently (requires confirm=true)\",\n args: '{ \"path\": \"string\", \"confirm\": true }',\n examples: [\n '{ \"path\": \"old-note.md\", \"confirm\": true }',\n '{ \"path\": \"old-note.md\", \"confirm\": false } // returns warning, does not delete',\n ],\n },\n move_note: {\n description: \"Move or rename a note — automatically updates wikilinks across the vault\",\n args: '{ \"from\": \"string\", \"to\": \"string\" }',\n examples: [\n '{ \"from\": \"user-service.md\", \"to\": \"auth-service.md\" }',\n '{ \"from\": \"old/note.md\", \"to\": \"new/location/note.md\" }',\n ],\n },\n\n // Metadata\n get_frontmatter: {\n description: \"Read parsed YAML frontmatter from a note as JSON\",\n args: '{ \"path\": \"string\" }',\n examples: ['{ \"path\": \"project-overview.md\" }'],\n },\n update_frontmatter: {\n description: \"Set or delete YAML frontmatter keys — pass null to delete a key\",\n args: '{ \"path\": \"string\", \"fields\": {} }',\n examples: [\n '{ \"path\": \"note.md\", \"fields\": { \"status\": \"active\", \"priority\": 1 } }',\n '{ \"path\": \"note.md\", \"fields\": { \"deprecated_field\": null } } // deletes the key',\n ],\n },\n manage_tags: {\n description: \"Add, remove, or list tags on a note (frontmatter and inline)\",\n args: '{ \"path\": \"string\", \"action\": \"add|remove|list\", \"tags?\": [\"string\"] }',\n examples: [\n '{ \"path\": \"note.md\", \"action\": \"list\" }',\n '{ \"path\": \"note.md\", \"action\": \"add\", \"tags\": [\"important\", \"reviewed\"] }',\n '{ \"path\": \"note.md\", \"action\": \"remove\", \"tags\": [\"draft\"] }',\n ],\n },\n rename_tag: {\n description: \"Rename a tag across all notes in the vault (frontmatter + inline)\",\n args: '{ \"oldTag\": \"string\", \"newTag\": \"string\" }',\n examples: ['{ \"oldTag\": \"architecture\", \"newTag\": \"arch\" }'],\n },\n\n // Graph\n backlinks: {\n description: \"Find all notes that link TO a given note via [[wikilinks]]\",\n args: '{ \"path\": \"string\" }',\n examples: ['{ \"path\": \"microservices.md\" }'],\n },\n forwardlinks: {\n description: \"Find all notes linked FROM a given note\",\n args: '{ \"path\": \"string\" }',\n examples: ['{ \"path\": \"project-overview.md\" }'],\n },\n graph_path: {\n description: \"Find the shortest path between two notes in the knowledge graph\",\n args: '{ \"from\": \"string\", \"to\": \"string\" }',\n examples: ['{ \"from\": \"project-overview.md\", \"to\": \"user-service.md\" }'],\n },\n graph_statistics: {\n description: \"Knowledge graph stats — most connected nodes, orphans, density\",\n args: \"{}\",\n examples: [\"{}\"],\n },\n\n // System\n get_stats: {\n description: \"Vault and index statistics — note count, chunks, embeddings, graph density\",\n args: \"{}\",\n examples: [\"{}\"],\n },\n reindex: {\n description: \"Force a full reindex of the vault\",\n args: \"{}\",\n examples: [\"{}\"],\n },\n};\n\nconst TOOL_CATEGORIES: Record<string, string[]> = {\n Search: [\"search_semantic\", \"search_text\", \"search_graph\", \"search_hybrid\"],\n Read: [\"read_note\", \"read_multiple_notes\", \"list_notes\"],\n Write: [\"create_note\", \"update_note\", \"delete_note\", \"move_note\"],\n Metadata: [\"get_frontmatter\", \"update_frontmatter\", \"manage_tags\", \"rename_tag\"],\n Graph: [\"backlinks\", \"forwardlinks\", \"graph_path\", \"graph_statistics\"],\n System: [\"get_stats\", \"reindex\"],\n};\n\nfunction printToolList() {\n console.log(\"\\nSemantic Pages — 21 MCP Tools\\n\");\n console.log(\"Usage: These tools are available via MCP when the server is running.\");\n console.log(\" Run `semantic-pages tools <name>` for details on a specific tool.\\n\");\n\n for (const [category, tools] of Object.entries(TOOL_CATEGORIES)) {\n console.log(` ${category}:`);\n for (const name of tools) {\n const tool = TOOL_HELP[name];\n console.log(` ${name.padEnd(24)} ${tool.description}`);\n }\n console.log();\n }\n\n console.log(\"Run `semantic-pages tools <tool-name>` for arguments and examples.\");\n}\n\nfunction printToolDetail(name: string) {\n const tool = TOOL_HELP[name];\n if (!tool) {\n console.error(`Unknown tool: ${name}`);\n console.error(`Run \\`semantic-pages tools\\` to see all available tools.`);\n process.exit(1);\n }\n\n console.log(`\\n ${name}`);\n console.log(` ${\"─\".repeat(name.length)}`);\n console.log(` ${tool.description}\\n`);\n console.log(` Arguments:`);\n console.log(` ${tool.args}\\n`);\n console.log(` Examples:`);\n for (const ex of tool.examples) {\n console.log(` ${ex}`);\n }\n console.log();\n}\n\nprogram\n .name(\"semantic-pages\")\n .description(\n \"Semantic search + knowledge graph MCP server for markdown files\\n\\n\" +\n \" Start MCP server: semantic-pages --notes ./vault\\n\" +\n \" Show vault stats: semantic-pages --notes ./vault --stats\\n\" +\n \" Force reindex: semantic-pages --notes ./vault --reindex\\n\" +\n \" List MCP tools: semantic-pages tools\\n\" +\n \" Tool details: semantic-pages tools search_semantic\"\n )\n .version(version);\n\nprogram\n .command(\"tools [name]\")\n .description(\"List all MCP tools, or show details for a specific tool\")\n .action((name?: string) => {\n if (name) {\n printToolDetail(name);\n } else {\n printToolList();\n }\n process.exit(0);\n });\n\nprogram\n .command(\"serve\", { isDefault: true })\n .description(\"Start the MCP server (default command)\")\n .requiredOption(\"--notes <path>\", \"Path to markdown notes directory\")\n .option(\"--reindex\", \"Force full reindex and exit\")\n .option(\"--stats\", \"Show vault statistics and exit\")\n .option(\"--wait-for-ready\", \"Block startup until index is fully built before serving (default: index in background; tools return 'Indexing in progress' until ready)\")\n .option(\"--read-only\", \"Suppress write tools (create_note, update_note, delete_note, move_note, update_frontmatter, manage_tags, rename_tag) — use for shared docs vaults owned by another tool\")\n .option(\"--model <name>\", \"Embedding model to use (default: all-MiniLM-L6-v2, fast; use nomic-ai/nomic-embed-text-v1.5 for higher quality)\")\n .option(\"--workers <n>\", \"Number of worker threads for parallel embedding\", parseInt)\n .option(\"--batch-size <n>\", \"Texts per ONNX forward pass (default: 8)\", parseInt)\n .option(\"--no-quantized\", \"Use full-precision model instead of quantized (slower, slightly higher quality)\")\n .option(\"--no-watch\", \"Disable file watcher\")\n .action(async (opts) => {\n const notesPath = resolve(opts.notes);\n\n if (!existsSync(notesPath)) {\n console.error(`Error: notes directory not found: ${notesPath}`);\n process.exit(1);\n }\n\n if (opts.stats) {\n const { Indexer } = await import(\"../core/indexer.js\");\n const indexer = new Indexer(notesPath);\n const docs = await indexer.indexAll();\n console.log(`Notes: ${docs.length}`);\n console.log(`Chunks: ${docs.reduce((n: number, d: any) => n + d.chunks.length, 0)}`);\n console.log(`Wikilinks: ${docs.reduce((n: number, d: any) => n + d.wikilinks.length, 0)}`);\n console.log(`Tags: ${new Set(docs.flatMap((d: any) => d.tags)).size} unique`);\n process.exit(0);\n }\n\n if (opts.reindex) {\n const { createServer } = await import(\"../mcp/server.js\");\n await createServer(notesPath, {\n watch: false,\n waitForReady: true,\n model: opts.model,\n workers: opts.workers,\n batchSize: opts.batchSize,\n quantized: opts.quantized,\n onProgress: (embedded, total) => {\n process.stderr.write(`\\rEmbedding ${embedded}/${total} chunks...`);\n },\n });\n process.stderr.write(\"\\n\");\n console.log(\"Reindex complete.\");\n process.exit(0);\n }\n\n // Default: start MCP server on stdio\n const { startServer } = await import(\"../mcp/server.js\");\n await startServer(notesPath, {\n watch: opts.watch,\n waitForReady: opts.waitForReady,\n model: opts.model,\n workers: opts.workers,\n batchSize: opts.batchSize,\n quantized: opts.quantized,\n readOnly: opts.readOnly,\n });\n });\n\nregisterUpdateCommands(program, {\n packageName: PKG_NAME,\n pluginName: \"semantic-pages\",\n configFile: \"semantic-pages.json\",\n onAfterUpdate: (cwd) => runRelink(cwd),\n});\n\nprogram.parse();\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// Plugin-specific SessionStart reconciliation for semantic-pages.
|
|
2
|
+
//
|
|
3
|
+
// Ensures <project>/.claude/.vault exists. Reconciles .mcp.json so the
|
|
4
|
+
// "semantic-vault" entry always points at ./.claude/.vault, and adds
|
|
5
|
+
// (or removes) a read-only "semantic-pages" entry pointed at
|
|
6
|
+
// ./.documentation only when hit-em-with-the-docs is enabled and the
|
|
7
|
+
// .documentation directory exists.
|
|
8
|
+
//
|
|
9
|
+
// Idempotent: only writes .mcp.json when the computed JSON differs from
|
|
10
|
+
// what's on disk.
|
|
11
|
+
|
|
12
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
13
|
+
import { homedir } from "node:os";
|
|
14
|
+
import { dirname, join } from "node:path";
|
|
15
|
+
|
|
16
|
+
const PKG = "@theglitchking/semantic-pages";
|
|
17
|
+
|
|
18
|
+
function readJson(path, fallback = null) {
|
|
19
|
+
try {
|
|
20
|
+
const raw = readFileSync(path, "utf8");
|
|
21
|
+
return raw.trim() ? JSON.parse(raw) : fallback;
|
|
22
|
+
} catch {
|
|
23
|
+
return fallback;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function hewtdEnabled() {
|
|
28
|
+
const settings = readJson(join(homedir(), ".claude", "settings.json"));
|
|
29
|
+
const enabled = settings?.enabledPlugins || {};
|
|
30
|
+
return Object.keys(enabled).some(
|
|
31
|
+
(k) => k.startsWith("hit-em-with-the-docs@") && enabled[k] === true,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function reconcile(projectRoot) {
|
|
36
|
+
const vaultDir = join(projectRoot, ".claude", ".vault");
|
|
37
|
+
const docsDir = join(projectRoot, ".documentation");
|
|
38
|
+
const mcpPath = join(projectRoot, ".mcp.json");
|
|
39
|
+
|
|
40
|
+
try { mkdirSync(vaultDir, { recursive: true }); } catch {}
|
|
41
|
+
|
|
42
|
+
const docsWired = hewtdEnabled() && existsSync(docsDir);
|
|
43
|
+
|
|
44
|
+
let data = { mcpServers: {} };
|
|
45
|
+
if (existsSync(mcpPath)) {
|
|
46
|
+
const parsed = readJson(mcpPath, null);
|
|
47
|
+
if (parsed === null) {
|
|
48
|
+
let raw = "";
|
|
49
|
+
try { raw = readFileSync(mcpPath, "utf8"); } catch {}
|
|
50
|
+
if (raw.trim()) {
|
|
51
|
+
process.stderr.write(`semantic-pages hook: could not parse ${mcpPath}; leaving untouched\n`);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
} else if (parsed && typeof parsed === "object") {
|
|
55
|
+
data = parsed;
|
|
56
|
+
if (!data.mcpServers || typeof data.mcpServers !== "object") {
|
|
57
|
+
data.mcpServers = {};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const before = JSON.stringify(data);
|
|
63
|
+
|
|
64
|
+
data.mcpServers["semantic-vault"] = {
|
|
65
|
+
type: "stdio",
|
|
66
|
+
command: "npx",
|
|
67
|
+
args: ["-y", `${PKG}@latest`, "--notes", "./.claude/.vault"],
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
if (docsWired) {
|
|
71
|
+
data.mcpServers["semantic-pages"] = {
|
|
72
|
+
type: "stdio",
|
|
73
|
+
command: "npx",
|
|
74
|
+
args: ["-y", `${PKG}@latest`, "--notes", "./.documentation", "--read-only"],
|
|
75
|
+
};
|
|
76
|
+
} else if (data.mcpServers["semantic-pages"]) {
|
|
77
|
+
const existing = data.mcpServers["semantic-pages"];
|
|
78
|
+
const args = Array.isArray(existing.args) ? existing.args : [];
|
|
79
|
+
const looksLikeOurs = args.some(
|
|
80
|
+
(a) => typeof a === "string" && a.includes(".documentation"),
|
|
81
|
+
);
|
|
82
|
+
if (looksLikeOurs) delete data.mcpServers["semantic-pages"];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const after = JSON.stringify(data);
|
|
86
|
+
if (after === before) return;
|
|
87
|
+
|
|
88
|
+
mkdirSync(dirname(mcpPath), { recursive: true });
|
|
89
|
+
writeFileSync(mcpPath, JSON.stringify(data, null, 2) + "\n");
|
|
90
|
+
}
|
package/hooks/session-start.js
CHANGED
|
@@ -1,283 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// semantic-pages SessionStart hook
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
// Never hard-fails. Emits a valid SessionStart hookSpecificOutput on stdout and
|
|
15
|
-
// exits 0. Has a ~3s budget when up-to-date (network fetch timeout). Caches the
|
|
16
|
-
// latest version for 6h. Skipped entirely in CI. Self-skips its update section
|
|
17
|
-
// when it detects a project-registered counterpart to avoid double-firing when
|
|
18
|
-
// the user has BOTH the Claude Code plugin AND the npm dep installed.
|
|
19
|
-
|
|
20
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
21
|
-
import { homedir } from "node:os";
|
|
22
|
-
import { dirname, join } from "node:path";
|
|
23
|
-
import { spawnSync } from "node:child_process";
|
|
24
|
-
|
|
25
|
-
const PKG = "@theglitchking/semantic-pages";
|
|
26
|
-
const CACHE_TTL_MS = 6 * 60 * 60 * 1000;
|
|
27
|
-
const NETWORK_TIMEOUT_MS = 3000;
|
|
28
|
-
const AUTO_UPDATE_TIMEOUT_MS = 60_000;
|
|
29
|
-
|
|
30
|
-
const VALID_POLICIES = new Set(["auto", "nudge", "off"]);
|
|
31
|
-
|
|
32
|
-
function emit(additionalContext) {
|
|
33
|
-
const out = { hookSpecificOutput: { hookEventName: "SessionStart" } };
|
|
34
|
-
if (additionalContext) out.hookSpecificOutput.additionalContext = additionalContext;
|
|
35
|
-
process.stdout.write(JSON.stringify(out) + "\n");
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function logWarn(msg) {
|
|
39
|
-
process.stderr.write(`semantic-pages hook: ${msg}\n`);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function readJson(path, fallback = null) {
|
|
43
|
-
try {
|
|
44
|
-
const raw = readFileSync(path, "utf8");
|
|
45
|
-
return raw.trim() ? JSON.parse(raw) : fallback;
|
|
46
|
-
} catch {
|
|
47
|
-
return fallback;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function isCi() {
|
|
52
|
-
return ["CI", "GITHUB_ACTIONS", "GITLAB_CI", "BUILDKITE", "CIRCLECI"].some(
|
|
53
|
-
(k) => !!process.env[k],
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function hewtdEnabled() {
|
|
58
|
-
const settings = readJson(join(homedir(), ".claude", "settings.json"));
|
|
59
|
-
const enabled = settings?.enabledPlugins || {};
|
|
60
|
-
return Object.keys(enabled).some(
|
|
61
|
-
(k) => k.startsWith("hit-em-with-the-docs@") && enabled[k] === true,
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function hasProjectRegisteredHook(projectRoot) {
|
|
66
|
-
const settings = readJson(join(projectRoot, ".claude", "settings.json"));
|
|
67
|
-
const groups = settings?.hooks?.SessionStart;
|
|
68
|
-
if (!Array.isArray(groups)) return false;
|
|
69
|
-
for (const group of groups) {
|
|
70
|
-
for (const h of group?.hooks || []) {
|
|
71
|
-
const cmd = h?.command;
|
|
72
|
-
if (typeof cmd === "string" && cmd.includes("semantic-pages")) return true;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
return false;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function reconcileMcp(projectRoot) {
|
|
79
|
-
const vaultDir = join(projectRoot, ".claude", ".vault");
|
|
80
|
-
const docsDir = join(projectRoot, ".documentation");
|
|
81
|
-
const mcpPath = join(projectRoot, ".mcp.json");
|
|
82
|
-
|
|
83
|
-
try { mkdirSync(vaultDir, { recursive: true }); } catch {}
|
|
84
|
-
|
|
85
|
-
const docsWired = hewtdEnabled() && existsSync(docsDir);
|
|
86
|
-
|
|
87
|
-
let data = { mcpServers: {} };
|
|
88
|
-
if (existsSync(mcpPath)) {
|
|
89
|
-
const parsed = readJson(mcpPath, null);
|
|
90
|
-
if (parsed === null) {
|
|
91
|
-
// File exists but didn't parse — leave it alone, don't clobber user data.
|
|
92
|
-
let raw = "";
|
|
93
|
-
try { raw = readFileSync(mcpPath, "utf8"); } catch {}
|
|
94
|
-
if (raw.trim()) {
|
|
95
|
-
logWarn(`could not parse ${mcpPath}; leaving untouched`);
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
} else if (parsed && typeof parsed === "object") {
|
|
99
|
-
data = parsed;
|
|
100
|
-
if (!data.mcpServers || typeof data.mcpServers !== "object") {
|
|
101
|
-
data.mcpServers = {};
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const before = JSON.stringify(data);
|
|
107
|
-
|
|
108
|
-
data.mcpServers["semantic-vault"] = {
|
|
109
|
-
type: "stdio",
|
|
110
|
-
command: "npx",
|
|
111
|
-
args: ["-y", `${PKG}@latest`, "--notes", "./.claude/.vault"],
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
if (docsWired) {
|
|
115
|
-
data.mcpServers["semantic-pages"] = {
|
|
116
|
-
type: "stdio",
|
|
117
|
-
command: "npx",
|
|
118
|
-
args: ["-y", `${PKG}@latest`, "--notes", "./.documentation", "--read-only"],
|
|
119
|
-
};
|
|
120
|
-
} else if (data.mcpServers["semantic-pages"]) {
|
|
121
|
-
// Only remove if it looks like ours (points at .documentation). Preserves
|
|
122
|
-
// any user-defined semantic-pages entry pointing elsewhere.
|
|
123
|
-
const existing = data.mcpServers["semantic-pages"];
|
|
124
|
-
const args = Array.isArray(existing.args) ? existing.args : [];
|
|
125
|
-
const looksLikeOurs = args.some(
|
|
126
|
-
(a) => typeof a === "string" && a.includes(".documentation"),
|
|
127
|
-
);
|
|
128
|
-
if (looksLikeOurs) delete data.mcpServers["semantic-pages"];
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const after = JSON.stringify(data);
|
|
132
|
-
if (after === before) return;
|
|
133
|
-
|
|
134
|
-
mkdirSync(dirname(mcpPath), { recursive: true });
|
|
135
|
-
writeFileSync(mcpPath, JSON.stringify(data, null, 2) + "\n");
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function loadPolicy(projectRoot) {
|
|
139
|
-
const env = process.env.SEMANTIC_PAGES_UPDATE_POLICY;
|
|
140
|
-
if (env && VALID_POLICIES.has(env)) return env;
|
|
141
|
-
const cfg = readJson(join(projectRoot, ".claude", "semantic-pages.json"));
|
|
142
|
-
if (cfg?.updatePolicy && VALID_POLICIES.has(cfg.updatePolicy)) return cfg.updatePolicy;
|
|
143
|
-
return "nudge";
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function getInstalledVersion(projectRoot) {
|
|
147
|
-
const candidates = [
|
|
148
|
-
join(projectRoot, "node_modules", "@theglitchking", "semantic-pages", "package.json"),
|
|
149
|
-
];
|
|
150
|
-
if (process.env.CLAUDE_PLUGIN_ROOT) {
|
|
151
|
-
candidates.push(join(process.env.CLAUDE_PLUGIN_ROOT, "package.json"));
|
|
152
|
-
}
|
|
153
|
-
for (const p of candidates) {
|
|
154
|
-
const pkg = readJson(p);
|
|
155
|
-
if (pkg?.version) return pkg.version;
|
|
156
|
-
}
|
|
157
|
-
return null;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function compareVersions(a, b) {
|
|
161
|
-
const pa = String(a).split(/[.-]/).map((p) => (/^\d+$/.test(p) ? +p : p));
|
|
162
|
-
const pb = String(b).split(/[.-]/).map((p) => (/^\d+$/.test(p) ? +p : p));
|
|
163
|
-
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
164
|
-
const x = pa[i] ?? 0;
|
|
165
|
-
const y = pb[i] ?? 0;
|
|
166
|
-
if (x < y) return -1;
|
|
167
|
-
if (x > y) return 1;
|
|
168
|
-
}
|
|
169
|
-
return 0;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
async function fetchLatestVersion(cacheFile) {
|
|
173
|
-
const cached = readJson(cacheFile);
|
|
174
|
-
if (
|
|
175
|
-
cached?.latest &&
|
|
176
|
-
typeof cached.checkedAt === "number" &&
|
|
177
|
-
Date.now() - cached.checkedAt < CACHE_TTL_MS
|
|
178
|
-
) {
|
|
179
|
-
return cached.latest;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const ctrl = new AbortController();
|
|
183
|
-
const timer = setTimeout(() => ctrl.abort(), NETWORK_TIMEOUT_MS);
|
|
184
|
-
try {
|
|
185
|
-
const res = await fetch(`https://registry.npmjs.org/${PKG}/latest`, {
|
|
186
|
-
signal: ctrl.signal,
|
|
187
|
-
headers: { accept: "application/json" },
|
|
188
|
-
});
|
|
189
|
-
if (!res.ok) return null;
|
|
190
|
-
const json = await res.json();
|
|
191
|
-
const latest = json?.version;
|
|
192
|
-
if (latest) {
|
|
193
|
-
try {
|
|
194
|
-
mkdirSync(dirname(cacheFile), { recursive: true });
|
|
195
|
-
writeFileSync(cacheFile, JSON.stringify({ latest, checkedAt: Date.now() }));
|
|
196
|
-
} catch {}
|
|
197
|
-
}
|
|
198
|
-
return latest || null;
|
|
199
|
-
} catch {
|
|
200
|
-
return null;
|
|
201
|
-
} finally {
|
|
202
|
-
clearTimeout(timer);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
function runAutoUpdate(projectRoot) {
|
|
207
|
-
const r = spawnSync("npm", ["update", PKG], {
|
|
208
|
-
cwd: projectRoot,
|
|
209
|
-
timeout: AUTO_UPDATE_TIMEOUT_MS,
|
|
210
|
-
stdio: "ignore",
|
|
211
|
-
});
|
|
212
|
-
if (r.status !== 0) return false;
|
|
213
|
-
// Re-link skills so any new skills ship to .claude/skills/.
|
|
214
|
-
const linker = join(
|
|
215
|
-
projectRoot,
|
|
216
|
-
"node_modules",
|
|
217
|
-
"@theglitchking",
|
|
218
|
-
"semantic-pages",
|
|
219
|
-
"scripts",
|
|
220
|
-
"link-skills.js",
|
|
221
|
-
);
|
|
222
|
-
if (existsSync(linker)) {
|
|
223
|
-
spawnSync(process.execPath, [linker], {
|
|
224
|
-
cwd: projectRoot,
|
|
225
|
-
env: { ...process.env, INIT_CWD: projectRoot },
|
|
226
|
-
stdio: "ignore",
|
|
227
|
-
timeout: 10_000,
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
return true;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
async function runUpdateCheck(projectRoot) {
|
|
234
|
-
const policy = loadPolicy(projectRoot);
|
|
235
|
-
if (policy === "off") return "";
|
|
236
|
-
const current = getInstalledVersion(projectRoot);
|
|
237
|
-
if (!current) return "";
|
|
238
|
-
const cacheFile = join(projectRoot, ".claude", ".semantic-pages-update-cache.json");
|
|
239
|
-
const latest = await fetchLatestVersion(cacheFile);
|
|
240
|
-
if (!latest || compareVersions(current, latest) >= 0) return "";
|
|
241
|
-
|
|
242
|
-
if (policy === "nudge") {
|
|
243
|
-
return `💡 semantic-pages v${current} → v${latest} available.\n Run /plugin semantic-pages → 'Update now' to upgrade.`;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// auto
|
|
247
|
-
const ok = runAutoUpdate(projectRoot);
|
|
248
|
-
if (ok) return `⬆️ semantic-pages v${current} → v${latest}`;
|
|
249
|
-
return `💡 semantic-pages v${current} → v${latest} available (auto-update failed — run /plugin semantic-pages → 'Update now').`;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
async function main() {
|
|
253
|
-
const projectRoot = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
254
|
-
try {
|
|
255
|
-
process.chdir(projectRoot);
|
|
256
|
-
} catch {
|
|
257
|
-
return emit();
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
try { reconcileMcp(projectRoot); } catch (err) { logWarn(`reconcile failed: ${err.message}`); }
|
|
261
|
-
|
|
262
|
-
// Dedup: if we're the plugin-registered instance (CLAUDE_PLUGIN_ROOT set) and
|
|
263
|
-
// the project has its own semantic-pages hook in .claude/settings.json, the
|
|
264
|
-
// project-registered hook will handle the update check. Defer to it.
|
|
265
|
-
if (process.env.CLAUDE_PLUGIN_ROOT && hasProjectRegisteredHook(projectRoot)) {
|
|
266
|
-
return emit();
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
if (isCi()) return emit();
|
|
270
|
-
|
|
271
|
-
let msg = "";
|
|
272
|
-
try {
|
|
273
|
-
msg = await runUpdateCheck(projectRoot);
|
|
274
|
-
} catch (err) {
|
|
275
|
-
logWarn(`update check failed: ${err.message}`);
|
|
276
|
-
}
|
|
277
|
-
emit(msg || undefined);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
main().catch((err) => {
|
|
281
|
-
logWarn(`fatal: ${err?.message || err}`);
|
|
282
|
-
emit();
|
|
2
|
+
// semantic-pages SessionStart hook. Delegates lifecycle + update-check to
|
|
3
|
+
// @theglitchking/claude-plugin-runtime; plugin-specific .mcp.json wiring
|
|
4
|
+
// lives in ./reconcile.js.
|
|
5
|
+
|
|
6
|
+
import { runSessionStart } from "@theglitchking/claude-plugin-runtime";
|
|
7
|
+
import { reconcile } from "./reconcile.js";
|
|
8
|
+
|
|
9
|
+
await runSessionStart({
|
|
10
|
+
packageName: "@theglitchking/semantic-pages",
|
|
11
|
+
pluginName: "semantic-pages",
|
|
12
|
+
configFile: "semantic-pages.json",
|
|
13
|
+
reconcile,
|
|
283
14
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theglitchking/semantic-pages",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Semantic search + knowledge graph MCP server for markdown vaults. Claude Code plugin with auto-wiring for .claude/.vault and read-only .documentation companion when hit-em-with-the-docs is installed.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/core/index.js",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"dependencies": {
|
|
59
59
|
"@huggingface/transformers": "^4.0.1",
|
|
60
60
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
61
|
-
"
|
|
61
|
+
"@theglitchking/claude-plugin-runtime": "^0.1.0",
|
|
62
62
|
"chokidar": "^5.0.0",
|
|
63
63
|
"commander": "^14.0.3",
|
|
64
64
|
"glob": "^13.0.6",
|
|
@@ -69,6 +69,7 @@
|
|
|
69
69
|
"gray-matter": "^4.0.3",
|
|
70
70
|
"hnswlib-node": "^3.0.0",
|
|
71
71
|
"minimatch": "^10.2.5",
|
|
72
|
+
"onnxruntime-web": "^1.24.3",
|
|
72
73
|
"remark-parse": "^11.0.0",
|
|
73
74
|
"remark-wiki-link": "^2.0.1",
|
|
74
75
|
"unified": "^11.0.5"
|
package/scripts/link-skills.js
CHANGED
|
@@ -1,205 +1,25 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// Postinstall
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// ({ "updatePolicy": "nudge" }) if one doesn't already exist.
|
|
7
|
-
// 3. Register the SessionStart hook in <consumer>/.claude/settings.json —
|
|
8
|
-
// but only if settings.json already exists, the Claude Code plugin
|
|
9
|
-
// version isn't already handling it, and no semantic-pages hook is
|
|
10
|
-
// already registered.
|
|
11
|
-
//
|
|
12
|
-
// Env overrides:
|
|
13
|
-
// SEMANTIC_PAGES_SKIP_LINK=1 — skip skill linking
|
|
14
|
-
// SEMANTIC_PAGES_SKIP_HOOK_REGISTER=1 — skip settings.json hook registration
|
|
15
|
-
//
|
|
16
|
-
// Never hard-fails — any error downgrades to a warning and exit 0 so the
|
|
17
|
-
// package still installs.
|
|
2
|
+
// Postinstall — delegates to @theglitchking/claude-plugin-runtime.
|
|
3
|
+
// The runtime handles: skill symlinking, default policy config write, and
|
|
4
|
+
// settings.json hook registration with plugin/npm dedup. See the runtime's
|
|
5
|
+
// docs/PLUGIN_AUTHORING_SCAFFOLD.md for the full contract.
|
|
18
6
|
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
lstatSync,
|
|
22
|
-
mkdirSync,
|
|
23
|
-
readFileSync,
|
|
24
|
-
readdirSync,
|
|
25
|
-
readlinkSync,
|
|
26
|
-
rmSync,
|
|
27
|
-
symlinkSync,
|
|
28
|
-
writeFileSync,
|
|
29
|
-
} from "node:fs";
|
|
30
|
-
import { homedir } from "node:os";
|
|
31
|
-
import { dirname, join, relative, resolve } from "node:path";
|
|
7
|
+
import { runPostinstall } from "@theglitchking/claude-plugin-runtime";
|
|
8
|
+
import { dirname, resolve } from "node:path";
|
|
32
9
|
import { fileURLToPath } from "node:url";
|
|
33
10
|
|
|
34
|
-
const
|
|
35
|
-
const info = (msg) => console.log(`[semantic-pages] ${msg}`);
|
|
36
|
-
|
|
37
|
-
const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
38
|
-
const CONSUMER_ROOT = process.env.INIT_CWD || process.cwd();
|
|
39
|
-
const HOOK_COMMAND = `node ./node_modules/@theglitchking/semantic-pages/hooks/session-start.js`;
|
|
40
|
-
const HOOK_MARKER = "semantic-pages"; // substring we scan for to detect our hooks
|
|
41
|
-
|
|
42
|
-
function readJson(path, fallback = null) {
|
|
43
|
-
try {
|
|
44
|
-
const raw = readFileSync(path, "utf8");
|
|
45
|
-
return raw.trim() ? JSON.parse(raw) : fallback;
|
|
46
|
-
} catch {
|
|
47
|
-
return fallback;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function writeJson(path, value) {
|
|
52
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
53
|
-
writeFileSync(path, JSON.stringify(value, null, 2) + "\n");
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function lstatSafe(p) {
|
|
57
|
-
try { return lstatSync(p); } catch { return null; }
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function isDeveloperInPlace() {
|
|
61
|
-
return resolve(CONSUMER_ROOT) === resolve(PACKAGE_ROOT);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function linkSkills() {
|
|
65
|
-
if (process.env.SEMANTIC_PAGES_SKIP_LINK === "1") {
|
|
66
|
-
info("SEMANTIC_PAGES_SKIP_LINK=1 — skipping skill linking.");
|
|
67
|
-
return 0;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const skillsSrcDir = join(PACKAGE_ROOT, "skills");
|
|
71
|
-
if (!existsSync(skillsSrcDir)) return 0;
|
|
72
|
-
|
|
73
|
-
const skillDirs = readdirSync(skillsSrcDir, { withFileTypes: true })
|
|
74
|
-
.filter((d) => d.isDirectory() && !d.name.endsWith("-workspace"))
|
|
75
|
-
.map((d) => d.name);
|
|
76
|
-
if (skillDirs.length === 0) return 0;
|
|
77
|
-
|
|
78
|
-
const destDir = join(CONSUMER_ROOT, ".claude", "skills");
|
|
79
|
-
try { mkdirSync(destDir, { recursive: true }); } catch (err) {
|
|
80
|
-
warn(`could not create ${destDir}: ${err.message}`);
|
|
81
|
-
return 0;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
let linked = 0;
|
|
85
|
-
for (const name of skillDirs) {
|
|
86
|
-
const src = join(skillsSrcDir, name);
|
|
87
|
-
const dest = join(destDir, name);
|
|
88
|
-
const rel = relative(dirname(dest), src);
|
|
89
|
-
try {
|
|
90
|
-
const st = lstatSafe(dest);
|
|
91
|
-
if (st) {
|
|
92
|
-
if (st.isSymbolicLink()) {
|
|
93
|
-
if (readlinkSync(dest) === rel) { linked++; continue; }
|
|
94
|
-
rmSync(dest, { force: true });
|
|
95
|
-
} else {
|
|
96
|
-
warn(`skipping ${dest} — a non-symlink already exists there.`);
|
|
97
|
-
continue;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
symlinkSync(rel, dest, "dir");
|
|
101
|
-
linked++;
|
|
102
|
-
} catch (err) {
|
|
103
|
-
warn(`could not link skill "${name}": ${err.message}`);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
return linked;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function writeDefaultConfig() {
|
|
110
|
-
const cfgPath = join(CONSUMER_ROOT, ".claude", "semantic-pages.json");
|
|
111
|
-
if (existsSync(cfgPath)) {
|
|
112
|
-
const cur = readJson(cfgPath);
|
|
113
|
-
return cur?.updatePolicy || "nudge";
|
|
114
|
-
}
|
|
115
|
-
try {
|
|
116
|
-
writeJson(cfgPath, { updatePolicy: "nudge" });
|
|
117
|
-
} catch (err) {
|
|
118
|
-
warn(`could not write ${cfgPath}: ${err.message}`);
|
|
119
|
-
}
|
|
120
|
-
return "nudge";
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function pluginAlreadyHandling() {
|
|
124
|
-
// If the Claude Code plugin is enabled globally, its own hooks.json handles
|
|
125
|
-
// SessionStart — no need to also register in project settings.json.
|
|
126
|
-
const settings = readJson(join(homedir(), ".claude", "settings.json"));
|
|
127
|
-
const enabled = settings?.enabledPlugins || {};
|
|
128
|
-
return Object.keys(enabled).some(
|
|
129
|
-
(k) => k.startsWith("semantic-pages@") && enabled[k] === true,
|
|
130
|
-
);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function settingsHasOurHook(settings) {
|
|
134
|
-
const groups = settings?.hooks?.SessionStart;
|
|
135
|
-
if (!Array.isArray(groups)) return false;
|
|
136
|
-
return groups.some((g) =>
|
|
137
|
-
(g?.hooks || []).some(
|
|
138
|
-
(h) => typeof h?.command === "string" && h.command.includes(HOOK_MARKER),
|
|
139
|
-
),
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function registerHookIfApplicable() {
|
|
144
|
-
if (process.env.SEMANTIC_PAGES_SKIP_HOOK_REGISTER === "1") {
|
|
145
|
-
return { status: "skipped", reason: "env" };
|
|
146
|
-
}
|
|
147
|
-
if (pluginAlreadyHandling()) {
|
|
148
|
-
return { status: "skipped", reason: "plugin" };
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const settingsPath = join(CONSUMER_ROOT, ".claude", "settings.json");
|
|
152
|
-
if (!existsSync(settingsPath)) {
|
|
153
|
-
// Only register when the user has already opted into project settings.
|
|
154
|
-
return { status: "skipped", reason: "no-settings" };
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const settings = readJson(settingsPath);
|
|
158
|
-
if (!settings || typeof settings !== "object") {
|
|
159
|
-
warn(`could not parse ${settingsPath}; not registering hook.`);
|
|
160
|
-
return { status: "skipped", reason: "unparseable" };
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
if (settingsHasOurHook(settings)) {
|
|
164
|
-
return { status: "already" };
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
settings.hooks ||= {};
|
|
168
|
-
settings.hooks.SessionStart ||= [];
|
|
169
|
-
settings.hooks.SessionStart.push({
|
|
170
|
-
hooks: [{ type: "command", command: HOOK_COMMAND }],
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
try {
|
|
174
|
-
writeJson(settingsPath, settings);
|
|
175
|
-
return { status: "registered" };
|
|
176
|
-
} catch (err) {
|
|
177
|
-
warn(`could not write ${settingsPath}: ${err.message}`);
|
|
178
|
-
return { status: "skipped", reason: "write-failed" };
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function main() {
|
|
183
|
-
if (isDeveloperInPlace()) return;
|
|
184
|
-
|
|
185
|
-
const linkedCount = linkSkills();
|
|
186
|
-
const policy = writeDefaultConfig();
|
|
187
|
-
const hookResult = registerHookIfApplicable();
|
|
188
|
-
|
|
189
|
-
const parts = [];
|
|
190
|
-
if (linkedCount > 0) parts.push(`${linkedCount} skill(s) linked`);
|
|
191
|
-
parts.push(`update mode = ${policy}`);
|
|
192
|
-
if (hookResult.status === "registered") parts.push("hook registered");
|
|
193
|
-
else if (hookResult.status === "already") parts.push("hook already registered");
|
|
194
|
-
else if (hookResult.status === "skipped" && hookResult.reason === "plugin") {
|
|
195
|
-
parts.push("hook deferred to plugin");
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
info(`${parts.join(", ")} (change via /plugin semantic-pages)`);
|
|
199
|
-
}
|
|
11
|
+
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
200
12
|
|
|
201
13
|
try {
|
|
202
|
-
|
|
14
|
+
runPostinstall({
|
|
15
|
+
packageName: "@theglitchking/semantic-pages",
|
|
16
|
+
pluginName: "semantic-pages",
|
|
17
|
+
configFile: "semantic-pages.json",
|
|
18
|
+
skillsDir: "skills",
|
|
19
|
+
packageRoot,
|
|
20
|
+
hookCommand:
|
|
21
|
+
"node ./node_modules/@theglitchking/semantic-pages/hooks/session-start.js",
|
|
22
|
+
});
|
|
203
23
|
} catch (err) {
|
|
204
|
-
warn(`postinstall failed: ${err?.message || err}`);
|
|
24
|
+
console.warn(`[semantic-pages] postinstall failed: ${err?.message || err}`);
|
|
205
25
|
}
|