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.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env bash
2
- # Install (or uninstall) agent-memory skills for Claude Code, Codex, and Cursor.
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
@@ -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 result = spawnSync("git", ["rev-parse", "--is-inside-work-tree"], {
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
- configureGitHooks();
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
- const VERSION = typeof __VERSION__ !== "undefined" ? __VERSION__ : "dev";
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
- const searchResults = noSearch ? "" : await searchRelevantMemories("");
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
- ensureDirs();
173
- const ts = nowTimestamp();
174
- const sid = "cli";
175
-
176
- if (target === "daily") {
177
- const filePath = dailyPath(todayStr());
178
- const existing = readFileSafe(filePath) ?? "";
179
- const separator = existing.trim() ? "\n\n" : "";
180
- const stamped = `<!-- ${ts} [${sid}] -->\n${content}`;
181
- fs.writeFileSync(filePath, existing + separator + stamped, "utf-8");
182
- await ensureQmdAvailableForUpdate();
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
- items.push({ done: false, text: text!, meta: `<!-- ${ts} [cli] -->` });
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: - [ ] ${text}`, json);
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 & print context injection string
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 --no-search
799
+ agent-memory context --query "database choice"
814
800
  agent-memory sync
815
801
  agent-memory status --json`);
816
802
  }