myagentmemory 0.4.4 → 0.4.6

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/README.md CHANGED
@@ -23,8 +23,7 @@ bun run build:cli
23
23
  agent-memory init
24
24
 
25
25
  # Install skill files for Claude Code, Codex, Cursor, and Agent
26
- bash scripts/install-skills.sh
27
- pwsh -File scripts/install-skills.ps1
26
+ agent-memory install-skills
28
27
  ```
29
28
 
30
29
  This installs:
@@ -84,6 +83,7 @@ The memory directory defaults to `~/.agent-memory/`. Override with `AGENT_MEMORY
84
83
  | `agent-memory read --target <long_term\|scratchpad\|daily\|list> [--date YYYY-MM-DD]` | Read memory files |
85
84
  | `agent-memory scratchpad <add\|done\|undo\|clear_done\|list> [--text <text>]` | Manage checklist |
86
85
  | `agent-memory search --query <text> [--mode keyword\|semantic\|deep] [--limit N]` | Search via qmd |
86
+ | `agent-memory install-skills` | Install bundled SKILL.md files into local agent directories |
87
87
  | `agent-memory init` | Create dirs, detect qmd, setup collection |
88
88
  | `agent-memory status` | Show config, qmd status, file counts |
89
89
 
@@ -186,8 +186,7 @@ bun run build:cli
186
186
  agent-memory write --target long_term --content "test" && agent-memory read --target long_term
187
187
 
188
188
  # Install skills
