myagentmemory 0.4.8 → 0.4.11

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
@@ -4,11 +4,15 @@ Persistent memory for coding agents — [Claude Code](https://claude.ai/code), [
4
4
 
5
5
  Thanks to https://github.com/skyfallsin/pi-mem for inspiration.
6
6
 
7
- Long-term facts, daily logs, and a scratchpad checklist stored as plain markdown files. Optional qmd integration adds keyword, semantic, and hybrid search across all memory files, plus automatic selective injection of relevant past memories into every turn.
7
+ Long-term facts, daily logs, topic/event notes, and a scratchpad checklist stored as plain markdown files. Optional qmd integration adds keyword, semantic, and hybrid search across all memory files, plus automatic selective injection of relevant past memories into every turn.
8
8
 
9
9
  ## Installation
10
10
 
11
11
  ```bash
12
+ # Homebrew (macOS)
13
+ brew tap jayzeng/agentmemory https://github.com/jayzeng/agentmemory
14
+ brew install jayzeng/agentmemory/agent-memory
15
+
12
16
  # Install the CLI globally
13
17
  npm install -g myagentmemory
14
18
 
@@ -24,8 +28,15 @@ agent-memory init
24
28
 
25
29
  # Install skill files for Claude Code, Codex, Cursor, and Agent
26
30
  agent-memory install-skills
31
+
32
+ # Uninstall skill files
33
+ agent-memory uninstall-skills
27
34
  ```
28
35
 
36
+ ### Pi users
37
+
38
+ If you're on Pi and prefer a native extension, use `pi-memory` (https://github.com/jayzeng/pi-memory) instead of installing this skill. The CLI + skill workflow here is the cross-platform alternative, and works fine on Pi without any extension.
39
+
29
40
  This installs:
30
41
  - `~/.claude/skills/agent-memory/SKILL.md` — Claude Code skill
31
42
  - `~/.codex/skills/agent-memory/SKILL.md` — Codex skill
@@ -79,11 +90,12 @@ The memory directory defaults to `~/.agent-memory/`. Override with `AGENT_MEMORY
79
90
  | Command | Purpose |
80
91
  |---------|---------|
81
92
  | `agent-memory context [--no-search]` | Build & print context injection string to stdout |
82
- | `agent-memory write --target <long_term\|daily> --content <text> [--mode append\|overwrite]` | Write to memory files |
83
- | `agent-memory read --target <long_term\|scratchpad\|daily\|list> [--date YYYY-MM-DD]` | Read memory files |
93
+ | `agent-memory write --target <long_term\|daily\|topic> --content <text> [--mode append\|overwrite] [--topic <name>] [--date YYYY-MM-DD]` | Write to memory files |
94
+ | `agent-memory read --target <long_term\|scratchpad\|daily\|list\|topic\|topics> [--date YYYY-MM-DD] [--topic <name>]` | Read memory files |
84
95
  | `agent-memory scratchpad <add\|done\|undo\|clear_done\|list> [--text <text>]` | Manage checklist |
85
96
  | `agent-memory search --query <text> [--mode keyword\|semantic\|deep] [--limit N]` | Search via qmd |
86
97
  | `agent-memory install-skills` | Install bundled SKILL.md files into local agent directories |
98
+ | `agent-memory uninstall-skills` | Uninstall bundled SKILL.md files from local agent directories |
87
99
  | `agent-memory init` | Create dirs, detect qmd, setup collection |
88
100
  | `agent-memory status` | Show config, qmd status, file counts |
89
101
 
@@ -109,6 +121,18 @@ If the first search doesn't find what you need, try rephrasing or switching mode
109
121
  2026-02-15.md # Daily append-only log
110
122
  2026-02-14.md
111
123
  ...
124
+ topics/
125
+ auth.md # Topic/event log linked back to daily entries
126
+ ```
127
+
128
+ ## Topic notes
129
+
130
+ Topic files are for event- or theme-based tracking across days. Each topic entry includes a `Daily: [[YYYY-MM-DD]]` backlink so you can jump from the topic to the full daily log.
131
+
132
+ ```bash
133
+ agent-memory write --target topic --topic "auth" --content "JWT refresh rolled out to edge #auth"
134
+ agent-memory read --target topic --topic "auth"
135
+ agent-memory read --target topics
112
136
  ```
113
137
 
114
138
  ## How it works
@@ -118,10 +142,11 @@ If the first search doesn't find what you need, try rephrasing or switching mode
118
142
  Before every agent turn, the following are injected into the system prompt (in priority order):
119
143
 
120
144
  1. **Open scratchpad items** (up to 2K chars)
121
- 2. **Today's daily log** (up to 3K chars, tail)
122
- 3. **Relevant memories via qmd search** (up to 2.5K chars) — searches using the user's current prompt to surface related past context
123
- 4. **MEMORY.md** (up to 4K chars, middle-truncated)
124
- 5. **Yesterday's daily log** (up to 3K chars, tail — lowest priority, trimmed first)
145
+ 2. **Recent topic entries** (up to 2K chars) — most recent topic notes with backlinks
146
+ 3. **Today's daily log** (up to 3K chars, tail)
147
+ 4. **Relevant memories via qmd search** (up to 2.5K chars) — searches using the user's current prompt to surface related past context
148
+ 5. **MEMORY.md** (up to 4K chars, middle-truncated)
149
+ 6. **Yesterday's daily log** (up to 3K chars, tail — lowest priority, trimmed first)
125
150
 
126
151
  Total injection is capped at 16K chars. When qmd is unavailable, step 3 is skipped and the rest works as before.
127
152
 
package/dist/agent-memory CHANGED
Binary file
package/dist/cli.d.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * Subcommands:
6
6
  * version — Print binary version
7
- * install-skills — Install SKILL.md files into local agent directories
7
+ * install-skills — Install (or --uninstall) SKILL.md files into local agent directories
8
8
  * context — Build & print context injection string to stdout
9
9
  * write — Write to memory files
10
10
  * read — Read memory files
package/dist/cli.js CHANGED
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * Subcommands:
6
6
  * version — Print binary version
7
- * install-skills — Install SKILL.md files into local agent directories
7
+ * install-skills — Install (or --uninstall) SKILL.md files into local agent directories
8
8
  * context — Build & print context injection string to stdout
9
9
  * write — Write to memory files
10
10
  * read — Read memory files
@@ -18,8 +18,8 @@
18
18
  * --json Machine-readable JSON output
19
19
  */
20
20
  import * as fs from "node:fs";
21
+ import { _setBaseDir, buildMemoryContext, checkCollection, dailyPath, detectQmd, distilMemories, ensureDirs, ensureQmdAvailableForSync, ensureQmdAvailableForUpdate, getCollectionName, getDailyDir, getMemoryDir, getMemoryFile, getQmdEmbedMode, getQmdHealth, getQmdResultPath, getQmdResultText, getScratchpadFile, getTopicsDir, installSkills, nowTimestamp, parseScratchpad, readFileSafe, runQmdEmbedDetached, runQmdSearch, runQmdSync, runQmdUpdateNow, scheduleQmdUpdate, searchRelevantMemories, serializeScratchpad, setupQmdCollection, slugifyTopic, todayStr, topicPath, uninstallSkills, } from "./core.js";
21
22
  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";
23
23
  function parseArgs(argv) {
24
24
  const flags = {};
25
25
  const positional = [];
@@ -97,11 +97,13 @@ async function cmdContext(flags) {
97
97
  }
98
98
  async function cmdWrite(flags) {
99
99
  const json = hasFlag(flags, "json");
100
- const target = getFlag(flags, "target");
100
+ const target = getFlag(flags, "target") ?? "daily";
101
101
  const content = getFlag(flags, "content");
102
102
  const mode = getFlag(flags, "mode") ?? "append";
103
- if (!target || !["long_term", "daily"].includes(target)) {
104
- exitError("--target must be 'long_term' or 'daily'", json);
103
+ const topic = getFlag(flags, "topic");
104
+ const date = getFlag(flags, "date");
105
+ if (!["long_term", "daily", "topic"].includes(target)) {
106
+ exitError("--target must be 'long_term', 'daily', or 'topic' (default: daily)", json);
105
107
  }
106
108
  if (!content) {
107
109
  exitError("--content is required", json);
@@ -122,6 +124,29 @@ async function cmdWrite(flags) {
122
124
  : `Appended to daily log: ${filePath}`, json);
123
125
  return;
124
126
  }
127
+ if (target === "topic") {
128
+ if (!topic) {
129
+ exitError("--topic is required when --target is 'topic'", json);
130
+ }
131
+ const slug = slugifyTopic(topic);
132
+ if (!slug) {
133
+ exitError("--topic must include at least one letter or number", json);
134
+ }
135
+ const filePath = topicPath(slug);
136
+ const existing = readFileSafe(filePath) ?? "";
137
+ const linkDate = date?.trim() || todayStr();
138
+ const header = `# Topic: ${topic}\n\n<!-- created: ${ts} [${sid}] -->\n`;
139
+ const separator = existing.trim() ? "\n\n" : "";
140
+ const base = existing.trim() ? existing : header.trimEnd();
141
+ const stamped = `<!-- ${ts} [${sid}] -->\n${content.trim()}\nDaily: [[${linkDate}]]`;
142
+ fs.writeFileSync(filePath, `${base}${separator}${stamped}`, "utf-8");
143
+ await ensureQmdAvailableForUpdate();
144
+ scheduleQmdUpdate();
145
+ output(json
146
+ ? { ok: true, path: filePath, target, mode: "append", timestamp: ts, topic, slug, date: linkDate }
147
+ : `Appended to topic: ${filePath}`, json);
148
+ return;
149
+ }
125
150
  // long_term
126
151
  const memFile = getMemoryFile();
127
152
  const existing = readFileSafe(memFile) ?? "";
@@ -144,8 +169,9 @@ async function cmdRead(flags) {
144
169
  const json = hasFlag(flags, "json");
145
170
  const target = getFlag(flags, "target");
146
171
  const date = getFlag(flags, "date");
147
- if (!target || !["long_term", "scratchpad", "daily", "list"].includes(target)) {
148
- exitError("--target must be 'long_term', 'scratchpad', 'daily', or 'list'", json);
172
+ const topic = getFlag(flags, "topic");
173
+ if (!target || !["long_term", "scratchpad", "daily", "list", "topic", "topics"].includes(target)) {
174
+ exitError("--target must be 'long_term', 'scratchpad', 'daily', 'list', 'topic', or 'topics'", json);
149
175
  }
150
176
  ensureDirs();
151
177
  if (target === "list") {
@@ -181,6 +207,42 @@ async function cmdRead(flags) {
181
207
  output(json ? { content, date: d, path: filePath } : content, json);
182
208
  return;
183
209
  }
210
+ if (target === "topics") {
211
+ try {
212
+ const files = fs
213
+ .readdirSync(getTopicsDir())
214
+ .filter((f) => f.endsWith(".md"))
215
+ .sort()
216
+ .reverse();
217
+ if (json) {
218
+ output({ files }, true);
219
+ }
220
+ else if (files.length === 0) {
221
+ console.log("No topics found.");
222
+ }
223
+ else {
224
+ console.log(`Topics:\n${files.map((f) => `- ${f}`).join("\n")}`);
225
+ }
226
+ }
227
+ catch {
228
+ output(json ? { files: [] } : "No topics directory.", json);
229
+ }
230
+ return;
231
+ }
232
+ if (target === "topic") {
233
+ if (!topic) {
234
+ exitError("--topic is required when --target is 'topic'", json);
235
+ }
236
+ const slug = slugifyTopic(topic);
237
+ const filePath = topicPath(slug);
238
+ const content = readFileSafe(filePath);
239
+ if (!content) {
240
+ output(json ? { content: null, topic } : `No topic file found for ${topic}.`, json);
241
+ return;
242
+ }
243
+ output(json ? { content, topic, slug, path: filePath } : content, json);
244
+ return;
245
+ }
184
246
  if (target === "scratchpad") {
185
247
  const content = readFileSafe(getScratchpadFile());
186
248
  if (!content?.trim()) {
@@ -324,6 +386,27 @@ async function cmdSearch(flags) {
324
386
  }
325
387
  function cmdInstallSkills(flags) {
326
388
  const json = hasFlag(flags, "json");
389
+ const uninstall = hasFlag(flags, "uninstall");
390
+ if (uninstall) {
391
+ const report = uninstallSkills();
392
+ if (!report.ok) {
393
+ exitError(report.error ?? "Failed to uninstall skills.", json);
394
+ }
395
+ if (json) {
396
+ output(report, true);
397
+ return;
398
+ }
399
+ for (const item of report.removed) {
400
+ console.log(`Uninstalled ${item.label}: ${item.path}`);
401
+ }
402
+ for (const item of report.skipped) {
403
+ console.log(`Skipping ${item.label} (${item.reason})`);
404
+ }
405
+ if (report.removed.length === 0) {
406
+ console.log("No skills were installed.");
407
+ }
408
+ return;
409
+ }
327
410
  const report = installSkills();
328
411
  if (!report.ok) {
329
412
  exitError(report.error ?? "Failed to install skills.", json);
@@ -430,7 +513,7 @@ async function cmdInit(flags) {
430
513
  }
431
514
  else {
432
515
  console.log(`Memory directory: ${dir}`);
433
- console.log(` MEMORY.md, SCRATCHPAD.md, daily/ created.`);
516
+ console.log(` MEMORY.md, SCRATCHPAD.md, daily/, topics/ created.`);
434
517
  if (qmdFound) {
435
518
  if (collectionCreated) {
436
519
  console.log(` qmd collection '${getCollectionName()}' created.`);
@@ -458,6 +541,7 @@ async function cmdStatus(flags) {
458
541
  const memFile = getMemoryFile();
459
542
  const spFile = getScratchpadFile();
460
543
  const dailyDir = getDailyDir();
544
+ const topicsDir = getTopicsDir();
461
545
  const memContent = readFileSafe(memFile);
462
546
  const spContent = readFileSafe(spFile);
463
547
  let dailyCount = 0;
@@ -467,6 +551,13 @@ async function cmdStatus(flags) {
467
551
  catch {
468
552
  // directory may not exist
469
553
  }
554
+ let topicCount = 0;
555
+ try {
556
+ topicCount = fs.readdirSync(topicsDir).filter((f) => f.endsWith(".md")).length;
557
+ }
558
+ catch {
559
+ // directory may not exist
560
+ }
470
561
  const qmdFound = await detectQmd();
471
562
  let hasCollection = false;
472
563
  let health = null;
@@ -492,6 +583,7 @@ async function cmdStatus(flags) {
492
583
  openItems: spContent ? parseScratchpad(spContent).filter((i) => !i.done).length : 0,
493
584
  },
494
585
  dailyLogs: dailyCount,
586
+ topics: topicCount,
495
587
  qmd: {
496
588
  available: qmdFound,
497
589
  collection: hasCollection ? getCollectionName() : null,
@@ -519,6 +611,7 @@ async function cmdStatus(flags) {
519
611
  console.log("SCRATCHPAD.md: not created yet");
520
612
  }
521
613
  console.log(`Daily logs: ${dailyCount} file(s)`);
614
+ console.log(`Topics: ${topicCount} file(s)`);
522
615
  console.log("");
523
616
  if (qmdFound) {
524
617
  console.log(`qmd: available`);
@@ -542,6 +635,29 @@ async function cmdStatus(flags) {
542
635
  }
543
636
  }
544
637
  }
638
+ async function cmdDistil(flags) {
639
+ const json = hasFlag(flags, "json");
640
+ const dryRun = hasFlag(flags, "dry-run");
641
+ const result = await distilMemories({ dryRun });
642
+ if (json) {
643
+ output(result, true);
644
+ }
645
+ else {
646
+ if (result.totalEntries === 0) {
647
+ console.log(result.output.trim());
648
+ return;
649
+ }
650
+ if (dryRun) {
651
+ console.log("--- Dry run (MEMORY.md not modified) ---\n");
652
+ }
653
+ console.log(result.output.trim());
654
+ console.log("");
655
+ console.log(`Distilled ${result.totalEntries} entries from ${result.totalDailyFiles} daily file(s) and ${result.totalTopicFiles} topic file(s), ${result.totalTags} tag(s).`);
656
+ if (!dryRun) {
657
+ console.log("MEMORY.md updated.");
658
+ }
659
+ }
660
+ }
545
661
  // ---------------------------------------------------------------------------
546
662
  // Usage
547
663
  // ---------------------------------------------------------------------------
@@ -553,12 +669,14 @@ Usage:
553
669
 
554
670
  Commands:
555
671
  version Show binary version
556
- install-skills Install bundled skills into local agent directories
672
+ install-skills Install (or --uninstall) bundled skills
673
+ uninstall-skills Uninstall bundled skills
557
674
  context Build & print context injection string
558
- write Write to memory files
675
+ write Write to memory files (default: daily)
559
676
  read Read memory files
560
677
  scratchpad Manage checklist items
561
678
  search Search across memory files (requires qmd)
679
+ distil Generate compact MEMORY.md index from daily logs + topics
562
680
  sync Re-index and embed all files (requires qmd)
563
681
  init Initialize memory directory and qmd collection
564
682
  status Show configuration and status
@@ -569,15 +687,19 @@ Global flags:
569
687
 
570
688
  Examples:
571
689
  agent-memory init
690
+ agent-memory write --content "Fixed auth bug in login flow"
572
691
  agent-memory write --target long_term --content "User prefers dark mode"
573
- agent-memory write --target daily --content "Fixed auth bug in login flow"
692
+ agent-memory write --target topic --topic "auth" --content "Rolled JWT refresh to edge"
574
693
  agent-memory read --target long_term
575
694
  agent-memory read --target daily --date 2026-02-15
576
695
  agent-memory read --target list
696
+ agent-memory read --target topic --topic "auth"
697
+ agent-memory read --target topics
577
698
  agent-memory scratchpad add --text "Review PR #42"
578
699
  agent-memory scratchpad list
579
700
  agent-memory scratchpad done --text "PR #42"
580
701
  agent-memory search --query "database choice" --mode keyword
702
+ agent-memory distil --dry-run
581
703
  agent-memory context --no-search
582
704
  agent-memory sync
583
705
  agent-memory status --json`);
@@ -620,6 +742,13 @@ async function main() {
620
742
  case "install-skills":
621
743
  cmdInstallSkills(flags);
622
744
  break;
745
+ case "uninstall-skills":
746
+ cmdInstallSkills({ ...flags, uninstall: true });
747
+ break;
748
+ case "distil":
749
+ case "distill":
750
+ await cmdDistil(flags);
751
+ break;
623
752
  case "sync":
624
753
  await cmdSync(flags);
625
754
  break;
package/dist/core.d.ts CHANGED
@@ -18,6 +18,8 @@ export declare function getMemoryFile(): string;
18
18
  export declare function getScratchpadFile(): string;
19
19
  /** Get the current daily log directory path. */
20
20
  export declare function getDailyDir(): string;
21
+ /** Get the current topics directory path. */
22
+ export declare function getTopicsDir(): string;
21
23
  export declare function ensureDirs(): void;
22
24
  export declare function todayStr(): string;
23
25
  export declare function yesterdayStr(): string;
@@ -25,6 +27,8 @@ export declare function nowTimestamp(): string;
25
27
  export declare function shortSessionId(sessionId: string): string;
26
28
  export declare function readFileSafe(filePath: string): string | null;
27
29
  export declare function dailyPath(date: string): string;
30
+ export declare function topicPath(slug: string): string;
31
+ export declare function slugifyTopic(name: string): string;
28
32
  export declare const RESPONSE_PREVIEW_MAX_CHARS = 4000;
29
33
  export declare const RESPONSE_PREVIEW_MAX_LINES = 120;
30
34
  export type TruncateMode = "start" | "end" | "middle";
@@ -132,6 +136,20 @@ export interface InstallSkillsReport {
132
136
  error?: string;
133
137
  }
134
138
  export declare function installSkills(): InstallSkillsReport;
139
+ export interface UninstallSkillsReport {
140
+ ok: boolean;
141
+ homeDir?: string;
142
+ removed: Array<{
143
+ label: string;
144
+ path: string;
145
+ }>;
146
+ skipped: Array<{
147
+ label: string;
148
+ reason: string;
149
+ }>;
150
+ error?: string;
151
+ }
152
+ export declare function uninstallSkills(): UninstallSkillsReport;
135
153
  export interface QmdHealthInfo {
136
154
  totalFiles: number | null;
137
155
  vectorsEmbedded: number | null;
@@ -167,10 +185,12 @@ export interface ToolResult {
167
185
  isError?: boolean;
168
186
  }
169
187
  export declare function memoryWrite(params: {
170
- target: "long_term" | "daily";
188
+ target?: "long_term" | "daily" | "topic";
171
189
  content: string;
172
190
  mode?: "append" | "overwrite";
173
191
  sessionId?: string;
192
+ topic?: string;
193
+ date?: string;
174
194
  }): Promise<ToolResult>;
175
195
  export declare function scratchpadAction(params: {
176
196
  action: "add" | "done" | "undo" | "clear_done" | "list";
@@ -178,12 +198,62 @@ export declare function scratchpadAction(params: {
178
198
  sessionId?: string;
179
199
  }): Promise<ToolResult>;
180
200
  export declare function memoryRead(params: {
181
- target: "long_term" | "scratchpad" | "daily" | "list";
201
+ target: "long_term" | "scratchpad" | "daily" | "list" | "topic" | "topics";
182
202
  date?: string;
203
+ topic?: string;
183
204
  }): Promise<ToolResult>;
184
205
  export declare function memorySearch(params: {
185
206
  query: string;
186
207
  mode?: "keyword" | "semantic" | "deep";
187
208
  limit?: number;
188
209
  }): Promise<ToolResult>;
210
+ /** Extract #tag patterns, deduplicated and lowercased. */
211
+ export declare function extractTags(content: string): string[];
212
+ /** Extract [[link]] patterns. */
213
+ export declare function extractLinks(content: string): string[];
214
+ /** Extract file-path-like patterns (e.g. src/foo.ts), filtering out URLs. */
215
+ export declare function extractFilePaths(content: string): string[];
216
+ /** Extract backtick-quoted commands. */
217
+ export declare function extractCommands(content: string): string[];
218
+ export interface DailyEntry {
219
+ date: string;
220
+ timestamp: string;
221
+ sessionId: string;
222
+ content: string;
223
+ tags: string[];
224
+ links: string[];
225
+ filePaths: string[];
226
+ commands: string[];
227
+ }
228
+ /** Split a daily file on <!-- timestamp --> markers into individual entries. */
229
+ export declare function parseDailyEntries(date: string, content: string): DailyEntry[];
230
+ export interface TopicEntry {
231
+ topic: string;
232
+ slug: string;
233
+ date: string;
234
+ timestamp: string;
235
+ sessionId: string;
236
+ content: string;
237
+ tags: string[];
238
+ links: string[];
239
+ filePaths: string[];
240
+ commands: string[];
241
+ }
242
+ /** Split a topic file on <!-- timestamp --> markers into entries. */
243
+ export declare function parseTopicEntries(topic: string, slug: string, content: string): TopicEntry[];
244
+ export interface DistilResult {
245
+ ok: boolean;
246
+ dryRun: boolean;
247
+ totalDailyFiles: number;
248
+ totalTopicFiles: number;
249
+ totalEntries: number;
250
+ totalTopicEntries: number;
251
+ totalTags: number;
252
+ tagCounts: Record<string, number>;
253
+ output: string;
254
+ }
255
+ export declare function distilMemories(params?: {
256
+ dryRun?: boolean;
257
+ sessionId?: string;
258
+ }): Promise<DistilResult>;
189
259
  export {};