myagentmemory 0.4.3
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/LICENSE +21 -0
- package/README.md +231 -0
- package/dist/agent-memory +0 -0
- package/dist/cli.d.ts +18 -0
- package/dist/cli.js +510 -0
- package/dist/core.d.ts +131 -0
- package/dist/core.js +883 -0
- package/package.json +70 -0
- package/scripts/install-skills.ps1 +48 -0
- package/scripts/install-skills.sh +55 -0
- package/scripts/postinstall.cjs +44 -0
- package/skills/claude-code/SKILL.md +101 -0
- package/skills/codex/SKILL.md +104 -0
- package/src/cli.ts +596 -0
- package/src/core.ts +1082 -0
package/src/cli.ts
ADDED
|
@@ -0,0 +1,596 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* agent-memory CLI
|
|
4
|
+
*
|
|
5
|
+
* Subcommands:
|
|
6
|
+
* context — Build & print context injection string to stdout
|
|
7
|
+
* write — Write to memory files
|
|
8
|
+
* read — Read memory files
|
|
9
|
+
* scratchpad — Manage checklist
|
|
10
|
+
* search — Search via qmd
|
|
11
|
+
* init — Create dirs, detect qmd, setup collection
|
|
12
|
+
* status — Show config, qmd status, file counts
|
|
13
|
+
*
|
|
14
|
+
* Global flags:
|
|
15
|
+
* --dir <path> Override memory directory
|
|
16
|
+
* --json Machine-readable JSON output
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import * as fs from "node:fs";
|
|
20
|
+
import {
|
|
21
|
+
_setBaseDir,
|
|
22
|
+
buildMemoryContext,
|
|
23
|
+
checkCollection,
|
|
24
|
+
dailyPath,
|
|
25
|
+
detectQmd,
|
|
26
|
+
ensureDirs,
|
|
27
|
+
ensureQmdAvailableForUpdate,
|
|
28
|
+
getCollectionName,
|
|
29
|
+
getDailyDir,
|
|
30
|
+
getMemoryDir,
|
|
31
|
+
getMemoryFile,
|
|
32
|
+
getQmdResultPath,
|
|
33
|
+
getQmdResultText,
|
|
34
|
+
getScratchpadFile,
|
|
35
|
+
nowTimestamp,
|
|
36
|
+
parseScratchpad,
|
|
37
|
+
readFileSafe,
|
|
38
|
+
runQmdSearch,
|
|
39
|
+
scheduleQmdUpdate,
|
|
40
|
+
searchRelevantMemories,
|
|
41
|
+
serializeScratchpad,
|
|
42
|
+
setupQmdCollection,
|
|
43
|
+
todayStr,
|
|
44
|
+
} from "./core.js";
|
|
45
|
+
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// Arg parsing (no external deps)
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
interface ParsedArgs {
|
|
51
|
+
command: string;
|
|
52
|
+
flags: Record<string, string | boolean>;
|
|
53
|
+
positional: string[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function parseArgs(argv: string[]): ParsedArgs {
|
|
57
|
+
const flags: Record<string, string | boolean> = {};
|
|
58
|
+
const positional: string[] = [];
|
|
59
|
+
let command = "";
|
|
60
|
+
|
|
61
|
+
for (let i = 0; i < argv.length; i++) {
|
|
62
|
+
const arg = argv[i];
|
|
63
|
+
|
|
64
|
+
if (!command && !arg.startsWith("-")) {
|
|
65
|
+
command = arg;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (arg.startsWith("--")) {
|
|
70
|
+
const key = arg.slice(2);
|
|
71
|
+
const next = argv[i + 1];
|
|
72
|
+
if (next && !next.startsWith("--")) {
|
|
73
|
+
flags[key] = next;
|
|
74
|
+
i++;
|
|
75
|
+
} else {
|
|
76
|
+
flags[key] = true;
|
|
77
|
+
}
|
|
78
|
+
} else if (!arg.startsWith("-")) {
|
|
79
|
+
positional.push(arg);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return { command, flags, positional };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function getFlag(flags: Record<string, string | boolean>, key: string): string | undefined {
|
|
87
|
+
const val = flags[key];
|
|
88
|
+
return typeof val === "string" ? val : undefined;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function hasFlag(flags: Record<string, string | boolean>, key: string): boolean {
|
|
92
|
+
return key in flags;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
// Output helpers
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
|
|
99
|
+
function output(data: unknown, json: boolean) {
|
|
100
|
+
if (json) {
|
|
101
|
+
console.log(JSON.stringify(data, null, 2));
|
|
102
|
+
} else if (typeof data === "string") {
|
|
103
|
+
console.log(data);
|
|
104
|
+
} else {
|
|
105
|
+
console.log(JSON.stringify(data, null, 2));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function exitError(message: string, json: boolean): never {
|
|
110
|
+
if (json) {
|
|
111
|
+
console.error(JSON.stringify({ error: message }));
|
|
112
|
+
} else {
|
|
113
|
+
console.error(`Error: ${message}`);
|
|
114
|
+
}
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
// Commands
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
|
|
122
|
+
async function cmdContext(flags: Record<string, string | boolean>) {
|
|
123
|
+
const json = hasFlag(flags, "json");
|
|
124
|
+
const noSearch = hasFlag(flags, "no-search");
|
|
125
|
+
|
|
126
|
+
ensureDirs();
|
|
127
|
+
const searchResults = noSearch ? "" : await searchRelevantMemories("");
|
|
128
|
+
const context = buildMemoryContext(searchResults);
|
|
129
|
+
|
|
130
|
+
if (json) {
|
|
131
|
+
output({ context, directory: getMemoryDir() }, true);
|
|
132
|
+
} else {
|
|
133
|
+
if (context) {
|
|
134
|
+
process.stdout.write(context);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function cmdWrite(flags: Record<string, string | boolean>) {
|
|
140
|
+
const json = hasFlag(flags, "json");
|
|
141
|
+
const target = getFlag(flags, "target");
|
|
142
|
+
const content = getFlag(flags, "content");
|
|
143
|
+
const mode = getFlag(flags, "mode") ?? "append";
|
|
144
|
+
|
|
145
|
+
if (!target || !["long_term", "daily"].includes(target)) {
|
|
146
|
+
exitError("--target must be 'long_term' or 'daily'", json);
|
|
147
|
+
}
|
|
148
|
+
if (!content) {
|
|
149
|
+
exitError("--content is required", json);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
ensureDirs();
|
|
153
|
+
const ts = nowTimestamp();
|
|
154
|
+
const sid = "cli";
|
|
155
|
+
|
|
156
|
+
if (target === "daily") {
|
|
157
|
+
const filePath = dailyPath(todayStr());
|
|
158
|
+
const existing = readFileSafe(filePath) ?? "";
|
|
159
|
+
const separator = existing.trim() ? "\n\n" : "";
|
|
160
|
+
const stamped = `<!-- ${ts} [${sid}] -->\n${content}`;
|
|
161
|
+
fs.writeFileSync(filePath, existing + separator + stamped, "utf-8");
|
|
162
|
+
await ensureQmdAvailableForUpdate();
|
|
163
|
+
scheduleQmdUpdate();
|
|
164
|
+
output(
|
|
165
|
+
json
|
|
166
|
+
? { ok: true, path: filePath, target, mode: "append", timestamp: ts }
|
|
167
|
+
: `Appended to daily log: ${filePath}`,
|
|
168
|
+
json,
|
|
169
|
+
);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// long_term
|
|
174
|
+
const memFile = getMemoryFile();
|
|
175
|
+
const existing = readFileSafe(memFile) ?? "";
|
|
176
|
+
|
|
177
|
+
if (mode === "overwrite") {
|
|
178
|
+
const stamped = `<!-- last updated: ${ts} [${sid}] -->\n${content}`;
|
|
179
|
+
fs.writeFileSync(memFile, stamped, "utf-8");
|
|
180
|
+
} else {
|
|
181
|
+
const separator = existing.trim() ? "\n\n" : "";
|
|
182
|
+
const stamped = `<!-- ${ts} [${sid}] -->\n${content}`;
|
|
183
|
+
fs.writeFileSync(memFile, existing + separator + stamped, "utf-8");
|
|
184
|
+
}
|
|
185
|
+
await ensureQmdAvailableForUpdate();
|
|
186
|
+
scheduleQmdUpdate();
|
|
187
|
+
output(
|
|
188
|
+
json
|
|
189
|
+
? { ok: true, path: memFile, target, mode, timestamp: ts }
|
|
190
|
+
: `${mode === "overwrite" ? "Overwrote" : "Appended to"} MEMORY.md`,
|
|
191
|
+
json,
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async function cmdRead(flags: Record<string, string | boolean>) {
|
|
196
|
+
const json = hasFlag(flags, "json");
|
|
197
|
+
const target = getFlag(flags, "target");
|
|
198
|
+
const date = getFlag(flags, "date");
|
|
199
|
+
|
|
200
|
+
if (!target || !["long_term", "scratchpad", "daily", "list"].includes(target)) {
|
|
201
|
+
exitError("--target must be 'long_term', 'scratchpad', 'daily', or 'list'", json);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
ensureDirs();
|
|
205
|
+
|
|
206
|
+
if (target === "list") {
|
|
207
|
+
try {
|
|
208
|
+
const files = fs
|
|
209
|
+
.readdirSync(getDailyDir())
|
|
210
|
+
.filter((f) => f.endsWith(".md"))
|
|
211
|
+
.sort()
|
|
212
|
+
.reverse();
|
|
213
|
+
if (json) {
|
|
214
|
+
output({ files }, true);
|
|
215
|
+
} else if (files.length === 0) {
|
|
216
|
+
console.log("No daily logs found.");
|
|
217
|
+
} else {
|
|
218
|
+
console.log(`Daily logs:\n${files.map((f) => `- ${f}`).join("\n")}`);
|
|
219
|
+
}
|
|
220
|
+
} catch {
|
|
221
|
+
output(json ? { files: [] } : "No daily logs directory.", json);
|
|
222
|
+
}
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (target === "daily") {
|
|
227
|
+
const d = date ?? todayStr();
|
|
228
|
+
const filePath = dailyPath(d);
|
|
229
|
+
const content = readFileSafe(filePath);
|
|
230
|
+
if (!content) {
|
|
231
|
+
output(json ? { content: null, date: d } : `No daily log for ${d}.`, json);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
output(json ? { content, date: d, path: filePath } : content, json);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (target === "scratchpad") {
|
|
239
|
+
const content = readFileSafe(getScratchpadFile());
|
|
240
|
+
if (!content?.trim()) {
|
|
241
|
+
output(json ? { content: null } : "SCRATCHPAD.md is empty or does not exist.", json);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
output(json ? { content, path: getScratchpadFile() } : content, json);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// long_term
|
|
249
|
+
const content = readFileSafe(getMemoryFile());
|
|
250
|
+
if (!content) {
|
|
251
|
+
output(json ? { content: null } : "MEMORY.md is empty or does not exist.", json);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
output(json ? { content, path: getMemoryFile() } : content, json);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async function cmdScratchpad(flags: Record<string, string | boolean>, positional: string[]) {
|
|
258
|
+
const json = hasFlag(flags, "json");
|
|
259
|
+
const action = positional[0];
|
|
260
|
+
const text = getFlag(flags, "text");
|
|
261
|
+
|
|
262
|
+
if (!action || !["add", "done", "undo", "clear_done", "list"].includes(action)) {
|
|
263
|
+
exitError("Usage: agent-memory scratchpad <add|done|undo|clear_done|list> [--text <text>]", json);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
ensureDirs();
|
|
267
|
+
const spFile = getScratchpadFile();
|
|
268
|
+
const existing = readFileSafe(spFile) ?? "";
|
|
269
|
+
let items = parseScratchpad(existing);
|
|
270
|
+
|
|
271
|
+
if (action === "list") {
|
|
272
|
+
if (items.length === 0) {
|
|
273
|
+
output(json ? { items: [], count: 0, open: 0 } : "Scratchpad is empty.", json);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (json) {
|
|
277
|
+
output(
|
|
278
|
+
{
|
|
279
|
+
items: items.map((i) => ({ done: i.done, text: i.text })),
|
|
280
|
+
count: items.length,
|
|
281
|
+
open: items.filter((i) => !i.done).length,
|
|
282
|
+
},
|
|
283
|
+
true,
|
|
284
|
+
);
|
|
285
|
+
} else {
|
|
286
|
+
console.log(serializeScratchpad(items));
|
|
287
|
+
}
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (action === "add") {
|
|
292
|
+
if (!text) exitError("--text is required for add", json);
|
|
293
|
+
const ts = nowTimestamp();
|
|
294
|
+
items.push({ done: false, text: text!, meta: `<!-- ${ts} [cli] -->` });
|
|
295
|
+
fs.writeFileSync(spFile, serializeScratchpad(items), "utf-8");
|
|
296
|
+
await ensureQmdAvailableForUpdate();
|
|
297
|
+
scheduleQmdUpdate();
|
|
298
|
+
output(json ? { ok: true, action, text } : `Added: - [ ] ${text}`, json);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (action === "done" || action === "undo") {
|
|
303
|
+
if (!text) exitError(`--text is required for ${action}`, json);
|
|
304
|
+
const needle = text!.toLowerCase();
|
|
305
|
+
const targetDone = action === "done";
|
|
306
|
+
let matched = false;
|
|
307
|
+
for (const item of items) {
|
|
308
|
+
if (item.done !== targetDone && item.text.toLowerCase().includes(needle)) {
|
|
309
|
+
item.done = targetDone;
|
|
310
|
+
matched = true;
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
if (!matched) {
|
|
315
|
+
exitError(`No matching ${targetDone ? "open" : "done"} item found for: "${text}"`, json);
|
|
316
|
+
}
|
|
317
|
+
fs.writeFileSync(spFile, serializeScratchpad(items), "utf-8");
|
|
318
|
+
await ensureQmdAvailableForUpdate();
|
|
319
|
+
scheduleQmdUpdate();
|
|
320
|
+
output(json ? { ok: true, action, text } : "Updated.", json);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (action === "clear_done") {
|
|
325
|
+
const before = items.length;
|
|
326
|
+
items = items.filter((i) => !i.done);
|
|
327
|
+
const removed = before - items.length;
|
|
328
|
+
fs.writeFileSync(spFile, serializeScratchpad(items), "utf-8");
|
|
329
|
+
await ensureQmdAvailableForUpdate();
|
|
330
|
+
scheduleQmdUpdate();
|
|
331
|
+
output(json ? { ok: true, action, removed } : `Cleared ${removed} done item(s).`, json);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
async function cmdSearch(flags: Record<string, string | boolean>) {
|
|
336
|
+
const json = hasFlag(flags, "json");
|
|
337
|
+
const query = getFlag(flags, "query");
|
|
338
|
+
const mode = (getFlag(flags, "mode") ?? "keyword") as "keyword" | "semantic" | "deep";
|
|
339
|
+
const limit = Number.parseInt(getFlag(flags, "limit") ?? "5", 10);
|
|
340
|
+
|
|
341
|
+
if (!query) exitError("--query is required", json);
|
|
342
|
+
if (!["keyword", "semantic", "deep"].includes(mode)) {
|
|
343
|
+
exitError("--mode must be 'keyword', 'semantic', or 'deep'", json);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const qmdFound = await detectQmd();
|
|
347
|
+
if (!qmdFound) {
|
|
348
|
+
exitError("qmd is not installed. Install: bun install -g https://github.com/tobi/qmd", json);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const collName = getCollectionName();
|
|
352
|
+
const hasCollection = await checkCollection(collName);
|
|
353
|
+
if (!hasCollection) {
|
|
354
|
+
exitError(`qmd collection '${collName}' not found. Run: agent-memory init`, json);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
try {
|
|
358
|
+
const { results, stderr } = await runQmdSearch(mode, query!, limit);
|
|
359
|
+
|
|
360
|
+
if (json) {
|
|
361
|
+
output({ mode, query, count: results.length, results }, true);
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (results.length === 0) {
|
|
366
|
+
const needsEmbed = /need embeddings/i.test(stderr ?? "");
|
|
367
|
+
if (needsEmbed && (mode === "semantic" || mode === "deep")) {
|
|
368
|
+
console.log(`No results found. qmd reports missing embeddings — run: qmd embed`);
|
|
369
|
+
} else {
|
|
370
|
+
console.log(`No results found for "${query}" (mode: ${mode}).`);
|
|
371
|
+
}
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
for (let i = 0; i < results.length; i++) {
|
|
376
|
+
const r = results[i];
|
|
377
|
+
const filePath = getQmdResultPath(r);
|
|
378
|
+
const text = getQmdResultText(r);
|
|
379
|
+
console.log(`--- Result ${i + 1} ---`);
|
|
380
|
+
if (filePath) console.log(`File: ${filePath}`);
|
|
381
|
+
if (r.score != null) console.log(`Score: ${r.score}`);
|
|
382
|
+
if (text) console.log(text);
|
|
383
|
+
console.log("");
|
|
384
|
+
}
|
|
385
|
+
} catch (err) {
|
|
386
|
+
exitError(`Search failed: ${err instanceof Error ? err.message : String(err)}`, json);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
async function cmdInit(flags: Record<string, string | boolean>) {
|
|
391
|
+
const json = hasFlag(flags, "json");
|
|
392
|
+
|
|
393
|
+
ensureDirs();
|
|
394
|
+
const dir = getMemoryDir();
|
|
395
|
+
|
|
396
|
+
const qmdFound = await detectQmd();
|
|
397
|
+
let collectionCreated = false;
|
|
398
|
+
|
|
399
|
+
if (qmdFound) {
|
|
400
|
+
const collName = getCollectionName();
|
|
401
|
+
const hasCollection = await checkCollection(collName);
|
|
402
|
+
if (!hasCollection) {
|
|
403
|
+
collectionCreated = await setupQmdCollection();
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (json) {
|
|
408
|
+
output(
|
|
409
|
+
{
|
|
410
|
+
ok: true,
|
|
411
|
+
directory: dir,
|
|
412
|
+
qmd: qmdFound,
|
|
413
|
+
collectionCreated,
|
|
414
|
+
},
|
|
415
|
+
true,
|
|
416
|
+
);
|
|
417
|
+
} else {
|
|
418
|
+
console.log(`Memory directory: ${dir}`);
|
|
419
|
+
console.log(` MEMORY.md, SCRATCHPAD.md, daily/ created.`);
|
|
420
|
+
if (qmdFound) {
|
|
421
|
+
if (collectionCreated) {
|
|
422
|
+
console.log(` qmd collection '${getCollectionName()}' created.`);
|
|
423
|
+
} else {
|
|
424
|
+
console.log(` qmd collection '${getCollectionName()}' already exists.`);
|
|
425
|
+
}
|
|
426
|
+
} else {
|
|
427
|
+
console.log(` qmd not found — search features unavailable.`);
|
|
428
|
+
console.log(` Install: bun install -g https://github.com/tobi/qmd`);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
async function cmdStatus(flags: Record<string, string | boolean>) {
|
|
434
|
+
const json = hasFlag(flags, "json");
|
|
435
|
+
|
|
436
|
+
ensureDirs();
|
|
437
|
+
const dir = getMemoryDir();
|
|
438
|
+
const memFile = getMemoryFile();
|
|
439
|
+
const spFile = getScratchpadFile();
|
|
440
|
+
const dailyDir = getDailyDir();
|
|
441
|
+
|
|
442
|
+
const memContent = readFileSafe(memFile);
|
|
443
|
+
const spContent = readFileSafe(spFile);
|
|
444
|
+
|
|
445
|
+
let dailyCount = 0;
|
|
446
|
+
try {
|
|
447
|
+
dailyCount = fs.readdirSync(dailyDir).filter((f) => f.endsWith(".md")).length;
|
|
448
|
+
} catch {
|
|
449
|
+
// directory may not exist
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const qmdFound = await detectQmd();
|
|
453
|
+
let hasCollection = false;
|
|
454
|
+
if (qmdFound) {
|
|
455
|
+
hasCollection = await checkCollection();
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
if (json) {
|
|
459
|
+
output(
|
|
460
|
+
{
|
|
461
|
+
directory: dir,
|
|
462
|
+
memoryFile: {
|
|
463
|
+
exists: memContent !== null,
|
|
464
|
+
chars: memContent?.length ?? 0,
|
|
465
|
+
lines: memContent ? memContent.split("\n").length : 0,
|
|
466
|
+
},
|
|
467
|
+
scratchpadFile: {
|
|
468
|
+
exists: spContent !== null,
|
|
469
|
+
items: spContent ? parseScratchpad(spContent).length : 0,
|
|
470
|
+
openItems: spContent ? parseScratchpad(spContent).filter((i) => !i.done).length : 0,
|
|
471
|
+
},
|
|
472
|
+
dailyLogs: dailyCount,
|
|
473
|
+
qmd: {
|
|
474
|
+
available: qmdFound,
|
|
475
|
+
collection: hasCollection ? getCollectionName() : null,
|
|
476
|
+
},
|
|
477
|
+
},
|
|
478
|
+
true,
|
|
479
|
+
);
|
|
480
|
+
} else {
|
|
481
|
+
console.log(`Memory directory: ${dir}`);
|
|
482
|
+
console.log("");
|
|
483
|
+
if (memContent !== null) {
|
|
484
|
+
const lines = memContent.split("\n").length;
|
|
485
|
+
console.log(`MEMORY.md: ${memContent.length} chars, ${lines} lines`);
|
|
486
|
+
} else {
|
|
487
|
+
console.log("MEMORY.md: not created yet");
|
|
488
|
+
}
|
|
489
|
+
if (spContent !== null) {
|
|
490
|
+
const items = parseScratchpad(spContent);
|
|
491
|
+
const open = items.filter((i) => !i.done).length;
|
|
492
|
+
console.log(`SCRATCHPAD.md: ${items.length} items (${open} open)`);
|
|
493
|
+
} else {
|
|
494
|
+
console.log("SCRATCHPAD.md: not created yet");
|
|
495
|
+
}
|
|
496
|
+
console.log(`Daily logs: ${dailyCount} file(s)`);
|
|
497
|
+
console.log("");
|
|
498
|
+
if (qmdFound) {
|
|
499
|
+
console.log(`qmd: available`);
|
|
500
|
+
console.log(
|
|
501
|
+
`Collection '${getCollectionName()}': ${hasCollection ? "configured" : "not configured — run: agent-memory init"}`,
|
|
502
|
+
);
|
|
503
|
+
} else {
|
|
504
|
+
console.log("qmd: not installed");
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// ---------------------------------------------------------------------------
|
|
510
|
+
// Usage
|
|
511
|
+
// ---------------------------------------------------------------------------
|
|
512
|
+
|
|
513
|
+
function printUsage() {
|
|
514
|
+
console.log(`agent-memory — persistent memory for coding agents
|
|
515
|
+
|
|
516
|
+
Usage:
|
|
517
|
+
agent-memory <command> [options]
|
|
518
|
+
|
|
519
|
+
Commands:
|
|
520
|
+
context Build & print context injection string
|
|
521
|
+
write Write to memory files
|
|
522
|
+
read Read memory files
|
|
523
|
+
scratchpad Manage checklist items
|
|
524
|
+
search Search across memory files (requires qmd)
|
|
525
|
+
init Initialize memory directory and qmd collection
|
|
526
|
+
status Show configuration and status
|
|
527
|
+
|
|
528
|
+
Global flags:
|
|
529
|
+
--dir <path> Override memory directory
|
|
530
|
+
--json Machine-readable JSON output
|
|
531
|
+
|
|
532
|
+
Examples:
|
|
533
|
+
agent-memory init
|
|
534
|
+
agent-memory write --target long_term --content "User prefers dark mode"
|
|
535
|
+
agent-memory write --target daily --content "Fixed auth bug in login flow"
|
|
536
|
+
agent-memory read --target long_term
|
|
537
|
+
agent-memory read --target daily --date 2026-02-15
|
|
538
|
+
agent-memory read --target list
|
|
539
|
+
agent-memory scratchpad add --text "Review PR #42"
|
|
540
|
+
agent-memory scratchpad list
|
|
541
|
+
agent-memory scratchpad done --text "PR #42"
|
|
542
|
+
agent-memory search --query "database choice" --mode keyword
|
|
543
|
+
agent-memory context --no-search
|
|
544
|
+
agent-memory status --json`);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// ---------------------------------------------------------------------------
|
|
548
|
+
// Main
|
|
549
|
+
// ---------------------------------------------------------------------------
|
|
550
|
+
|
|
551
|
+
async function main() {
|
|
552
|
+
const { command, flags, positional } = parseArgs(process.argv.slice(2));
|
|
553
|
+
const json = hasFlag(flags, "json");
|
|
554
|
+
|
|
555
|
+
// Apply --dir override
|
|
556
|
+
const dir = getFlag(flags, "dir");
|
|
557
|
+
if (dir) {
|
|
558
|
+
_setBaseDir(dir);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
if (!command || command === "help" || hasFlag(flags, "help")) {
|
|
562
|
+
printUsage();
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
switch (command) {
|
|
567
|
+
case "context":
|
|
568
|
+
await cmdContext(flags);
|
|
569
|
+
break;
|
|
570
|
+
case "write":
|
|
571
|
+
await cmdWrite(flags);
|
|
572
|
+
break;
|
|
573
|
+
case "read":
|
|
574
|
+
await cmdRead(flags);
|
|
575
|
+
break;
|
|
576
|
+
case "scratchpad":
|
|
577
|
+
await cmdScratchpad(flags, positional);
|
|
578
|
+
break;
|
|
579
|
+
case "search":
|
|
580
|
+
await cmdSearch(flags);
|
|
581
|
+
break;
|
|
582
|
+
case "init":
|
|
583
|
+
await cmdInit(flags);
|
|
584
|
+
break;
|
|
585
|
+
case "status":
|
|
586
|
+
await cmdStatus(flags);
|
|
587
|
+
break;
|
|
588
|
+
default:
|
|
589
|
+
exitError(`Unknown command: ${command}. Run 'agent-memory help' for usage.`, json);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
main().catch((err) => {
|
|
594
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
595
|
+
process.exit(1);
|
|
596
|
+
});
|