189
- bash scripts/install-skills.sh
190
- pwsh -File scripts/install-skills.ps1
189
+ agent-memory install-skills
191
190
  ```
192
191
 
193
192
  ## Publishing (maintainers)
package/dist/agent-memory CHANGED
Binary file
package/dist/cli.d.ts CHANGED
@@ -3,6 +3,8 @@
3
3
  * agent-memory CLI
4
4
  *
5
5
  * Subcommands:
6
+ * version — Print binary version
7
+ * install-skills — Install SKILL.md files into local agent directories
6
8
  * context — Build & print context injection string to stdout
7
9
  * write — Write to memory files
8
10
  * read — Read memory files
package/dist/cli.js CHANGED
@@ -3,6 +3,8 @@
3
3
  * agent-memory CLI
4
4
  *
5
5
  * Subcommands:
6
+ * version — Print binary version
7
+ * install-skills — Install SKILL.md files into local agent directories
6
8
  * context — Build & print context injection string to stdout
7
9
  * write — Write to memory files
8
10
  * read — Read memory files
@@ -16,7 +18,8 @@
16
18
  * --json Machine-readable JSON output
17
19
  */
18
20
  import * as fs from "node:fs";
19
- import { _setBaseDir, buildMemoryContext, checkCollection, dailyPath, detectQmd, ensureDirs, ensureQmdAvailableForUpdate, getCollectionName, getDailyDir, getMemoryDir, getMemoryFile, getQmdResultPath, getQmdResultText, getScratchpadFile, nowTimestamp, parseScratchpad, readFileSafe, runQmdSearch, scheduleQmdUpdate, searchRelevantMemories, serializeScratchpad, setupQmdCollection, todayStr, } from "./core.js";
21
+ const VERSION = typeof __VERSION__ !== "undefined" ? __VERSION__ : "dev";
22
+ import { _setBaseDir, buildMemoryContext, checkCollection, dailyPath, detectQmd, ensureDirs, ensureQmdAvailableForSync, ensureQmdAvailableForUpdate, getCollectionName, getDailyDir, getMemoryDir, getMemoryFile, getQmdEmbedMode, getQmdHealth, getQmdResultPath, getQmdResultText, getScratchpadFile, installSkills, nowTimestamp, parseScratchpad, readFileSafe, runQmdEmbedDetached, runQmdSearch, runQmdSync, runQmdUpdateNow, scheduleQmdUpdate, searchRelevantMemories, serializeScratchpad, setupQmdCollection, todayStr, } from "./core.js";
20
23
  function parseArgs(argv) {
21
24
  const flags = {};
22
25
  const positional = [];
@@ -319,18 +322,91 @@ async function cmdSearch(flags) {
319
322
  exitError(`Search failed: ${err instanceof Error ? err.message : String(err)}`, json);
320
323
  }
321
324
  }
325
+ function cmdInstallSkills(flags) {
326
+ const json = hasFlag(flags, "json");
327
+ const report = installSkills();
328
+ if (!report.ok) {
329
+ exitError(report.error ?? "Failed to install skills.", json);
330
+ }
331
+ if (json) {
332
+ output(report, true);
333
+ return;
334
+ }
335
+ if (report.detected.length === 0) {
336
+ console.log("No supported agent installations detected.");
337
+ }
338
+ else {
339
+ const detectedLabels = report.detected.map((item) => item.label).join(", ");
340
+ console.log(`Detected: ${detectedLabels}`);
341
+ }
342
+ if (report.installed.length === 0) {
343
+ console.log("No skills installed.");
344
+ }
345
+ else {
346
+ for (const item of report.installed) {
347
+ console.log(`Installed ${item.label}: ${item.path}`);
348
+ }
349
+ }
350
+ if (report.skipped.length > 0) {
351
+ for (const item of report.skipped) {
352
+ console.log(`Skipped ${item.label} (${item.reason})`);
353
+ }
354
+ }
355
+ }
356
+ async function cmdSync(flags) {
357
+ const json = hasFlag(flags, "json");
358
+ ensureDirs();
359
+ const qmdFound = await ensureQmdAvailableForSync();
360
+ if (!qmdFound) {
361
+ exitError("qmd is not installed. Install: bun install -g https://github.com/tobi/qmd", json);
362
+ }
363
+ const collName = getCollectionName();
364
+ const hasCollection = await checkCollection(collName);
365
+ if (!hasCollection) {
366
+ exitError(`qmd collection '${collName}' not found. Run: agent-memory init`, json);
367
+ }
368
+ const result = await runQmdSync();
369
+ if (json) {
370
+ output({ ok: result.updateOk && result.embedOk, updateOk: result.updateOk, embedOk: result.embedOk }, true);
371
+ }
372
+ else {
373
+ if (result.updateOk) {
374
+ console.log("qmd update: ok");
375
+ }
376
+ else {
377
+ console.log("qmd update: failed");
378
+ }
379
+ if (result.embedOk) {
380
+ console.log("qmd embed: ok");
381
+ }
382
+ else {
383
+ console.log("qmd embed: failed");
384
+ }
385
+ if (result.updateOk && result.embedOk) {
386
+ console.log("\nIndex fully synced.");
387
+ }
388
+ }
389
+ }
322
390
  async function cmdInit(flags) {
323
391
  const json = hasFlag(flags, "json");
324
392
  ensureDirs();
325
393
  const dir = getMemoryDir();
326
394
  const qmdFound = await detectQmd();
327
395
  let collectionCreated = false;
396
+ let indexUpdated = false;
397
+ let embedStarted = false;
328
398
  if (qmdFound) {
329
399
  const collName = getCollectionName();
330
400
  const hasCollection = await checkCollection(collName);
331
401
  if (!hasCollection) {
332
402
  collectionCreated = await setupQmdCollection();
333
403
  }
404
+ // Run initial index update + start background embed
405
+ await ensureQmdAvailableForUpdate();
406
+ await runQmdUpdateNow();
407
+ indexUpdated = true;
408
+ const child = runQmdEmbedDetached();
409
+ embedStarted = child !== null;
334
410
  }
335
411
  if (json) {
336
412
  output({
@@ -338,6 +414,8 @@ async function cmdInit(flags) {
338
414
  directory: dir,
339
415
  qmd: qmdFound,
340
416
  collectionCreated,
417
+ indexUpdated,
418
+ embedStarted,
341
419
  }, true);
342
420
  }
343
421
  else {
@@ -350,6 +428,12 @@ async function cmdInit(flags) {
350
428
  else {
351
429
  console.log(` qmd collection '${getCollectionName()}' already exists.`);
352
430
  }
431
+ if (indexUpdated) {
432
+ console.log(` Index updated.`);
433
+ }
434
+ if (embedStarted) {
435
+ console.log(` Embedding started in background.`);
436
+ }
353
437
  }
354
438
  else {
355
439
  console.log(` qmd not found — search features unavailable.`);
@@ -375,9 +459,15 @@ async function cmdStatus(flags) {
375
459
  }
376
460
  const qmdFound = await detectQmd();
377
461
  let hasCollection = false;
462
+ let health = null;
378
463
  if (qmdFound) {
379
464
  hasCollection = await checkCollection();
465
+ if (hasCollection) {
466
+ await ensureQmdAvailableForSync();
467
+ health = await getQmdHealth();
468
+ }
380
469
  }
470
+ const embedMode = getQmdEmbedMode();
381
471
  if (json) {
382
472
  output({
383
473
  directory: dir,
@@ -395,7 +485,9 @@ async function cmdStatus(flags) {
395
485
  qmd: {
396
486
  available: qmdFound,
397
487
  collection: hasCollection ? getCollectionName() : null,
488
+ health,
398
489
  },
490
+ embedMode,
399
491
  }, true);
400
492
  }
401
493
  else {
@@ -421,6 +513,19 @@ async function cmdStatus(flags) {
421
513
  if (qmdFound) {
422
514
  console.log(`qmd: available`);
423
515
  console.log(`Collection '${getCollectionName()}': ${hasCollection ? "configured" : "not configured — run: agent-memory init"}`);
516
+ console.log(`Embed mode: ${embedMode}`);
517
+ if (health) {
518
+ if (health.totalFiles !== null)
519
+ console.log(`Files indexed: ${health.totalFiles}`);
520
+ if (health.vectorsEmbedded !== null)
521
+ console.log(`Vectors embedded: ${health.vectorsEmbedded}`);
522
+ if (health.pendingEmbed !== null && health.pendingEmbed > 0) {
523
+ console.log(`Pending embeds: ${health.pendingEmbed}`);
524
+ console.log(` run: agent-memory sync`);
525
+ }
526
+ if (health.lastUpdated)
527
+ console.log(`Last updated: ${health.lastUpdated}`);
528
+ }
424
529
  }
425
530
  else {
426
531
  console.log("qmd: not installed");
@@ -437,11 +542,14 @@ Usage:
437
542
  agent-memory <command> [options]
438
543
 
439
544
  Commands:
545
+ version Show binary version
546
+ install-skills Install bundled skills into local agent directories
440
547
  context Build & print context injection string
441
548
  write Write to memory files
442
549
  read Read memory files
443
550
  scratchpad Manage checklist items
444
551
  search Search across memory files (requires qmd)
552
+ sync Re-index and embed all files (requires qmd)
445
553
  init Initialize memory directory and qmd collection
446
554
  status Show configuration and status
447
555
 
@@ -461,6 +569,7 @@ Examples:
461
569
  agent-memory scratchpad done --text "PR #42"
462
570
  agent-memory search --query "database choice" --mode keyword
463
571
  agent-memory context --no-search
572
+ agent-memory sync
464
573
  agent-memory status --json`);
