@theglitchking/semantic-memory 1.0.0 → 1.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/dist/cli/index.js CHANGED
@@ -1,19 +1,191 @@
1
1
  #!/usr/bin/env node
2
+ import "../chunk-PZ5AY32C.js";
2
3
 
3
4
  // src/cli/index.ts
4
5
  import { program } from "commander";
5
- import { resolve, join, dirname, relative } from "path";
6
- import { existsSync, readFileSync, writeFileSync, rmSync } from "fs";
6
+ import { resolve as resolve2, join as join2, dirname, relative } from "path";
7
+ import { existsSync as existsSync2, readFileSync, writeFileSync, rmSync } from "fs";
7
8
  import { createRequire } from "module";
8
9
  import { spawnSync } from "child_process";
9
10
  import { registerUpdateCommands } from "@theglitchking/claude-plugin-runtime";
11
+
12
+ // src/cli/skills.ts
13
+ import { mkdir, readFile, writeFile, copyFile, readdir, rm } from "fs/promises";
14
+ import { existsSync } from "fs";
15
+ import { join, resolve } from "path";
16
+ import { homedir } from "os";
17
+ import { createHash } from "crypto";
18
+ var MANIFEST_FILE = ".semantic-memory-skill-manifest.json";
19
+ function resolveTarget(agent, scope, projectRoot) {
20
+ const root = resolve(projectRoot);
21
+ const home = homedir();
22
+ const map = {
23
+ claude: { global: join(home, ".claude", "skills"), local: join(root, ".claude", "skills") },
24
+ codex: { global: join(home, ".codex", "skills"), local: join(root, ".codex", "skills") },
25
+ copilot: { global: join(home, ".copilot", "skills"), local: join(root, ".github", "skills") },
26
+ pi: { global: join(home, ".pi", "agent", "skills"), local: join(root, ".pi", "skills") }
27
+ };
28
+ return { agent, scope, path: map[agent][scope] };
29
+ }
30
+ async function listShippedSkills(sourceRoot) {
31
+ const skillsDir = join(sourceRoot, "skills");
32
+ if (!existsSync(skillsDir)) return [];
33
+ const entries = await readdir(skillsDir, { withFileTypes: true });
34
+ const out = [];
35
+ for (const e of entries) {
36
+ if (!e.isDirectory()) continue;
37
+ const dir = join(skillsDir, e.name);
38
+ const skillFile = join(dir, "SKILL.md");
39
+ if (!existsSync(skillFile)) continue;
40
+ out.push({ name: e.name, dir });
41
+ }
42
+ return out;
43
+ }
44
+ async function sha256OfFile(path) {
45
+ const buf = await readFile(path);
46
+ return createHash("sha256").update(buf).digest("hex");
47
+ }
48
+ async function copyBundle(srcDir, destDir) {
49
+ await mkdir(destDir, { recursive: true });
50
+ const entries = await readdir(srcDir, { withFileTypes: true });
51
+ const written = [];
52
+ for (const e of entries) {
53
+ const src = join(srcDir, e.name);
54
+ const dest = join(destDir, e.name);
55
+ if (e.isDirectory()) {
56
+ const childWritten = await copyBundle(src, dest);
57
+ written.push(...childWritten);
58
+ continue;
59
+ }
60
+ if (!e.isFile()) continue;
61
+ await copyFile(src, dest);
62
+ written.push(dest);
63
+ }
64
+ return written;
65
+ }
66
+ async function installSkills(opts) {
67
+ const target = resolveTarget(opts.agent, opts.scope, opts.projectRoot);
68
+ const shipped = await listShippedSkills(opts.sourceRoot);
69
+ const filter = opts.only && opts.only.length > 0 ? new Set(opts.only) : null;
70
+ const toInstall = filter ? shipped.filter((s) => filter.has(s.name)) : shipped;
71
+ if (toInstall.length === 0) {
72
+ return {
73
+ agent: opts.agent,
74
+ scope: opts.scope,
75
+ target_path: target.path,
76
+ installed: [],
77
+ skipped: [],
78
+ reason: filter ? `No matching skills found (only=${[...filter].join(",")}).` : "No shipped skills found at sourceRoot/skills/."
79
+ };
80
+ }
81
+ const manifestPath = join(target.path, MANIFEST_FILE);
82
+ const existing = await readManifest(manifestPath);
83
+ if (existing && !opts.force) {
84
+ const drift = await detectDrift(existing, toInstall);
85
+ if (drift.length > 0) {
86
+ return {
87
+ agent: opts.agent,
88
+ scope: opts.scope,
89
+ target_path: target.path,
90
+ installed: [],
91
+ skipped: toInstall.map((s) => s.name),
92
+ reason: `Drift detected on ${drift.length} skill(s): ${drift.join(", ")}. Pass --force to overwrite.`,
93
+ manifest_path: manifestPath
94
+ };
95
+ }
96
+ }
97
+ await mkdir(target.path, { recursive: true });
98
+ const installed = [];
99
+ const skillEntries = [];
100
+ for (const skill of toInstall) {
101
+ const destDir = join(target.path, skill.name);
102
+ await copyBundle(skill.dir, destDir);
103
+ const skillFile = join(skill.dir, "SKILL.md");
104
+ skillEntries.push({ name: skill.name, sha256: await sha256OfFile(skillFile) });
105
+ installed.push(skill.name);
106
+ }
107
+ const manifest = {
108
+ package: "@theglitchking/semantic-memory",
109
+ version: opts.version,
110
+ agent: opts.agent,
111
+ scope: opts.scope,
112
+ installed_at: (/* @__PURE__ */ new Date()).toISOString(),
113
+ skills: skillEntries
114
+ };
115
+ await writeFile(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
116
+ return {
117
+ agent: opts.agent,
118
+ scope: opts.scope,
119
+ target_path: target.path,
120
+ installed,
121
+ skipped: [],
122
+ manifest_path: manifestPath
123
+ };
124
+ }
125
+ async function uninstallSkills(opts) {
126
+ const target = resolveTarget(opts.agent, opts.scope, opts.projectRoot);
127
+ const manifestPath = join(target.path, MANIFEST_FILE);
128
+ const manifest = await readManifest(manifestPath);
129
+ if (!manifest) {
130
+ return {
131
+ agent: opts.agent,
132
+ scope: opts.scope,
133
+ target_path: target.path,
134
+ removed: [],
135
+ manifest_present: false,
136
+ reason: "No semantic-memory manifest found \u2014 nothing to uninstall."
137
+ };
138
+ }
139
+ const removed = [];
140
+ for (const skill of manifest.skills) {
141
+ const dir = join(target.path, skill.name);
142
+ if (existsSync(dir)) {
143
+ await rm(dir, { recursive: true, force: true });
144
+ removed.push(skill.name);
145
+ }
146
+ }
147
+ await rm(manifestPath, { force: true });
148
+ return {
149
+ agent: opts.agent,
150
+ scope: opts.scope,
151
+ target_path: target.path,
152
+ removed,
153
+ manifest_present: true
154
+ };
155
+ }
156
+ async function readManifest(path) {
157
+ if (!existsSync(path)) return null;
158
+ try {
159
+ const raw = await readFile(path, "utf-8");
160
+ const parsed = JSON.parse(raw);
161
+ if (typeof parsed?.package !== "string") return null;
162
+ return parsed;
163
+ } catch {
164
+ return null;
165
+ }
166
+ }
167
+ async function detectDrift(existing, toInstall) {
168
+ const driftNames = [];
169
+ const existingByName = new Map(existing.skills.map((s) => [s.name, s.sha256]));
170
+ for (const skill of toInstall) {
171
+ const sourceSha = await sha256OfFile(join(skill.dir, "SKILL.md"));
172
+ const existingSha = existingByName.get(skill.name);
173
+ if (existingSha !== void 0 && existingSha !== sourceSha) {
174
+ driftNames.push(skill.name);
175
+ }
176
+ }
177
+ return driftNames;
178
+ }
179
+ var SKILL_BUNDLER_AGENTS = ["claude", "codex", "copilot", "pi"];
180
+
181
+ // src/cli/index.ts
10
182
  var require_ = createRequire(import.meta.url);
11
183
  var { version } = require_("../../package.json");
12
184
  var PKG_NAME = "@theglitchking/semantic-sidekick";
13
185
  function runRelink(cwd) {
14
- const linker = join(cwd, "node_modules", "@theglitchking", "semantic-sidekick", "scripts", "link-skills.js");
15
- const script = existsSync(linker) ? linker : resolve(process.cwd(), "scripts", "link-skills.js");
16
- if (!existsSync(script)) {
186
+ const linker = join2(cwd, "node_modules", "@theglitchking", "semantic-sidekick", "scripts", "link-skills.js");
187
+ const script = existsSync2(linker) ? linker : resolve2(process.cwd(), "scripts", "link-skills.js");
188
+ if (!existsSync2(script)) {
17
189
  console.error("link-skills.js not found \u2014 is the package installed?");
18
190
  return;
19
191
  }
@@ -24,8 +196,8 @@ function runRelink(cwd) {
24
196
  });
25
197
  }
26
198
  function findLocalBin(cwd) {
27
- const p = join(cwd, "node_modules", "@theglitchking", "semantic-sidekick", "bin", "semantic-sidekick");
28
- return existsSync(p) ? p : null;
199
+ const p = join2(cwd, "node_modules", "@theglitchking", "semantic-sidekick", "bin", "semantic-sidekick");
200
+ return existsSync2(p) ? p : null;
29
201
  }
30
202
  function localBinArg(cwd) {
31
203
  const abs = findLocalBin(cwd);
@@ -271,7 +443,7 @@ program.command("search <query>").description("Run a vault search and print top
271
443
  });
272
444
  program.command("lint").description("Lint the vault against vault.schema.yml (schema violations + missing provenance + stale)").requiredOption("--notes <path>", "Path to markdown notes directory").option("--rule <name>", "Limit output to one rule: schema_violations | missing_provenance | stale").option("--json", "Emit JSON instead of the human-readable report").option("--strict", "Exit non-zero on warnings too (default: exit non-zero only on errors)").action(async (opts) => {
273
445
  const { lintVault, formatLintReport } = await import("../lint-GMXQF4RW.js");
274
- const report = await lintVault(resolve(opts.notes));
446
+ const report = await lintVault(resolve2(opts.notes));
275
447
  if (opts.json) {
276
448
  const out = opts.rule ? report.byRule[opts.rule] : report;
277
449
  console.log(JSON.stringify(out, null, 2));
@@ -294,7 +466,7 @@ program.command("log-event").description("Append a structured event to the vault
294
466
  }
295
467
  }
296
468
  try {
297
- const entry = await logEvent(resolve(opts.notes), {
469
+ const entry = await logEvent(resolve2(opts.notes), {
298
470
  kind: opts.kind,
299
471
  summary: opts.summary,
300
472
  payload
@@ -308,7 +480,7 @@ program.command("log-event").description("Append a structured event to the vault
308
480
  program.command("log-query").description("Read structured log entries from log.md \u2014 filters by kind, date range, limit").requiredOption("--notes <path>", "Path to markdown notes directory").option("--kind <name>", "Filter by kind (ingest, synthesis, error, mode_change, etc.)").option("--after <iso>", "ISO timestamp \u2014 only entries on or after").option("--before <iso>", "ISO timestamp \u2014 only entries on or before").option("--limit <n>", "Max entries (most recent)", (v) => parseInt(v, 10)).action(async (opts) => {
309
481
  const { logQuery } = await import("../log-LQXIJXIA.js");
310
482
  try {
311
- const entries = await logQuery(resolve(opts.notes), {
483
+ const entries = await logQuery(resolve2(opts.notes), {
312
484
  kind: opts.kind,
313
485
  after: opts.after,
314
486
  before: opts.before,
@@ -322,7 +494,7 @@ program.command("log-query").description("Read structured log entries from log.m
322
494
  });
323
495
  program.command("install-schema").description("Bootstrap vault.schema.yml in the vault root with the default schema").requiredOption("--notes <path>", "Path to markdown notes directory").option("--force", "Overwrite existing schema file").action(async (opts) => {
324
496
  const { installDefaultSchema } = await import("../schema-F3RTY6SQ.js");
325
- const r = await installDefaultSchema(resolve(opts.notes), opts.force);
497
+ const r = await installDefaultSchema(resolve2(opts.notes), opts.force);
326
498
  console.log(r.written ? `Installed default schema: ${r.path}` : `Schema already exists: ${r.path} (use --force to overwrite)`);
327
499
  process.exit(0);
328
500
  });
@@ -335,8 +507,8 @@ program.command("tools [name]").description("List all MCP tools, or show details
335
507
  process.exit(0);
336
508
  });
337
509
  program.command("serve", { isDefault: true }).description("Start the MCP server (default command)").requiredOption("--notes <path>", "Path to markdown notes directory").option("--reindex", "Force full reindex and exit").option("--stats", "Show vault statistics and exit").option("--wait-for-ready", "Block startup until index is fully built before serving (default: index in background; tools return 'Indexing in progress' until ready)").option("--read-only", "Suppress write tools (create_note, update_note, delete_note, move_note, update_frontmatter, manage_tags, rename_tag) \u2014 use for shared docs vaults owned by another tool").option("--model <name>", "Embedding model to use (default: all-MiniLM-L6-v2, fast; use nomic-ai/nomic-embed-text-v1.5 for higher quality)").option("--workers <n>", "Number of worker threads for parallel embedding", parseInt).option("--batch-size <n>", "Texts per ONNX forward pass (default: 8)", parseInt).option("--no-quantized", "Use full-precision model instead of quantized (slower, slightly higher quality)").option("--no-watch", "Disable file watcher").action(async (opts) => {
338
- const notesPath = resolve(opts.notes);
339
- if (!existsSync(notesPath)) {
510
+ const notesPath = resolve2(opts.notes);
511
+ if (!existsSync2(notesPath)) {
340
512
  console.error(`Error: notes directory not found: ${notesPath}`);
341
513
  process.exit(1);
342
514
  }
@@ -388,8 +560,8 @@ program.command("normalize-config").description(
388
560
  "Rewrite fragile `npx @latest` entries in .mcp.json to the stable node-against-node_modules form (with backup and validation)"
389
561
  ).option("--dry-run", "print the proposed changes but don't write").action((opts) => {
390
562
  const cwd = process.cwd();
391
- const mcpPath = join(cwd, ".mcp.json");
392
- if (!existsSync(mcpPath)) {
563
+ const mcpPath = join2(cwd, ".mcp.json");
564
+ if (!existsSync2(mcpPath)) {
393
565
  console.log("no .mcp.json in current directory \u2014 nothing to do");
394
566
  process.exit(0);
395
567
  }
@@ -429,7 +601,7 @@ run 'npm install --save @theglitchking/semantic-sidekick' first, then re-run thi
429
601
  console.log("--- (dry-run; no changes written)");
430
602
  process.exit(0);
431
603
  }
432
- const bakPath = join(dirname(mcpPath), ".mcp.json.bak");
604
+ const bakPath = join2(dirname(mcpPath), ".mcp.json.bak");
433
605
  try {
434
606
  writeFileSync(bakPath, readFileSync(mcpPath, "utf8"));
435
607
  console.log(`backup written: ${bakPath}`);
@@ -450,8 +622,8 @@ ${verify.stderr?.toString() || ""}`);
450
622
  process.exit(0);
451
623
  });
452
624
  program.command("healthcheck").description(
453
- "Verify the local install starts cleanly; self-heal common npx-cache corruption (ERR_MODULE_NOT_FOUND)"
454
- ).action(() => {
625
+ "Verify the local install starts cleanly + run drift detection. Self-heals common npx-cache corruption (ERR_MODULE_NOT_FOUND). With --fast, skips the slow tier (full vault lint)."
626
+ ).option("--fast", "Run only the fast-tier drift checks (file-system probes). Skips the slow tier (full vault lint).", false).option("--json", "Emit structured JSON output instead of human-readable text.", false).action(async (opts) => {
455
627
  const cwd = process.cwd();
456
628
  const bin = findLocalBin(cwd);
457
629
  if (!bin) {
@@ -459,8 +631,8 @@ program.command("healthcheck").description(
459
631
  console.error(`run 'npm install --save @theglitchking/semantic-sidekick' to install.`);
460
632
  process.exit(1);
461
633
  }
462
- const mcpPath = join(cwd, ".mcp.json");
463
- if (existsSync(mcpPath)) {
634
+ const mcpPath = join2(cwd, ".mcp.json");
635
+ if (existsSync2(mcpPath)) {
464
636
  const data = readJsonSafe(mcpPath);
465
637
  const entries = data?.mcpServers && typeof data.mcpServers === "object" ? Object.entries(data.mcpServers) : [];
466
638
  const fragile = entries.filter(([, e]) => isNpxForm(e)).map(([k]) => k);
@@ -498,8 +670,113 @@ ${r2.stderr?.toString() || ""}`);
498
670
  console.error(`ERR_MODULE_NOT_FOUND but could not locate offending cache path in the error.`);
499
671
  }
500
672
  }
501
- console.error(stderr || `local install failed (exit ${r.status})`);
502
- process.exit(1);
673
+ if (stderr) console.error(stderr);
674
+ if (r.status !== 0) {
675
+ console.error(`local install failed (exit ${r.status})`);
676
+ process.exit(1);
677
+ }
678
+ });
679
+ var driftHealthcheckActionPostprocess = async (opts) => {
680
+ try {
681
+ const { runHealthcheck, formatDriftBanner } = await import("../healthcheck-DCFGPI7M.js");
682
+ const projectRoot = process.cwd();
683
+ const vaultCandidate = join2(projectRoot, ".claude", ".vault");
684
+ const vaultPath = existsSync2(vaultCandidate) ? vaultCandidate : void 0;
685
+ const result = await runHealthcheck({
686
+ projectRoot,
687
+ vaultPath,
688
+ tier: opts.fast ? "fast" : "all",
689
+ force: true
690
+ });
691
+ if (opts.json) {
692
+ console.log(JSON.stringify(result, null, 2));
693
+ return;
694
+ }
695
+ const banner = formatDriftBanner(result);
696
+ if (banner) {
697
+ console.log("");
698
+ console.log(banner);
699
+ } else {
700
+ console.log("");
701
+ console.log(`\u2713 no drift detected (${result.findings.length} checks ran in ${result.duration_ms}ms)`);
702
+ }
703
+ } catch (err) {
704
+ console.error(`drift check threw: ${err?.message ?? err}`);
705
+ }
706
+ };
707
+ var healthcheckCmd = program.commands.find((c) => c.name() === "healthcheck");
708
+ if (healthcheckCmd) {
709
+ healthcheckCmd.hook("postAction", async (thisCmd) => {
710
+ const opts = thisCmd.opts();
711
+ await driftHealthcheckActionPostprocess({ fast: !!opts.fast, json: !!opts.json });
712
+ });
713
+ }
714
+ function parseAgent(input) {
715
+ const a = input.toLowerCase();
716
+ if (!SKILL_BUNDLER_AGENTS.includes(a)) {
717
+ console.error(`Unknown agent: ${input}. Supported: ${SKILL_BUNDLER_AGENTS.join(", ")}.`);
718
+ process.exit(2);
719
+ }
720
+ return a;
721
+ }
722
+ function parseScope(input) {
723
+ if (input !== "global" && input !== "local") {
724
+ console.error(`Unknown scope: ${input}. Use 'global' or 'local'.`);
725
+ process.exit(2);
726
+ }
727
+ return input;
728
+ }
729
+ function findSourceRoot() {
730
+ for (const pkg of ["semantic-memory", "semantic-sidekick"]) {
731
+ const installed = join2(process.cwd(), "node_modules", "@theglitchking", pkg);
732
+ if (existsSync2(installed)) return installed;
733
+ }
734
+ const dev = resolve2(new URL("../../", import.meta.url).pathname);
735
+ return dev;
736
+ }
737
+ var skillsCmd = program.command("skills").description("Install, target, or uninstall skill bundles for non-Claude agents (codex, copilot, pi). Claude installs continue to use the npm postinstall flow.");
738
+ skillsCmd.command("targets").description("Preview where skill bundles would be installed for one or more agents.").option("--agent <name...>", "One or more agents (default: all). Repeatable.", void 0).option("--scope <scope>", "global or local (default: local)", "local").option("--project <path>", "Project root for local-scope paths (default: cwd)", process.cwd()).action((opts) => {
739
+ const scope = parseScope(opts.scope);
740
+ const agents = (opts.agent && opts.agent.length > 0 ? opts.agent : SKILL_BUNDLER_AGENTS).map(parseAgent);
741
+ const out = agents.map((a) => resolveTarget(a, scope, opts.project));
742
+ console.log(JSON.stringify(out, null, 2));
743
+ });
744
+ skillsCmd.command("install").description("Install skill bundles into an agent's skill directory. Non-destructive by default; --force to overwrite.").requiredOption("--agent <name>", "Agent runtime (claude, codex, copilot, pi)").option("--scope <scope>", "global or local (default: local)", "local").option("--project <path>", "Project root for local-scope paths (default: cwd)", process.cwd()).option("--only <names...>", "Restrict to specific skill names (default: all shipped)").option("--force", "Overwrite even if drift is detected against an existing manifest", false).action(async (opts) => {
745
+ const agent = parseAgent(opts.agent);
746
+ const scope = parseScope(opts.scope);
747
+ if (agent === "claude") {
748
+ console.error(
749
+ "Claude skill installs are handled by the npm postinstall (claude-plugin-runtime). Run `npm install @theglitchking/semantic-memory` instead \u2014 that flow symlinks skills into ~/.claude/skills/ or .claude/skills/ automatically. The skills CLI is intended for non-Claude agents (codex, copilot, pi) which do not have that postinstall integration."
750
+ );
751
+ process.exit(2);
752
+ }
753
+ const sourceRoot = findSourceRoot();
754
+ const result = await installSkills({
755
+ agent,
756
+ scope,
757
+ sourceRoot,
758
+ projectRoot: opts.project,
759
+ version,
760
+ only: opts.only,
761
+ force: opts.force === true
762
+ });
763
+ console.log(JSON.stringify(result, null, 2));
764
+ if (result.installed.length === 0 && result.reason) process.exit(1);
765
+ });
766
+ skillsCmd.command("uninstall").description("Remove skill bundles tracked by the semantic-memory manifest. User-written skills outside the manifest are left untouched.").requiredOption("--agent <name>", "Agent runtime (claude, codex, copilot, pi)").option("--scope <scope>", "global or local (default: local)", "local").option("--project <path>", "Project root for local-scope paths (default: cwd)", process.cwd()).action(async (opts) => {
767
+ const agent = parseAgent(opts.agent);
768
+ const scope = parseScope(opts.scope);
769
+ if (agent === "claude") {
770
+ console.error("Claude skill uninstalls go through the npm uninstall flow. The skills CLI is for non-Claude agents.");
771
+ process.exit(2);
772
+ }
773
+ const result = await uninstallSkills({ agent, scope, projectRoot: opts.project });
774
+ console.log(JSON.stringify(result, null, 2));
775
+ });
776
+ skillsCmd.command("list").description("List the skill bundles shipped with this plugin version.").action(async () => {
777
+ const sourceRoot = findSourceRoot();
778
+ const shipped = await listShippedSkills(sourceRoot);
779
+ console.log(JSON.stringify(shipped.map((s) => s.name), null, 2));
503
780
  });
504
781
  program.parse();
505
782
  //# sourceMappingURL=index.js.map