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 +3 -4
- package/dist/agent-memory +0 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +120 -1
- package/dist/core.d.ts +54 -1
- package/dist/core.js +272 -2
- package/package.json +2 -2
- package/skills/agent/SKILL.md +102 -53
- package/skills/claude-code/SKILL.md +98 -51
- package/skills/codex/SKILL.md +100 -53
- package/skills/cursor/SKILL.md +102 -53
- package/src/cli.ts +136 -0
- package/src/core.ts +317 -2
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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.
|
|
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",
|