465
574
  }
466
575
  // ---------------------------------------------------------------------------
@@ -474,6 +583,10 @@ async function main() {
474
583
  if (dir) {
475
584
  _setBaseDir(dir);
476
585
  }
586
+ if (command === "version" || hasFlag(flags, "version")) {
587
+ output(json ? { version: VERSION } : VERSION, json);
588
+ return;
589
+ }
477
590
  if (!command || command === "help" || hasFlag(flags, "help")) {
478
591
  printUsage();
479
592
  return;
@@ -494,6 +607,12 @@ async function main() {
494
607
  case "search":
495
608
  await cmdSearch(flags);
496
609
  break;
610
+ case "install-skills":
611
+ cmdInstallSkills(flags);
612
+ break;
613
+ case "sync":
614
+ await cmdSync(flags);
615
+ break;
497
616
  case "init":
498
617
  await cmdInit(flags);
499
618
  break;
package/dist/core.d.ts CHANGED
@@ -4,7 +4,8 @@
4
4
  * Core logic for agent-memory CLI and skills.
5
5
  * Zero pi peer dependencies — only node:fs, node:path, node:child_process.
6
6
  */
7
- import { execFile } from "node:child_process";
7
+ import type { ChildProcess } from "node:child_process";
8
+ import { execFile, spawn } from "node:child_process";
8
9
  /** Override base directory (for testing or platform-specific defaults). */
9
10
  export declare function _setBaseDir(baseDir: string): void;
10
11
  /** Reset to default paths. */
@@ -59,10 +60,19 @@ export declare function parseScratchpad(content: string): ScratchpadItem[];
59
60
  export declare function serializeScratchpad(items: ScratchpadItem[]): string;
60
61
  export declare function buildMemoryContext(searchResults?: string): string;
61
62
  type ExecFileFn = typeof execFile;
63
+ type SpawnFn = typeof spawn;
62
64
  /** Override execFile implementation (for testing). */
63
65
  export declare function _setExecFileForTest(fn: ExecFileFn): void;
64
66
  /** Reset execFile implementation (for testing). */
65
67
  export declare function _resetExecFileForTest(): void;
68
+ /** Override skills root directory (for testing). */
69
+ export declare function _setSkillsRootForTest(dir: string | null): void;
70
+ /** Override home directory (for testing). */
71
+ export declare function _setHomeDirForTest(dir: string | null): void;
72
+ /** Override spawn implementation (for testing). */
73
+ export declare function _setSpawnForTest(fn: SpawnFn): void;
74
+ /** Reset spawn implementation (for testing). */
75
+ export declare function _resetSpawnForTest(): void;
66
76
  /** Set qmd availability flag (for testing). */
67
77
  export declare function _setQmdAvailable(value: boolean): void;
68
78
  /** Get current qmd availability flag. */
@@ -71,6 +81,10 @@ export declare function _getQmdAvailable(): boolean;
71
81
  export declare function _getUpdateTimer(): ReturnType<typeof setTimeout> | null;
72
82
  /** Clear the update timer (for testing). */
73
83
  export declare function _clearUpdateTimer(): void;
84
+ /** Get current embed timer (for testing). */
85
+ export declare function _getEmbedTimer(): ReturnType<typeof setTimeout> | null;
86
+ /** Clear the embed timer (for testing). */
87
+ export declare function _clearEmbedTimer(): void;
74
88
  /** Get the current QMD collection name. */
75
89
  export declare function getCollectionName(): string;
76
90
  /** Set the QMD collection name (for platform-specific overrides). */
@@ -83,8 +97,47 @@ export declare function detectQmd(): Promise<boolean>;
83
97
  export declare function checkCollection(name?: string): Promise<boolean>;
84
98
  export declare function getQmdUpdateMode(): "background" | "manual" | "off";
85
99
  export declare function ensureQmdAvailableForUpdate(): Promise<boolean>;
100
+ export declare function getQmdEmbedMode(): "background" | "manual" | "off";
101
+ export declare function runQmdEmbedDetached(): ChildProcess | null;
102
+ export declare function scheduleQmdEmbed(): void;
86
103
  export declare function scheduleQmdUpdate(): void;
87
104
  export declare function runQmdUpdateNow(): Promise<void>;
105
+ export declare function runQmdEmbedNow(): Promise<boolean>;
106
+ export declare function ensureQmdAvailableForSync(): Promise<boolean>;
107
+ export declare function runQmdSync(): Promise<{
108
+ updateOk: boolean;
109
+ embedOk: boolean;
110
+ }>;
111
+ export interface InstallSkillsReport {
112
+ ok: boolean;
113
+ projectDir?: string;
114
+ homeDir?: string;
115
+ detected: Array<{
116
+ label: string;
117
+ homeMarker: string;
118
+ }>;
119
+ installed: Array<{
120
+ label: string;
121
+ path: string;
122
+ }>;
123
+ skipped: Array<{
124
+ label: string;
125
+ reason: string;
126
+ }>;
127
+ error?: string;
128
+ }
129
+ export declare function installSkills(): InstallSkillsReport;
130
+ export interface QmdHealthInfo {
131
+ totalFiles: number | null;
132
+ vectorsEmbedded: number | null;
133
+ pendingEmbed: number | null;
134
+ lastUpdated: string | null;
135
+ collectionFiles: number | null;
136
+ collectionUpdated: string | null;
137
+ embedMode: string;
138
+ }
139
+ export declare function parseQmdStatus(stdout: string, collectionName: string): QmdHealthInfo;
140
+ export declare function getQmdHealth(): Promise<QmdHealthInfo | null>;
88
141
  /** Search for memories relevant to the user's prompt. Returns formatted markdown or empty string on error. */
89
142
  export declare function searchRelevantMemories(prompt: string): Promise<string>;
90
143
  export interface QmdSearchResult {
package/dist/core.js CHANGED
@@ -4,7 +4,7 @@
4
4
  * Core logic for agent-memory CLI and skills.
5
5
  * Zero pi peer dependencies — only node:fs, node:path, node:child_process.
6
6
  */
7
- import { execFile } from "node:child_process";
7
+ import { execFile, spawn } from "node:child_process";
8
8
  import * as fs from "node:fs";
9
9
  import * as path from "node:path";
10
10
  // ---------------------------------------------------------------------------
@@ -280,8 +280,12 @@ export function buildMemoryContext(searchResults) {
280
280
  return context;
281
281
  }
282
282
  let execFileFn = execFile;
283
+ let spawnFn = spawn;
283
284
  let qmdAvailable = false;
284
285
  let updateTimer = null;
286
+ let embedTimer = null;
287
+ let skillsRootOverride = null;
288
+ let homeDirOverride = null;
285
289
  /** QMD collection name — configurable per platform. */
286
290
  let QMD_COLLECTION_NAME = "agent-memory";
287
291
  /** Override execFile implementation (for testing). */
@@ -292,6 +296,22 @@ export function _setExecFileForTest(fn) {
292
296
  export function _resetExecFileForTest() {
293
297
  execFileFn = execFile;
294
298
  }
299
+ /** Override skills root directory (for testing). */
300
+ export function _setSkillsRootForTest(dir) {
301
+ skillsRootOverride = dir;
302
+ }
303
+ /** Override home directory (for testing). */
304
+ export function _setHomeDirForTest(dir) {
305
+ homeDirOverride = dir;
306
+ }
307
+ /** Override spawn implementation (for testing). */
308
+ export function _setSpawnForTest(fn) {
309
+ spawnFn = fn;
310
+ }
311
+ /** Reset spawn implementation (for testing). */
312
+ export function _resetSpawnForTest() {
313
+ spawnFn = spawn;
314
+ }
295
315
  /** Set qmd availability flag (for testing). */
296
316
  export function _setQmdAvailable(value) {
297
317
  qmdAvailable = value;
@@ -311,6 +331,51 @@ export function _clearUpdateTimer() {
311
331
  updateTimer = null;
312
332
  }
313
333
  }
334
+ /** Get current embed timer (for testing). */
335
+ export function _getEmbedTimer() {
336
+ return embedTimer;
337
+ }
338
+ /** Clear the embed timer (for testing). */
339
+ export function _clearEmbedTimer() {
340
+ if (embedTimer) {
341
+ clearTimeout(embedTimer);
342
+ embedTimer = null;
343
+ }
344
+ }
345
+ function resolveHomeDir() {
346
+ if (homeDirOverride !== null)
347
+ return homeDirOverride;
348
+ return process.env.HOME ?? process.env.USERPROFILE ?? null;
349
+ }
350
+ function findSkillsRoot() {
351
+ const envRoot = process.env.AGENT_MEMORY_SKILLS_ROOT;
352
+ if (envRoot)
353
+ return envRoot;
354
+ if (skillsRootOverride)
355
+ return skillsRootOverride;
356
+ const scanUp = (startDir) => {
357
+ let dir = startDir;
358
+ while (true) {
359
+ if (fs.existsSync(path.join(dir, "skills")))
360
+ return dir;
361
+ const parent = path.dirname(dir);
362
+ if (parent === dir)
363
+ return null;
364
+ dir = parent;
365
+ }
366
+ };
367
+ const argvPath = process.argv[1];
368
+ if (argvPath) {
369
+ const found = scanUp(path.resolve(path.dirname(argvPath)));
370
+ if (found)
371
+ return found;
372
+ }
373
+ const execDir = path.dirname(process.execPath);
374
+ const found = scanUp(path.resolve(execDir));
375
+ if (found)
376
+ return found;
377
+ return scanUp(path.resolve(process.cwd()));
378
+ }
314
379
  /** Get the current QMD collection name. */
315
380
  export function getCollectionName() {
316
381
  return QMD_COLLECTION_NAME;
@@ -425,6 +490,39 @@ export async function ensureQmdAvailableForUpdate() {
425
490
  qmdAvailable = await detectQmd();
426
491
  return qmdAvailable;
427
492
  }
493
+ export function getQmdEmbedMode() {
494
+ const mode = (process.env.AGENT_MEMORY_QMD_EMBED ?? "background").toLowerCase();
495
+ if (mode === "manual" || mode === "off" || mode === "background") {
496
+ return mode;
497
+ }
498
+ return "background";
499
+ }
500
+ export function runQmdEmbedDetached() {
501
+ if (!qmdAvailable)
502
+ return null;
503
+ const child = spawnFn("qmd", ["embed"], {
504
+ detached: true,
505
+ stdio: "ignore",
506
+ });
507
+ child.unref();
508
+ return child;
509
+ }
510
+ export function scheduleQmdEmbed() {
511
+ if (getQmdEmbedMode() !== "background")
512
+ return;
513
+ if (!qmdAvailable)
514
+ return;
515
+ if (embedTimer)
516
+ clearTimeout(embedTimer);
517
+ embedTimer = setTimeout(() => {
518
+ embedTimer = null;
519
+ runQmdEmbedDetached();
520
+ }, 5_000);
521
+ // Don't prevent process exit while waiting to embed
522
+ if (embedTimer && typeof embedTimer === "object" && "unref" in embedTimer) {
523
+ embedTimer.unref();
524
+ }
525
+ }
428
526
  export function scheduleQmdUpdate() {
429
527
  if (getQmdUpdateMode() !== "background")
430
528
  return;
@@ -434,7 +532,9 @@ export function scheduleQmdUpdate() {
434
532
  clearTimeout(updateTimer);
435
533
  updateTimer = setTimeout(() => {
436
534
  updateTimer = null;
437
- execFileFn("qmd", ["update"], { timeout: 30_000 }, () => { });
535
+ execFileFn("qmd", ["update"], { timeout: 30_000 }, () => {
536
+ scheduleQmdEmbed();
537
+ });
438
538
  }, 500);
439
539
  }
440
540
  export async function runQmdUpdateNow() {
@@ -446,6 +546,176 @@ export async function runQmdUpdateNow() {
446
546
  execFileFn("qmd", ["update"], { timeout: 30_000 }, () => resolve());
447
547
  });
448
548
  }
549
+ export async function runQmdEmbedNow() {
550
+ if (!qmdAvailable)
551
+ return false;
552
+ return new Promise((resolve) => {
553
+ execFileFn("qmd", ["embed"], { timeout: 120_000 }, (err) => {
554
+ resolve(!err);
555
+ });
556
+ });
557
+ }
558
+ export async function ensureQmdAvailableForSync() {
559
+ if (qmdAvailable)
560
+ return true;
561
+ qmdAvailable = await detectQmd();
562
+ return qmdAvailable;
563
+ }
564
+ export async function runQmdSync() {
565
+ if (!qmdAvailable)
566
+ return { updateOk: false, embedOk: false };
567
+ const updateOk = await new Promise((resolve) => {
568
+ execFileFn("qmd", ["update"], { timeout: 30_000 }, (err) => {
569
+ resolve(!err);
570
+ });
571
+ });
572
+ const embedOk = await runQmdEmbedNow();
573
+ return { updateOk, embedOk };
574
+ }
575
+ export function installSkills() {
576
+ const homeDir = resolveHomeDir();
577
+ if (!homeDir) {
578
+ return {
579
+ ok: false,
580
+ detected: [],
581
+ installed: [],
582
+ skipped: [],
583
+ error: "Home directory not found. Set HOME (or USERPROFILE on Windows) and retry.",
584
+ };
585
+ }
586
+ const projectDir = findSkillsRoot();
587
+ if (!projectDir) {
588
+ return {
589
+ ok: false,
590
+ homeDir,
591
+ detected: [],
592
+ installed: [],
593
+ skipped: [],
594
+ error: "Could not locate the skills directory. Ensure the package includes skills/ (reinstall if needed).",
595
+ };
596
+ }
597
+ const skillsDir = path.join(projectDir, "skills");
598
+ if (!fs.existsSync(skillsDir)) {
599
+ return {
600
+ ok: false,
601
+ projectDir,
602
+ homeDir,
603
+ detected: [],
604
+ installed: [],
605
+ skipped: [],
606
+ error: `Skills directory not found: ${skillsDir}`,
607
+ };
608
+ }
609
+ const targets = [
610
+ {
611
+ label: "Claude Code skill",
612
+ srcDir: path.join(skillsDir, "claude-code"),
613
+ destDir: path.join(homeDir, ".claude", "skills", "agent-memory"),
614
+ homeMarker: path.join(homeDir, ".claude"),
615
+ },
616
+ {
617
+ label: "Codex skill",
618
+ srcDir: path.join(skillsDir, "codex"),
619
+ destDir: path.join(homeDir, ".codex", "skills", "agent-memory"),
620
+ homeMarker: path.join(homeDir, ".codex"),
621
+ },
622
+ {
623
+ label: "Cursor skill",
624
+ srcDir: path.join(skillsDir, "cursor"),
625
+ destDir: path.join(homeDir, ".cursor", "skills", "agent-memory"),
626
+ homeMarker: path.join(homeDir, ".cursor"),
627
+ },
628
+ {
629
+ label: "Agent CLI skill",
630
+ srcDir: path.join(skillsDir, "agent"),
631
+ destDir: path.join(homeDir, ".agents", "skills", "agent-memory"),
632
+ homeMarker: path.join(homeDir, ".agents"),
633
+ },
634
+ ];
635
+ const detected = [];
636
+ const installed = [];
637
+ const skipped = [];
638
+ for (const target of targets) {
639
+ if (!fs.existsSync(target.homeMarker)) {
640
+ skipped.push({ label: target.label, reason: `${target.homeMarker} not found` });
641
+ continue;
642
+ }
643
+ detected.push({ label: target.label, homeMarker: target.homeMarker });
644
+ const skillFile = path.join(target.srcDir, "SKILL.md");
645
+ if (!fs.existsSync(skillFile)) {
646
+ skipped.push({ label: target.label, reason: `${skillFile} not found` });
647
+ continue;
648
+ }
649
+ fs.mkdirSync(target.destDir, { recursive: true });
650
+ fs.copyFileSync(skillFile, path.join(target.destDir, "SKILL.md"));
651
+ installed.push({ label: target.label, path: path.join(target.destDir, "SKILL.md") });
652
+ }
653
+ return {
654
+ ok: true,
655
+ projectDir,
656
+ homeDir,
657
+ detected,
658
+ installed,
659
+ skipped,
660
+ };
661
+ }
662
+ export function parseQmdStatus(stdout, collectionName) {
663
+ const result = {
664
+ totalFiles: null,
665
+ vectorsEmbedded: null,
666
+ pendingEmbed: null,
667
+ lastUpdated: null,
668
+ collectionFiles: null,
669
+ collectionUpdated: null,
670
+ embedMode: getQmdEmbedMode(),
671
+ };
672
+ if (!stdout.trim())
673
+ return result;
674
+ // Total files across all collections
675
+ const totalFilesMatch = stdout.match(/(\d+)\s+(?:total\s+)?files?/i);
676
+ if (totalFilesMatch) {
677
+ result.totalFiles = Number.parseInt(totalFilesMatch[1], 10);
678
+ }
679
+ // Vectors / embeddings
680
+ const vectorsMatch = stdout.match(/(\d+)\s+(?:vectors?|embeddings?)/i);
681
+ if (vectorsMatch) {
682
+ result.vectorsEmbedded = Number.parseInt(vectorsMatch[1], 10);
683
+ }
684
+ // Pending embed
685
+ const pendingMatch = stdout.match(/(\d+)\s+pending/i);
686
+ if (pendingMatch) {
687
+ result.pendingEmbed = Number.parseInt(pendingMatch[1], 10);
688
+ }
689
+ // If we have totalFiles and vectorsEmbedded but no explicit pending, infer it
690
+ if (result.pendingEmbed === null && result.totalFiles !== null && result.vectorsEmbedded !== null) {
691
+ result.pendingEmbed = Math.max(0, result.totalFiles - result.vectorsEmbedded);
692
+ }
693
+ // Last updated timestamp
694
+ const updatedMatch = stdout.match(/(?:last\s+)?updated:?\s+(\d{4}-\d{2}-\d{2}[\sT]\d{2}:\d{2}[:\d]*)/i);
695
+ if (updatedMatch) {
696
+ result.lastUpdated = updatedMatch[1];
697
+ }
698
+ // Collection-specific info — look for collection name section
699
+ const collectionPattern = new RegExp(`${collectionName}[:\\s]*(?:.*?)(\\d+)\\s+files?`, "i");
700
+ const collMatch = stdout.match(collectionPattern);
701
+ if (collMatch) {
702
+ result.collectionFiles = Number.parseInt(collMatch[1], 10);
703
+ }
704
+ return result;
705
+ }
706
+ export async function getQmdHealth() {
707
+ if (!qmdAvailable)
708
+ return null;
709
+ return new Promise((resolve) => {
710
+ execFileFn("qmd", ["status"], { timeout: 10_000 }, (err, stdout) => {
711
+ if (err) {
712
+ resolve(null);
713
+ return;
714
+ }
715
+ resolve(parseQmdStatus(stdout ?? "", QMD_COLLECTION_NAME));
716
+ });
717
+ });
718
+ }
449
719
  /** Search for memories relevant to the user's prompt. Returns formatted markdown or empty string on error. */
450
720
  export async function searchRelevantMemories(prompt) {
451
721
  if (!qmdAvailable || !prompt.trim())
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myagentmemory",
3
- "version": "0.4.4",
3
+ "version": "0.4.6",
4
4
  "description": "Persistent memory for coding agents (Claude Code, Codex, Cursor, Agent) with qmd-powered semantic search across daily logs, long-term memory, and scratchpad",
5
5
  "main": "./dist/core.js",
6
6
  "types": "./dist/core.d.ts",
@@ -52,7 +52,7 @@
52
52
  "postinstall": "node scripts/postinstall.cjs",
53
53
  "build": "tsc -p tsconfig.json --noEmit",
54
54
  "build:lib": "tsc -p tsconfig.build.json",
55
- "build:cli": "bun build src/cli.ts --compile --outfile dist/agent-memory",
55
+ "build:cli": "bun build src/cli.ts --compile --outfile dist/agent-memory --define __VERSION__=\"'$(node -p \"require('./package.json').version\")'\"",
56
56
  "prepack": "npm run build:lib && bun run build:cli",
57
57
  "lint": "biome check .",
58
58
  "test": "bun test test/unit.test.ts",