myagentmemory 0.4.12 → 0.4.13
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 +83 -38
- package/dist/cli.js +60 -67
- package/dist/core.d.ts +21 -1
- package/dist/core.js +282 -50
- package/package.json +29 -11
- package/scripts/install-skills.sh +4 -1
- package/scripts/postinstall.cjs +23 -4
- package/src/cli.ts +64 -78
- package/src/core.ts +297 -50
- package/dist/agent-memory +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# Install (or uninstall) agent-memory skills for Claude Code, Codex, and
|
|
2
|
+
# Install (or uninstall) agent-memory skills for Claude Code, Codex, Cursor, and Agent CLI.
|
|
3
3
|
# Usage: bash scripts/install-skills.sh [--uninstall]
|
|
4
4
|
|
|
5
5
|
set -euo pipefail
|
|
@@ -62,11 +62,13 @@ SKILL_DIRS=(
|
|
|
62
62
|
"$HOME/.claude/skills/agent-memory"
|
|
63
63
|
"$HOME/.codex/skills/agent-memory"
|
|
64
64
|
"$HOME/.cursor/skills/agent-memory"
|
|
65
|
+
"$HOME/.agents/skills/agent-memory"
|
|
65
66
|
)
|
|
66
67
|
SKILL_LABELS=(
|
|
67
68
|
"Claude Code skill"
|
|
68
69
|
"Codex skill"
|
|
69
70
|
"Cursor skill"
|
|
71
|
+
"Agent CLI skill"
|
|
70
72
|
)
|
|
71
73
|
|
|
72
74
|
if $UNINSTALL; then
|
|
@@ -81,6 +83,7 @@ else
|
|
|
81
83
|
install_skill "Claude Code skill" "$PROJECT_DIR/skills/claude-code" "$HOME/.claude/skills/agent-memory" "$HOME/.claude" '[ -f "$HOME/.claude/settings.json" ] || [ -f "$HOME/.claude/settings.local.json" ] || command_exists claude'
|
|
82
84
|
install_skill "Codex skill" "$PROJECT_DIR/skills/codex" "$HOME/.codex/skills/agent-memory" "$HOME/.codex" '[ -f "$HOME/.codex/config.toml" ] || command_exists codex'
|
|
83
85
|
install_skill "Cursor skill" "$PROJECT_DIR/skills/cursor" "$HOME/.cursor/skills/agent-memory" "$HOME/.cursor"
|
|
86
|
+
install_skill "Agent CLI skill" "$PROJECT_DIR/skills/agent" "$HOME/.agents/skills/agent-memory" "$HOME/.agents"
|
|
84
87
|
echo ""
|
|
85
88
|
echo "Done."
|
|
86
89
|
fi
|
package/scripts/postinstall.cjs
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
const { spawnSync } = require("node:child_process");
|
|
2
|
+
const fs = require("node:fs");
|
|
2
3
|
const path = require("node:path");
|
|
3
4
|
|
|
5
|
+
const packageRoot = path.resolve(__dirname, "..");
|
|
6
|
+
|
|
4
7
|
function hasQmd() {
|
|
5
8
|
const result = spawnSync("qmd", ["status"], {
|
|
6
9
|
stdio: "ignore",
|
|
@@ -14,23 +17,39 @@ function memoryDir() {
|
|
|
14
17
|
return path.join(home, ".agent-memory");
|
|
15
18
|
}
|
|
16
19
|
|
|
20
|
+
// Distinguish a development checkout of agentmemory from an end-user install.
|
|
21
|
+
// When agentmemory is installed as a dependency it lives under node_modules and
|
|
22
|
+
// ships without the `.githooks` directory (it's absent from the package.json
|
|
23
|
+
// "files" allowlist). We must never touch a consumer's repo or VCS config, so
|
|
24
|
+
// the dev-only hook setup below is gated on "are we actually in the source repo?".
|
|
25
|
+
function isDevCheckout() {
|
|
26
|
+
if (packageRoot.split(path.sep).includes("node_modules")) return false;
|
|
27
|
+
return fs.existsSync(path.join(packageRoot, ".githooks"));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Point git at the repo's tracked hooks (lint + tests on commit). Scoped to the
|
|
31
|
+
// agentmemory working tree only — `cwd` + the dev-checkout gate ensure we only
|
|
32
|
+
// ever write to this repo's local git config, never a consumer's.
|
|
17
33
|
function configureGitHooks() {
|
|
18
|
-
const
|
|
34
|
+
const insideRepo = spawnSync("git", ["rev-parse", "--is-inside-work-tree"], {
|
|
35
|
+
cwd: packageRoot,
|
|
19
36
|
stdio: "ignore",
|
|
20
37
|
shell: process.platform === "win32",
|
|
21
38
|
});
|
|
22
|
-
|
|
23
|
-
if (result.status !== 0) {
|
|
39
|
+
if (insideRepo.status !== 0) {
|
|
24
40
|
return;
|
|
25
41
|
}
|
|
26
42
|
|
|
27
43
|
spawnSync("git", ["config", "core.hooksPath", ".githooks"], {
|
|
44
|
+
cwd: packageRoot,
|
|
28
45
|
stdio: "ignore",
|
|
29
46
|
shell: process.platform === "win32",
|
|
30
47
|
});
|
|
31
48
|
}
|
|
32
49
|
|
|
33
|
-
|
|
50
|
+
if (isDevCheckout()) {
|
|
51
|
+
configureGitHooks();
|
|
52
|
+
}
|
|
34
53
|
|
|
35
54
|
if (!hasQmd()) {
|
|
36
55
|
const dir = memoryDir();
|
package/src/cli.ts
CHANGED
|
@@ -41,9 +41,12 @@ import {
|
|
|
41
41
|
getScratchpadFile,
|
|
42
42
|
getTopicsDir,
|
|
43
43
|
installSkills,
|
|
44
|
+
memoryWrite,
|
|
44
45
|
nowTimestamp,
|
|
45
46
|
parseScratchpad,
|
|
47
|
+
probeEmbeddings,
|
|
46
48
|
readFileSafe,
|
|
49
|
+
redactSecrets,
|
|
47
50
|
runQmdEmbedDetached,
|
|
48
51
|
runQmdSearch,
|
|
49
52
|
runQmdSync,
|
|
@@ -59,7 +62,19 @@ import {
|
|
|
59
62
|
} from "./core.js";
|
|
60
63
|
|
|
61
64
|
declare const __VERSION__: string;
|
|
62
|
-
|
|
65
|
+
|
|
66
|
+
function readPackageVersion(): string {
|
|
67
|
+
try {
|
|
68
|
+
const packageJson = JSON.parse(fs.readFileSync(new URL("../package.json", import.meta.url), "utf-8")) as {
|
|
69
|
+
version?: unknown;
|
|
70
|
+
};
|
|
71
|
+
return typeof packageJson.version === "string" ? packageJson.version : "dev";
|
|
72
|
+
} catch {
|
|
73
|
+
return "dev";
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const VERSION = typeof __VERSION__ !== "undefined" ? __VERSION__ : readPackageVersion();
|
|
63
78
|
|
|
64
79
|
// ---------------------------------------------------------------------------
|
|
65
80
|
// Arg parsing (no external deps)
|
|
@@ -140,9 +155,11 @@ function exitError(message: string, json: boolean): never {
|
|
|
140
155
|
async function cmdContext(flags: Record<string, string | boolean>) {
|
|
141
156
|
const json = hasFlag(flags, "json");
|
|
142
157
|
const noSearch = hasFlag(flags, "no-search");
|
|
158
|
+
const query = getFlag(flags, "query") ?? "";
|
|
143
159
|
|
|
144
160
|
ensureDirs();
|
|
145
|
-
|
|
161
|
+
if (!noSearch && query) await ensureQmdAvailableForSync();
|
|
162
|
+
const searchResults = noSearch ? "" : await searchRelevantMemories(query);
|
|
146
163
|
const context = buildMemoryContext(searchResults);
|
|
147
164
|
|
|
148
165
|
if (json) {
|
|
@@ -161,82 +178,29 @@ async function cmdWrite(flags: Record<string, string | boolean>) {
|
|
|
161
178
|
const mode = getFlag(flags, "mode") ?? "append";
|
|
162
179
|
const topic = getFlag(flags, "topic");
|
|
163
180
|
const date = getFlag(flags, "date");
|
|
181
|
+
const sourceUri = getFlag(flags, "source-uri");
|
|
164
182
|
|
|
165
183
|
if (!["long_term", "daily", "topic"].includes(target)) {
|
|
166
184
|
exitError("--target must be 'long_term', 'daily', or 'topic' (default: daily)", json);
|
|
167
185
|
}
|
|
186
|
+
if (!["append", "overwrite"].includes(mode)) {
|
|
187
|
+
exitError("--mode must be 'append' or 'overwrite'", json);
|
|
188
|
+
}
|
|
168
189
|
if (!content) {
|
|
169
190
|
exitError("--content is required", json);
|
|
170
191
|
}
|
|
171
192
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
scheduleQmdUpdate();
|
|
184
|
-
output(
|
|
185
|
-
json
|
|
186
|
-
? { ok: true, path: filePath, target, mode: "append", timestamp: ts }
|
|
187
|
-
: `Appended to daily log: ${filePath}`,
|
|
188
|
-
json,
|
|
189
|
-
);
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
if (target === "topic") {
|
|
194
|
-
if (!topic) {
|
|
195
|
-
exitError("--topic is required when --target is 'topic'", json);
|
|
196
|
-
}
|
|
197
|
-
const slug = slugifyTopic(topic);
|
|
198
|
-
if (!slug) {
|
|
199
|
-
exitError("--topic must include at least one letter or number", json);
|
|
200
|
-
}
|
|
201
|
-
const filePath = topicPath(slug);
|
|
202
|
-
const existing = readFileSafe(filePath) ?? "";
|
|
203
|
-
const linkDate = date?.trim() || todayStr();
|
|
204
|
-
const header = `# Topic: ${topic}\n\n<!-- created: ${ts} [${sid}] -->\n`;
|
|
205
|
-
const separator = existing.trim() ? "\n\n" : "";
|
|
206
|
-
const base = existing.trim() ? existing : header.trimEnd();
|
|
207
|
-
const stamped = `<!-- ${ts} [${sid}] -->\n${content.trim()}\nDaily: [[${linkDate}]]`;
|
|
208
|
-
fs.writeFileSync(filePath, `${base}${separator}${stamped}`, "utf-8");
|
|
209
|
-
await ensureQmdAvailableForUpdate();
|
|
210
|
-
scheduleQmdUpdate();
|
|
211
|
-
output(
|
|
212
|
-
json
|
|
213
|
-
? { ok: true, path: filePath, target, mode: "append", timestamp: ts, topic, slug, date: linkDate }
|
|
214
|
-
: `Appended to topic: ${filePath}`,
|
|
215
|
-
json,
|
|
216
|
-
);
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// long_term
|
|
221
|
-
const memFile = getMemoryFile();
|
|
222
|
-
const existing = readFileSafe(memFile) ?? "";
|
|
223
|
-
|
|
224
|
-
if (mode === "overwrite") {
|
|
225
|
-
const stamped = `<!-- last updated: ${ts} [${sid}] -->\n${content}`;
|
|
226
|
-
fs.writeFileSync(memFile, stamped, "utf-8");
|
|
227
|
-
} else {
|
|
228
|
-
const separator = existing.trim() ? "\n\n" : "";
|
|
229
|
-
const stamped = `<!-- ${ts} [${sid}] -->\n${content}`;
|
|
230
|
-
fs.writeFileSync(memFile, existing + separator + stamped, "utf-8");
|
|
231
|
-
}
|
|
232
|
-
await ensureQmdAvailableForUpdate();
|
|
233
|
-
scheduleQmdUpdate();
|
|
234
|
-
output(
|
|
235
|
-
json
|
|
236
|
-
? { ok: true, path: memFile, target, mode, timestamp: ts }
|
|
237
|
-
: `${mode === "overwrite" ? "Overwrote" : "Appended to"} MEMORY.md`,
|
|
238
|
-
json,
|
|
239
|
-
);
|
|
193
|
+
const result = await memoryWrite({
|
|
194
|
+
target: target as "long_term" | "daily" | "topic",
|
|
195
|
+
content,
|
|
196
|
+
mode: mode as "append" | "overwrite",
|
|
197
|
+
sessionId: "cli",
|
|
198
|
+
topic,
|
|
199
|
+
date,
|
|
200
|
+
sourceUri,
|
|
201
|
+
});
|
|
202
|
+
if (result.isError) exitError(result.text.replace(/^Error:\s*/, ""), json);
|
|
203
|
+
output(json ? { ok: true, ...result.details } : result.text.split("\n\n", 1)[0], json);
|
|
240
204
|
}
|
|
241
205
|
|
|
242
206
|
async function cmdRead(flags: Record<string, string | boolean>) {
|
|
@@ -349,7 +313,11 @@ async function cmdScratchpad(flags: Record<string, string | boolean>, positional
|
|
|
349
313
|
ensureDirs();
|
|
350
314
|
const spFile = getScratchpadFile();
|
|
351
315
|
const existing = readFileSafe(spFile) ?? "";
|
|
352
|
-
let items = parseScratchpad(existing)
|
|
316
|
+
let items = parseScratchpad(existing).map((item) => ({
|
|
317
|
+
...item,
|
|
318
|
+
text: redactSecrets(item.text).content,
|
|
319
|
+
meta: redactSecrets(item.meta).content,
|
|
320
|
+
}));
|
|
353
321
|
|
|
354
322
|
if (action === "list") {
|
|
355
323
|
if (items.length === 0) {
|
|
@@ -374,11 +342,12 @@ async function cmdScratchpad(flags: Record<string, string | boolean>, positional
|
|
|
374
342
|
if (action === "add") {
|
|
375
343
|
if (!text) exitError("--text is required for add", json);
|
|
376
344
|
const ts = nowTimestamp();
|
|
377
|
-
|
|
345
|
+
const safeText = redactSecrets(text!).content;
|
|
346
|
+
items.push({ done: false, text: safeText, meta: `<!-- ${ts} [cli] -->` });
|
|
378
347
|
fs.writeFileSync(spFile, serializeScratchpad(items), "utf-8");
|
|
379
348
|
await ensureQmdAvailableForUpdate();
|
|
380
349
|
scheduleQmdUpdate();
|
|
381
|
-
output(json ? { ok: true, action, text } : `Added: - [ ] ${
|
|
350
|
+
output(json ? { ok: true, action, text: safeText } : `Added: - [ ] ${safeText}`, json);
|
|
382
351
|
return;
|
|
383
352
|
}
|
|
384
353
|
|
|
@@ -665,11 +634,18 @@ async function cmdStatus(flags: Record<string, string | boolean>) {
|
|
|
665
634
|
const qmdFound = await detectQmd();
|
|
666
635
|
let hasCollection = false;
|
|
667
636
|
let health = null;
|
|
637
|
+
let embeddings: "ready" | "missing" | "unknown" | "n/a" = "n/a";
|
|
668
638
|
if (qmdFound) {
|
|
669
639
|
hasCollection = await checkCollection();
|
|
670
640
|
if (hasCollection) {
|
|
671
641
|
await ensureQmdAvailableForSync();
|
|
672
642
|
health = await getQmdHealth();
|
|
643
|
+
// A live semantic probe confirms embeddings are actually usable, but
|
|
644
|
+
// it costs a real qmd query (and a possible model load), so it's
|
|
645
|
+
// opt-in — the cheap pending-embed count below covers the common case.
|
|
646
|
+
if (hasFlag(flags, "probe")) {
|
|
647
|
+
embeddings = await probeEmbeddings();
|
|
648
|
+
}
|
|
673
649
|
}
|
|
674
650
|
}
|
|
675
651
|
|
|
@@ -695,6 +671,7 @@ async function cmdStatus(flags: Record<string, string | boolean>) {
|
|
|
695
671
|
available: qmdFound,
|
|
696
672
|
collection: hasCollection ? getCollectionName() : null,
|
|
697
673
|
health,
|
|
674
|
+
embeddings,
|
|
698
675
|
},
|
|
699
676
|
embedMode,
|
|
700
677
|
},
|
|
@@ -725,6 +702,15 @@ async function cmdStatus(flags: Record<string, string | boolean>) {
|
|
|
725
702
|
`Collection '${getCollectionName()}': ${hasCollection ? "configured" : "not configured — run: agent-memory init"}`,
|
|
726
703
|
);
|
|
727
704
|
console.log(`Embed mode: ${embedMode}`);
|
|
705
|
+
if (hasCollection && embeddings !== "n/a") {
|
|
706
|
+
const embLabel =
|
|
707
|
+
embeddings === "ready"
|
|
708
|
+
? "ready"
|
|
709
|
+
: embeddings === "missing"
|
|
710
|
+
? "missing — run: agent-memory sync"
|
|
711
|
+
: "unknown (could not verify within probe timeout)";
|
|
712
|
+
console.log(`Embeddings (semantic/deep search): ${embLabel}`);
|
|
713
|
+
}
|
|
728
714
|
if (health) {
|
|
729
715
|
if (health.totalFiles !== null) console.log(`Files indexed: ${health.totalFiles}`);
|
|
730
716
|
if (health.vectorsEmbedded !== null) console.log(`Vectors embedded: ${health.vectorsEmbedded}`);
|
|
@@ -781,15 +767,15 @@ Commands:
|
|
|
781
767
|
version Show binary version
|
|
782
768
|
install-skills Install (or --uninstall) bundled skills
|
|
783
769
|
uninstall-skills Uninstall bundled skills
|
|
784
|
-
context Build
|
|
785
|
-
write Write to memory files (default: daily)
|
|
770
|
+
context Build context; optionally retrieve memories with --query
|
|
771
|
+
write Write to memory files (default: daily; optional --source-uri)
|
|
786
772
|
read Read memory files
|
|
787
773
|
scratchpad Manage checklist items
|
|
788
774
|
search Search across memory files (requires qmd)
|
|
789
775
|
distil Generate compact MEMORY.md index from daily logs + topics
|
|
790
776
|
sync Re-index and embed all files (requires qmd)
|
|
791
777
|
init Initialize memory directory and qmd collection
|
|
792
|
-
status Show configuration and status
|
|
778
|
+
status Show configuration and status (--probe for a live embeddings check)
|
|
793
779
|
|
|
794
780
|
Global flags:
|
|
795
781
|
--dir <path> Override memory directory
|
|
@@ -798,7 +784,7 @@ Global flags:
|
|
|
798
784
|
Examples:
|
|
799
785
|
agent-memory init
|
|
800
786
|
agent-memory write --content "Fixed auth bug in login flow"
|
|
801
|
-
agent-memory write --target long_term --content "User prefers dark mode"
|
|
787
|
+
agent-memory write --target long_term --content "User prefers dark mode" --source-uri "session://agent/turn/12"
|
|
802
788
|
agent-memory write --target topic --topic "auth" --content "Rolled JWT refresh to edge"
|
|
803
789
|
agent-memory read --target long_term
|
|
804
790
|
agent-memory read --target daily --date 2026-02-15
|
|
@@ -810,7 +796,7 @@ Examples:
|
|
|
810
796
|
agent-memory scratchpad done --text "PR #42"
|
|
811
797
|
agent-memory search --query "database choice" --mode keyword
|
|
812
798
|
agent-memory distil --dry-run
|
|
813
|
-
agent-memory context --
|
|
799
|
+
agent-memory context --query "database choice"
|
|
814
800
|
agent-memory sync
|
|
815
801
|
agent-memory status --json`);
|
|
816
802
|
}
|