toon-memory 1.5.0 → 1.6.1

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
@@ -1,10 +1,29 @@
1
1
  # toon-memory
2
2
 
3
- Persistent memory for AI coding agents — remember decisions, patterns, and bugs between sessions.
3
+ > Persistent memory for AI coding agents — remember decisions, patterns, and bugs between sessions.
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/toon-memory.svg)](https://www.npmjs.com/package/toon-memory)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
7
  [![CI](https://github.com/LuiggiVal08/toon-memory/actions/workflows/ci.yml/badge.svg)](https://github.com/LuiggiVal08/toon-memory/actions/workflows/ci.yml)
8
+ [![Docs](https://img.shields.io/badge/docs-online-blue)](https://luiggival08.github.io/toon-memory/)
9
+
10
+ ---
11
+
12
+ ## Table of Contents
13
+
14
+ - [What is toon-memory?](#what-is-toon-memory)
15
+ - [Blog Post](#blog-post)
16
+ - [Features](#features)
17
+ - [Quick Start](#quick-start)
18
+ - [Supported Agents](#supported-agents)
19
+ - [MCP Tools](#mcp-tools)
20
+ - [CLI Commands](#cli-commands)
21
+ - [Configuration](#configuration)
22
+ - [How It Works](#how-it-works)
23
+ - [Why TOON?](#why-toon)
24
+ - [Development](#development)
25
+ - [Contributing](#contributing)
26
+ - [License](#license)
8
27
 
9
28
  ---
10
29
 
@@ -12,6 +31,8 @@ Persistent memory for AI coding agents — remember decisions, patterns, and bug
12
31
 
13
32
  AI agents forget everything between sessions. toon-memory fixes this by providing persistent memory that survives restarts.
14
33
 
34
+ 📖 **[Read the documentation](https://luiggival08.github.io/toon-memory/)**
35
+
15
36
  **Use cases:**
16
37
  - Remember design decisions ("Why did we choose X over Y?")
17
38
  - Track patterns ("This project uses Zod for validation")
@@ -20,18 +41,24 @@ AI agents forget everything between sessions. toon-memory fixes this by providin
20
41
 
21
42
  ---
22
43
 
44
+ ## Blog Post
45
+
46
+ Read [How toon-memory Makes Your AI Agent Smarter](https://luiggival08.github.io/toon-memory/blog) to see a real-world demo of persistent memory in action.
47
+
48
+ ---
49
+
23
50
  ## Features
24
51
 
25
- - **8 MCP tools** — `memory_remember`, `memory_recall`, `memory_forget`, `memory_stats`, `memory_summary`, `memory_archive`, `memory_encrypt`, `memory_decrypt`
52
+ - **8 MCP tools** — Full memory management via Model Context Protocol
26
53
  - **7 agents supported** — OpenCode, VS Code/Copilot, Claude Code, Cursor, Windsurf, Cline, Continue
27
54
  - **TOON format** — 40% fewer tokens than JSON, better LLM comprehension
28
- - **Per-project memory** — each project gets its own memory file
29
- - **Zero config** — just install and use
30
- - **Auto gitignore** — automatically adds `.opencode/memory/` to `.gitignore`
31
- - **Date filtering** — search memory by date range
32
- - **Auto-archive** — old entries (>30 days) moved to archive automatically
55
+ - **Per-project memory** — Each project gets its own memory file
56
+ - **Zero config** — Just install and use
57
+ - **Auto gitignore** — Automatically adds `.opencode/memory/` to `.gitignore`
58
+ - **Date filtering** — Search memory by date range
59
+ - **Auto-archive** — Old entries (>30 days) moved to archive automatically
33
60
  - **Encryption** — AES-256-GCM encryption for sensitive data
34
- - **Watch mode** — auto-backup every N minutes
61
+ - **Watch mode** — Auto-backup every N minutes
35
62
 
36
63
  ---
37
64
 
@@ -83,7 +110,7 @@ memory_remember # Save important decisions
83
110
 
84
111
  ---
85
112
 
86
- ## Tools
113
+ ## MCP Tools
87
114
 
88
115
  | Tool | Description |
89
116
  |------|-------------|
@@ -96,12 +123,33 @@ memory_remember # Save important decisions
96
123
  | `memory_encrypt` | Enable AES-256-GCM encryption |
97
124
  | `memory_decrypt` | Disable encryption |
98
125
 
99
- ### Date Filtering
126
+ ### Examples
127
+
128
+ #### Remember a decision
100
129
 
101
- Search memory by date range:
130
+ ```typescript
131
+ memory_remember({
132
+ category: "decision",
133
+ key: "use-zod",
134
+ content: "Use Zod for validation",
135
+ file: "src/types.ts",
136
+ tags: "validation;types"
137
+ })
138
+ // 🧠 Guardado: decision/use-zod (a1b2c3d4)
139
+ ```
140
+
141
+ #### Search memory
142
+
143
+ ```typescript
144
+ memory_recall({ query: "redis" })
145
+ // [bug] redis-pool-fix (i9j0k1l2)
146
+ // Added max_connections=20
147
+ // File: redis.ts | Tags: redis;fix | Date: 2026-07-10
148
+ ```
149
+
150
+ #### Search with date filter
102
151
 
103
152
  ```typescript
104
- // Search with date filter
105
153
  memory_recall({
106
154
  query: "redis",
107
155
  from_date: "2026-07-01",
@@ -109,52 +157,21 @@ memory_recall({
109
157
  })
110
158
  ```
111
159
 
112
- ### Auto-Archive
113
-
114
- Entries older than 30 days are automatically archived to keep memory clean:
160
+ #### Archive old entries
115
161
 
116
162
  ```typescript
117
- // Manually trigger archiving
118
163
  memory_archive()
119
164
  // 📦 Archivadas 5 entradas antiguas
120
165
  // 📋 Quedan 42 entradas activas
121
166
  ```
122
167
 
123
- ### Encryption
124
-
125
- Enable AES-256-GCM encryption for sensitive data:
168
+ #### Enable encryption
126
169
 
127
170
  ```typescript
128
- // Enable encryption
129
171
  memory_encrypt()
130
172
  // 🔐 Encriptación habilitada
131
173
  // ⚠️ Guarda esta clave (no se puede recuperar):
132
174
  // a1b2c3d4...
133
-
134
- // Disable encryption
135
- memory_decrypt({ key: "a1b2c3d4..." })
136
- // 🔓 Encriptación deshabilitada
137
- ```
138
-
139
- ---
140
-
141
- ## How It Works
142
-
143
- 1. **MCP Server** — runs locally, talks to your agent via stdio
144
- 2. **TOON Format** — stores data in Token-Oriented Object Notation (~40% fewer tokens than JSON)
145
- 3. **Per-project memory** — each project gets `.opencode/memory/data.toon`
146
- 4. **Zero config** — just install and use
147
-
148
- ### Memory File Format
149
-
150
- ```
151
- version: 1
152
- entries[3|]{id|category|key|content|file|tags|date}:
153
- a1b2c3d4|decision|use-zod|Use Zod for validation|src/types.ts|validation;types|2026-07-10
154
- e5f6g7h8|pattern|pydantic-configs|Project uses Pydantic v2|config.py|python;patterns|2026-07-10
155
- i9j0k1l2|bug|redis-pool-fix|Added max_connections=20|redis.ts|redis;fix|2026-07-10
156
- summaries:
157
- src/services/redis.ts: Redis connection pool with retry logic
158
175
  ```
159
176
 
160
177
  ---
@@ -169,12 +186,14 @@ npx toon-memory status # Check installation status
169
186
  npx toon-memory stats # View memory statistics
170
187
  npx toon-memory export # Export memory to JSON
171
188
  npx toon-memory import <file> # Import memory from JSON
172
- npx toon-memory watch [mins] # Auto-backup every N minutes (default: 5)
189
+ npx toon-memory watch [options] # Auto-backup with options
173
190
  npx toon-memory upgrade # Update to latest version
174
191
  npx toon-memory uninstall # Remove from all agents
175
192
  ```
176
193
 
177
- ### Stats
194
+ ### Examples
195
+
196
+ #### Stats
178
197
 
179
198
  ```bash
180
199
  $ npx toon-memory stats
@@ -192,7 +211,7 @@ Last updated: 2026-07-10
192
211
  File size: 12.4 KB
193
212
  ```
194
213
 
195
- ### Export
214
+ #### Export
196
215
 
197
216
  ```bash
198
217
  $ npx toon-memory export
@@ -203,7 +222,7 @@ Exported 45 entries to:
203
222
  /path/to/project/toon-memory-export.json
204
223
  ```
205
224
 
206
- ### Import
225
+ #### Import
207
226
 
208
227
  ```bash
209
228
  $ npx toon-memory import backup.json
@@ -214,6 +233,34 @@ Imported 3 new entries
214
233
  Skipped 2 duplicates
215
234
  ```
216
235
 
236
+ #### Watch
237
+
238
+ ```bash
239
+ $ npx toon-memory watch 15 -c -m 20
240
+
241
+ 🧠 toon-memory watch
242
+
243
+ Watching memory file every 15 minutes...
244
+ Max backups: 20
245
+ Compression: enabled
246
+ Logging: disabled
247
+ Press Ctrl+C to stop
248
+
249
+ 📦 Backup #1 created: 2026-07-11T16-00-00-000Z
250
+ 📦 Backup #2 created: 2026-07-11T16-15-00-000Z
251
+ ^C
252
+ ✅ Watch stopped. 2 backups created.
253
+ ```
254
+
255
+ **Watch Options:**
256
+
257
+ | Option | Description | Default |
258
+ |--------|-------------|---------|
259
+ | `[interval]` | Backup interval in minutes | 5 |
260
+ | `-c, --compress` | Enable gzip compression | off |
261
+ | `-l, --log [path]` | Enable file logging | off |
262
+ | `-m, --max-backups <n>` | Max backups to keep (0=unlimited) | 10 |
263
+
217
264
  ---
218
265
 
219
266
  ## Configuration
@@ -267,6 +314,41 @@ Add to `.vscode/mcp.json`:
267
314
 
268
315
  ---
269
316
 
317
+ ## How It Works
318
+
319
+ 1. **MCP Server** — Runs locally, talks to your agent via stdio
320
+ 2. **TOON Format** — Stores data in Token-Oriented Object Notation (~40% fewer tokens than JSON)
321
+ 3. **Per-project memory** — Each project gets `.opencode/memory/data.toon`
322
+ 4. **Zero config** — Just install and use
323
+
324
+ ### Memory File Format
325
+
326
+ ```
327
+ version: 1
328
+ entries[3|]{id|category|key|content|file|tags|date}:
329
+ a1b2c3d4|decision|use-zod|Use Zod for validation|src/types.ts|validation;types|2026-07-10
330
+ e5f6g7h8|pattern|pydantic-configs|Project uses Pydantic v2|config.py|python;patterns|2026-07-10
331
+ i9j0k1l2|bug|redis-pool-fix|Added max_connections=20|redis.ts|redis;fix|2026-07-10
332
+ summaries:
333
+ src/services/redis.ts: Redis connection pool with retry logic
334
+ ```
335
+
336
+ ### File Structure
337
+
338
+ ```
339
+ .opencode/
340
+ ├── memory/
341
+ │ ├── data.toon # Main memory file
342
+ │ ├── archive.toon # Archived entries (>30 days)
343
+ │ ├── config.json # Encryption settings
344
+ │ └── backups/ # Watch mode backups
345
+ │ ├── backup-2026-07-11T16-00-00-000Z.toon
346
+ │ └── backup-2026-07-11T16-10-00-000Z.toon
347
+ └── opencode.json # MCP server config
348
+ ```
349
+
350
+ ---
351
+
270
352
  ## Why TOON?
271
353
 
272
354
  TOON (Token-Oriented Object Notation) is designed for LLMs:
@@ -278,8 +360,8 @@ TOON (Token-Oriented Object Notation) is designed for LLMs:
278
360
  | **TOON** | **60** | **76.4%** |
279
361
 
280
362
  - **40% fewer tokens** than JSON
281
- - **Lossless roundtrip** — no data loss
282
- - **Better LLM comprehension** — structured for AI consumption
363
+ - **Lossless roundtrip** — No data loss
364
+ - **Better LLM comprehension** — Structured for AI consumption
283
365
 
284
366
  ---
285
367
 
@@ -293,6 +375,30 @@ npm run build
293
375
  npm test
294
376
  ```
295
377
 
378
+ ### Project Structure
379
+
380
+ ```
381
+ toon-memory/
382
+ ├── src/
383
+ │ ├── bin/
384
+ │ │ └── toon-memory.ts # Entry point
385
+ │ ├── cli/
386
+ │ │ ├── setup.ts # CLI commands
387
+ │ │ └── toon-memory.ts # CLI runner
388
+ │ ├── mcp/
389
+ │ │ └── server.ts # MCP server (8 tools)
390
+ │ └── memory.ts # Custom tool (OpenCode)
391
+ ├── tests/
392
+ │ ├── cli.test.ts # CLI tests
393
+ │ └── memory.test.ts # Memory tests
394
+ ├── .github/workflows/
395
+ │ ├── ci.yml # CI (Node.js 20/22)
396
+ │ └── publish.yml # Auto-publish on release
397
+ ├── package.json
398
+ ├── tsconfig.json
399
+ └── vitest.config.ts
400
+ ```
401
+
296
402
  ---
297
403
 
298
404
  ## Contributing
File without changes
package/dist/cli/setup.js CHANGED
@@ -1,9 +1,10 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync, cpSync, unlinkSync } from "fs";
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, cpSync, unlinkSync, readdirSync, statSync } from "fs";
2
2
  import { basename, dirname, join } from "path";
3
3
  import { fileURLToPath } from "url";
4
4
  import { execSync } from "child_process";
5
5
  import { createInterface } from "readline";
6
6
  import { createRequire } from "module";
7
+ import { gzipSync, gunzipSync } from "zlib";
7
8
  const __dirname = dirname(fileURLToPath(import.meta.url));
8
9
  const projectRoot = process.cwd();
9
10
  const sourceDir = join(__dirname, "..", "..", "src");
@@ -112,10 +113,18 @@ function installMCPConfig(agent, scope) {
112
113
  }
113
114
  const mcpKey = agent.mcpKey || "mcpServers";
114
115
  if (!config[mcpKey]) config[mcpKey] = {};
115
- config[mcpKey]["toon-memory"] = {
116
- command: "npx",
117
- args: ["-y", "toon-memory", "mcp"]
118
- };
116
+ if (agent.name === "opencode") {
117
+ config[mcpKey]["toon-memory"] = {
118
+ enabled: true,
119
+ type: "local",
120
+ command: ["npx", "-y", "toon-memory", "mcp"]
121
+ };
122
+ } else {
123
+ config[mcpKey]["toon-memory"] = {
124
+ command: "npx",
125
+ args: ["-y", "toon-memory", "mcp"]
126
+ };
127
+ }
119
128
  writeFileSync(configPath, JSON.stringify(config, null, 2));
120
129
  console.log(` MCP server added to ${configPath}`);
121
130
  }
@@ -335,6 +344,56 @@ ${newLines}
335
344
  console.log(`Skipped ${importData.entries.length - newEntries.length} duplicates
336
345
  `);
337
346
  }
347
+ function parseWatchOptions(args2) {
348
+ const opts = {
349
+ interval: 5,
350
+ maxBackups: 10,
351
+ compress: false,
352
+ logFile: false,
353
+ logPath: ""
354
+ };
355
+ for (let i = 1; i < args2.length; i++) {
356
+ const arg = args2[i];
357
+ if (arg === "--compress" || arg === "-c") {
358
+ opts.compress = true;
359
+ } else if (arg === "--log" || arg === "-l") {
360
+ opts.logFile = true;
361
+ opts.logPath = args2[++i] || join(projectRoot, ".opencode", "memory", "watch.log");
362
+ } else if (arg === "--max-backups" || arg === "-m") {
363
+ opts.maxBackups = parseInt(args2[++i]) || 10;
364
+ } else if (!arg.startsWith("-")) {
365
+ opts.interval = parseInt(arg) || 5;
366
+ }
367
+ }
368
+ return opts;
369
+ }
370
+ function writeWatchLog(logPath, message) {
371
+ if (!logPath) return;
372
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
373
+ const logLine = `[${timestamp}] ${message}
374
+ `;
375
+ writeFileSync(logPath, logLine, { flag: "a" });
376
+ }
377
+ function getBackupFiles(backupDir) {
378
+ if (!existsSync(backupDir)) return [];
379
+ return readdirSync(backupDir).filter((f) => f.startsWith("backup-") && (f.endsWith(".toon") || f.endsWith(".gz"))).map((f) => join(backupDir, f)).sort((a, b) => statSync(a).mtimeMs - statSync(b).mtimeMs);
380
+ }
381
+ function pruneBackups(backupDir, maxBackups) {
382
+ if (maxBackups <= 0) return 0;
383
+ const files = getBackupFiles(backupDir);
384
+ const excess = files.length - maxBackups;
385
+ if (excess <= 0) return 0;
386
+ for (let i = 0; i < excess; i++) {
387
+ unlinkSync(files[i]);
388
+ }
389
+ return excess;
390
+ }
391
+ function compressData(data) {
392
+ return gzipSync(Buffer.from(data, "utf-8"));
393
+ }
394
+ function decompressData(data) {
395
+ return gunzipSync(data).toString("utf-8");
396
+ }
338
397
  function watch() {
339
398
  console.log("\n\u{1F9E0} toon-memory watch\n");
340
399
  const memoryFile = join(projectRoot, ".opencode", "memory", "data.toon");
@@ -344,30 +403,68 @@ function watch() {
344
403
  return;
345
404
  }
346
405
  if (!existsSync(backupDir)) mkdirSync(backupDir, { recursive: true });
347
- const intervalMinutes = parseInt(args[1]) || 5;
348
- console.log(`Watching memory file every ${intervalMinutes} minutes...`);
406
+ const opts = parseWatchOptions(args);
407
+ if (opts.logFile) {
408
+ const logDir = dirname(opts.logPath);
409
+ if (!existsSync(logDir)) mkdirSync(logDir, { recursive: true });
410
+ }
411
+ console.log(`Watching memory file every ${opts.interval} minutes...`);
412
+ console.log(`Max backups: ${opts.maxBackups === 0 ? "unlimited" : opts.maxBackups}`);
413
+ console.log(`Compression: ${opts.compress ? "enabled" : "disabled"}`);
414
+ console.log(`Logging: ${opts.logFile ? `enabled (${opts.logPath})` : "disabled"}`);
349
415
  console.log(`Press Ctrl+C to stop
350
416
  `);
351
417
  let lastContent = readFileSync(memoryFile, "utf-8");
418
+ let lastHash = hashContent(lastContent);
352
419
  let backupCount = 0;
420
+ if (opts.logFile) writeWatchLog(opts.logPath, "Watch started");
353
421
  const backup = () => {
354
- const currentContent = readFileSync(memoryFile, "utf-8");
355
- if (currentContent !== lastContent) {
356
- const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
357
- const backupFile = join(backupDir, `backup-${timestamp}.toon`);
358
- writeFileSync(backupFile, currentContent);
359
- backupCount++;
360
- console.log(`\u{1F4E6} Backup #${backupCount} created: ${timestamp}`);
361
- lastContent = currentContent;
422
+ try {
423
+ const currentContent = readFileSync(memoryFile, "utf-8");
424
+ const currentHash = hashContent(currentContent);
425
+ if (currentHash !== lastHash) {
426
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
427
+ const ext = opts.compress ? ".toon.gz" : ".toon";
428
+ const backupFile = join(backupDir, `backup-${timestamp}${ext}`);
429
+ if (opts.compress) {
430
+ writeFileSync(backupFile, compressData(currentContent));
431
+ } else {
432
+ writeFileSync(backupFile, currentContent);
433
+ }
434
+ backupCount++;
435
+ console.log(`\u{1F4E6} Backup #${backupCount} created: ${timestamp}`);
436
+ if (opts.logFile) writeWatchLog(opts.logPath, `Backup #${backupCount}: ${timestamp}`);
437
+ lastContent = currentContent;
438
+ lastHash = currentHash;
439
+ const pruned = pruneBackups(backupDir, opts.maxBackups);
440
+ if (pruned > 0) {
441
+ console.log(`\u{1F5D1}\uFE0F Pruned ${pruned} old backup(s)`);
442
+ if (opts.logFile) writeWatchLog(opts.logPath, `Pruned ${pruned} old backup(s)`);
443
+ }
444
+ }
445
+ } catch (err) {
446
+ const msg = `Error creating backup: ${err.message}`;
447
+ console.error(`\u274C ${msg}`);
448
+ if (opts.logFile) writeWatchLog(opts.logPath, msg);
362
449
  }
363
450
  };
451
+ function hashContent(content) {
452
+ let hash = 0;
453
+ for (let i = 0; i < content.length; i++) {
454
+ const char = content.charCodeAt(i);
455
+ hash = (hash << 5) - hash + char;
456
+ hash = hash & hash;
457
+ }
458
+ return hash.toString(36);
459
+ }
364
460
  backup();
365
- const interval = setInterval(backup, intervalMinutes * 60 * 1e3);
461
+ const interval = setInterval(backup, opts.interval * 60 * 1e3);
366
462
  process.on("SIGINT", () => {
367
463
  clearInterval(interval);
368
464
  console.log(`
369
465
  \u2705 Watch stopped. ${backupCount} backups created.
370
466
  `);
467
+ if (opts.logFile) writeWatchLog(opts.logPath, `Watch stopped. ${backupCount} backups created.`);
371
468
  process.exit(0);
372
469
  });
373
470
  process.on("SIGTERM", () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toon-memory",
3
- "version": "1.5.0",
3
+ "version": "1.6.1",
4
4
  "description": "Persistent memory system for AI coding agents using TOON format (40% fewer tokens than JSON)",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli/setup.ts CHANGED
@@ -1,9 +1,10 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync, cpSync, unlinkSync } from "fs"
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, cpSync, unlinkSync, readdirSync, statSync } from "fs"
2
2
  import { basename, dirname, join } from "path"
3
3
  import { fileURLToPath } from "url"
4
4
  import { execSync } from "child_process"
5
5
  import { createInterface } from "readline"
6
6
  import { createRequire } from "module"
7
+ import { gzipSync, gunzipSync } from "zlib"
7
8
 
8
9
  const __dirname = dirname(fileURLToPath(import.meta.url))
9
10
  const projectRoot = process.cwd()
@@ -12,10 +13,15 @@ const projectRoot = process.cwd()
12
13
  const sourceDir = join(__dirname, "..", "..", "src")
13
14
  const HOME = process.env.HOME || process.env.USERPROFILE || "~"
14
15
 
16
+ /** Supported AI coding agent configuration */
15
17
  interface Agent {
18
+ /** Agent identifier (e.g., "opencode", "vscode/copilot") */
16
19
  name: string
20
+ /** Global config file path (e.g., ~/.config/opencode/opencode.json) */
17
21
  global?: string
22
+ /** Local (project-level) config file path (e.g., .opencode/opencode.json) */
18
23
  local?: string
24
+ /** JSON key where MCP servers are stored */
19
25
  mcpKey: string
20
26
  }
21
27
 
@@ -27,7 +33,22 @@ try {
27
33
  execSync("npm install @toon-format/toon", { cwd: projectRoot, stdio: "inherit" })
28
34
  }
29
35
 
30
- // Detect all supported agents
36
+ /**
37
+ * Detect all supported AI coding agents on the system.
38
+ *
39
+ * Scans for configuration files in both global (~/.config/) and local
40
+ * (.opencode/, .vscode/, etc.) locations.
41
+ *
42
+ * @returns Array of detected agent configurations
43
+ *
44
+ * @example
45
+ * ```typescript
46
+ * const agents = detectAgents()
47
+ * for (const agent of agents) {
48
+ * console.log(`${agent.name}: ${agent.local || "not found"}`)
49
+ * }
50
+ * ```
51
+ */
31
52
  function detectAgents(): Agent[] {
32
53
  const agents: Agent[] = []
33
54
 
@@ -94,7 +115,17 @@ function detectAgents(): Agent[] {
94
115
  return agents
95
116
  }
96
117
 
97
- // Install custom tools for OpenCode
118
+ /**
119
+ * Install custom tools and memory directory for OpenCode.
120
+ *
121
+ * Creates `.opencode/tools/` and `.opencode/memory/` directories,
122
+ * copies `memory.ts` tool, and creates initial `data.toon` if needed.
123
+ *
124
+ * @example
125
+ * ```bash
126
+ * npx toon-memory init # Calls installOpenCodeTools()
127
+ * ```
128
+ */
98
129
  function installOpenCodeTools(): void {
99
130
  const toolsDir = join(projectRoot, ".opencode", "tools")
100
131
  const memoryDir = join(projectRoot, ".opencode", "memory")
@@ -112,7 +143,17 @@ function installOpenCodeTools(): void {
112
143
  }
113
144
  }
114
145
 
115
- // Add .opencode/memory/ to .gitignore if not present
146
+ /**
147
+ * Add `.opencode/memory/` to `.gitignore` if not already present.
148
+ *
149
+ * Creates `.gitignore` if it doesn't exist, or appends the memory
150
+ * exclusion pattern to the existing file.
151
+ *
152
+ * @example
153
+ * ```typescript
154
+ * ensureGitignore() // Adds .opencode/memory/ to .gitignore
155
+ * ```
156
+ */
116
157
  function ensureGitignore(): void {
117
158
  const gitignorePath = join(projectRoot, ".gitignore")
118
159
  const entry = ".opencode/memory/"
@@ -130,7 +171,20 @@ function ensureGitignore(): void {
130
171
  }
131
172
  }
132
173
 
133
- // Install MCP server config for different agents
174
+ /**
175
+ * Install MCP server configuration for an agent.
176
+ *
177
+ * Adds the `toon-memory` MCP server entry to the agent's config file.
178
+ *
179
+ * @param agent - Agent configuration with config path and MCP key
180
+ * @param scope - "global" or "local" installation scope
181
+ *
182
+ * @example
183
+ * ```typescript
184
+ * const agent = { name: "opencode", local: ".opencode/opencode.json", mcpKey: "mcp" }
185
+ * installMCPConfig(agent, "local")
186
+ * ```
187
+ */
134
188
  function installMCPConfig(agent: Agent, scope: string): void {
135
189
  const configPath = scope === "global" ? agent.global : agent.local
136
190
 
@@ -154,16 +208,34 @@ function installMCPConfig(agent: Agent, scope: string): void {
154
208
  const mcpKey = agent.mcpKey || "mcpServers"
155
209
  if (!config[mcpKey]) config[mcpKey] = {}
156
210
 
157
- config[mcpKey]["toon-memory"] = {
158
- command: "npx",
159
- args: ["-y", "toon-memory", "mcp"]
211
+ // OpenCode uses a different format than other agents
212
+ if (agent.name === "opencode") {
213
+ config[mcpKey]["toon-memory"] = {
214
+ enabled: true,
215
+ type: "local",
216
+ command: ["npx", "-y", "toon-memory", "mcp"]
217
+ }
218
+ } else {
219
+ config[mcpKey]["toon-memory"] = {
220
+ command: "npx",
221
+ args: ["-y", "toon-memory", "mcp"]
222
+ }
160
223
  }
161
224
 
162
225
  writeFileSync(configPath, JSON.stringify(config, null, 2))
163
226
  console.log(` MCP server added to ${configPath}`)
164
227
  }
165
228
 
166
- // Uninstall from all agents
229
+ /**
230
+ * Uninstall toon-memory from all detected agents.
231
+ *
232
+ * Removes MCP server configurations and custom tools from all agents.
233
+ *
234
+ * @example
235
+ * ```bash
236
+ * npx toon-memory uninstall
237
+ * ```
238
+ */
167
239
  function uninstall(): void {
168
240
  console.log("\n🧠 toon-memory uninstaller\n")
169
241
 
@@ -198,7 +270,20 @@ function uninstall(): void {
198
270
  console.log("\n✅ toon-memory uninstalled from all agents\n")
199
271
  }
200
272
 
201
- // Quick init without interactive prompts
273
+ /**
274
+ * Initialize toon-memory for all detected agents (non-interactive).
275
+ *
276
+ * Installs MCP server configs, creates memory directory, and
277
+ * updates `.gitignore`.
278
+ *
279
+ * @param scope - "local" (default) or "global" installation scope
280
+ *
281
+ * @example
282
+ * ```bash
283
+ * npx toon-memory init # Local install
284
+ * npx toon-memory init global # Global install
285
+ * ```
286
+ */
202
287
  function init(scope: string = "local"): void {
203
288
  console.log("\n🧠 toon-memory init\n")
204
289
 
@@ -220,7 +305,16 @@ function init(scope: string = "local"): void {
220
305
  console.log("Done! Restart your agent to use memory tools.\n")
221
306
  }
222
307
 
223
- // Show installation status
308
+ /**
309
+ * Show toon-memory installation status.
310
+ *
311
+ * Displays version, memory entry count, and agent configuration status.
312
+ *
313
+ * @example
314
+ * ```bash
315
+ * npx toon-memory status
316
+ * ```
317
+ */
224
318
  function status(): void {
225
319
  console.log("\n🧠 toon-memory status\n")
226
320
 
@@ -272,7 +366,16 @@ function status(): void {
272
366
  console.log("")
273
367
  }
274
368
 
275
- // Upgrade to latest version
369
+ /**
370
+ * Upgrade toon-memory to the latest version.
371
+ *
372
+ * Checks npm registry for updates and installs if available.
373
+ *
374
+ * @example
375
+ * ```bash
376
+ * npx toon-memory upgrade
377
+ * ```
378
+ */
276
379
  function upgrade(): void {
277
380
  console.log("\n🧠 toon-memory upgrade\n")
278
381
 
@@ -291,7 +394,16 @@ function upgrade(): void {
291
394
  }
292
395
  }
293
396
 
294
- // Show memory statistics
397
+ /**
398
+ * Display memory statistics.
399
+ *
400
+ * Shows entry counts by category, last update date, and file size.
401
+ *
402
+ * @example
403
+ * ```bash
404
+ * npx toon-memory stats
405
+ * ```
406
+ */
295
407
  function stats(): void {
296
408
  console.log("\n🧠 toon-memory stats\n")
297
409
 
@@ -333,7 +445,17 @@ function stats(): void {
333
445
  console.log("")
334
446
  }
335
447
 
336
- // Export memory to JSON
448
+ /**
449
+ * Export memory to JSON format.
450
+ *
451
+ * Creates a `toon-memory-export.json` file with all entries
452
+ * for backup or transfer to another project.
453
+ *
454
+ * @example
455
+ * ```bash
456
+ * npx toon-memory export
457
+ * ```
458
+ */
337
459
  function exportMemory(): void {
338
460
  console.log("\n🧠 toon-memory export\n")
339
461
 
@@ -374,7 +496,18 @@ function exportMemory(): void {
374
496
  console.log(` ${outputPath}\n`)
375
497
  }
376
498
 
377
- // Import memory from JSON
499
+ /**
500
+ * Import memory from JSON file.
501
+ *
502
+ * Imports entries from a JSON export, skipping duplicates based on key.
503
+ *
504
+ * @param file - Path to JSON file (relative or absolute)
505
+ *
506
+ * @example
507
+ * ```bash
508
+ * npx toon-memory import toon-memory-export.json
509
+ * ```
510
+ */
378
511
  function importMemory(): void {
379
512
  console.log("\n🧠 toon-memory import\n")
380
513
 
@@ -446,7 +579,104 @@ function importMemory(): void {
446
579
  console.log(`Skipped ${importData.entries.length - newEntries.length} duplicates\n`)
447
580
  }
448
581
 
449
- // Watch mode - backup memory every N minutes
582
+ /** Watch mode options */
583
+ interface WatchOptions {
584
+ /** Backup interval in minutes (default: 5) */
585
+ interval: number
586
+ /** Maximum number of backups to keep (0 = unlimited) */
587
+ maxBackups: number
588
+ /** Enable gzip compression for backups */
589
+ compress: boolean
590
+ /** Enable file logging */
591
+ logFile: boolean
592
+ /** Log file path */
593
+ logPath: string
594
+ }
595
+
596
+ /** Parse watch CLI arguments into WatchOptions */
597
+ function parseWatchOptions(args: string[]): WatchOptions {
598
+ const opts: WatchOptions = {
599
+ interval: 5,
600
+ maxBackups: 10,
601
+ compress: false,
602
+ logFile: false,
603
+ logPath: ""
604
+ }
605
+
606
+ for (let i = 1; i < args.length; i++) {
607
+ const arg = args[i]
608
+ if (arg === "--compress" || arg === "-c") {
609
+ opts.compress = true
610
+ } else if (arg === "--log" || arg === "-l") {
611
+ opts.logFile = true
612
+ opts.logPath = args[++i] || join(projectRoot, ".opencode", "memory", "watch.log")
613
+ } else if (arg === "--max-backups" || arg === "-m") {
614
+ opts.maxBackups = parseInt(args[++i]) || 10
615
+ } else if (!arg.startsWith("-")) {
616
+ opts.interval = parseInt(arg) || 5
617
+ }
618
+ }
619
+
620
+ return opts
621
+ }
622
+
623
+ /** Write a line to the watch log file */
624
+ function writeWatchLog(logPath: string, message: string): void {
625
+ if (!logPath) return
626
+ const timestamp = new Date().toISOString()
627
+ const logLine = `[${timestamp}] ${message}\n`
628
+ writeFileSync(logPath, logLine, { flag: "a" })
629
+ }
630
+
631
+ /** Get list of backup files sorted by creation time (oldest first) */
632
+ function getBackupFiles(backupDir: string): string[] {
633
+ if (!existsSync(backupDir)) return []
634
+
635
+ return readdirSync(backupDir)
636
+ .filter(f => f.startsWith("backup-") && (f.endsWith(".toon") || f.endsWith(".gz")))
637
+ .map(f => join(backupDir, f))
638
+ .sort((a, b) => statSync(a).mtimeMs - statSync(b).mtimeMs)
639
+ }
640
+
641
+ /** Remove oldest backups if we exceed maxBackups */
642
+ function pruneBackups(backupDir: string, maxBackups: number): number {
643
+ if (maxBackups <= 0) return 0
644
+
645
+ const files = getBackupFiles(backupDir)
646
+ const excess = files.length - maxBackups
647
+
648
+ if (excess <= 0) return 0
649
+
650
+ for (let i = 0; i < excess; i++) {
651
+ unlinkSync(files[i])
652
+ }
653
+
654
+ return excess
655
+ }
656
+
657
+ /** Compress content with gzip */
658
+ function compressData(data: string): Buffer {
659
+ return gzipSync(Buffer.from(data, "utf-8"))
660
+ }
661
+
662
+ /** Decompress gzip data to string */
663
+ function decompressData(data: Buffer): string {
664
+ return gunzipSync(data).toString("utf-8")
665
+ }
666
+
667
+ /**
668
+ * Watch mode - backup memory every N minutes
669
+ *
670
+ * @example
671
+ * ```bash
672
+ * npx toon-memory watch # Default: 5 min interval, 10 max backups
673
+ * npx toon-memory watch 10 # 10 minute interval
674
+ * npx toon-memory watch -c # Enable compression
675
+ * npx toon-memory watch -l # Enable file logging
676
+ * npx toon-memory watch -m 20 # Keep max 20 backups
677
+ * npx toon-memory watch 15 -c -l -m 5 # All options
678
+ * ```
679
+ */
450
680
  function watch(): void {
451
681
  console.log("\n🧠 toon-memory watch\n")
452
682
 
@@ -460,36 +690,83 @@ function watch(): void {
460
690
 
461
691
  if (!existsSync(backupDir)) mkdirSync(backupDir, { recursive: true })
462
692
 
463
- const intervalMinutes = parseInt(args[1]) || 5
464
- console.log(`Watching memory file every ${intervalMinutes} minutes...`)
693
+ const opts = parseWatchOptions(args)
694
+
695
+ if (opts.logFile) {
696
+ const logDir = dirname(opts.logPath)
697
+ if (!existsSync(logDir)) mkdirSync(logDir, { recursive: true })
698
+ }
699
+
700
+ console.log(`Watching memory file every ${opts.interval} minutes...`)
701
+ console.log(`Max backups: ${opts.maxBackups === 0 ? "unlimited" : opts.maxBackups}`)
702
+ console.log(`Compression: ${opts.compress ? "enabled" : "disabled"}`)
703
+ console.log(`Logging: ${opts.logFile ? `enabled (${opts.logPath})` : "disabled"}`)
465
704
  console.log(`Press Ctrl+C to stop\n`)
466
705
 
467
706
  let lastContent = readFileSync(memoryFile, "utf-8")
707
+ let lastHash = hashContent(lastContent)
468
708
  let backupCount = 0
469
709
 
710
+ if (opts.logFile) writeWatchLog(opts.logPath, "Watch started")
711
+
470
712
  const backup = () => {
471
- const currentContent = readFileSync(memoryFile, "utf-8")
472
-
473
- if (currentContent !== lastContent) {
474
- const timestamp = new Date().toISOString().replace(/[:.]/g, "-")
475
- const backupFile = join(backupDir, `backup-${timestamp}.toon`)
476
- writeFileSync(backupFile, currentContent)
477
- backupCount++
478
- console.log(`📦 Backup #${backupCount} created: ${timestamp}`)
479
- lastContent = currentContent
713
+ try {
714
+ const currentContent = readFileSync(memoryFile, "utf-8")
715
+ const currentHash = hashContent(currentContent)
716
+
717
+ if (currentHash !== lastHash) {
718
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-")
719
+ const ext = opts.compress ? ".toon.gz" : ".toon"
720
+ const backupFile = join(backupDir, `backup-${timestamp}${ext}`)
721
+
722
+ if (opts.compress) {
723
+ writeFileSync(backupFile, compressData(currentContent))
724
+ } else {
725
+ writeFileSync(backupFile, currentContent)
726
+ }
727
+
728
+ backupCount++
729
+ console.log(`📦 Backup #${backupCount} created: ${timestamp}`)
730
+ if (opts.logFile) writeWatchLog(opts.logPath, `Backup #${backupCount}: ${timestamp}`)
731
+
732
+ lastContent = currentContent
733
+ lastHash = currentHash
734
+
735
+ const pruned = pruneBackups(backupDir, opts.maxBackups)
736
+ if (pruned > 0) {
737
+ console.log(`🗑️ Pruned ${pruned} old backup(s)`)
738
+ if (opts.logFile) writeWatchLog(opts.logPath, `Pruned ${pruned} old backup(s)`)
739
+ }
740
+ }
741
+ } catch (err) {
742
+ const msg = `Error creating backup: ${(err as Error).message}`
743
+ console.error(`❌ ${msg}`)
744
+ if (opts.logFile) writeWatchLog(opts.logPath, msg)
745
+ }
746
+ }
747
+
748
+ /** Simple hash for change detection (not cryptographic) */
749
+ function hashContent(content: string): string {
750
+ let hash = 0
751
+ for (let i = 0; i < content.length; i++) {
752
+ const char = content.charCodeAt(i)
753
+ hash = ((hash << 5) - hash) + char
754
+ hash = hash & hash
480
755
  }
756
+ return hash.toString(36)
481
757
  }
482
758
 
483
759
  // Initial backup
484
760
  backup()
485
761
 
486
762
  // Set interval
487
- const interval = setInterval(backup, intervalMinutes * 60 * 1000)
763
+ const interval = setInterval(backup, opts.interval * 60 * 1000)
488
764
 
489
765
  // Handle Ctrl+C
490
766
  process.on("SIGINT", () => {
491
767
  clearInterval(interval)
492
768
  console.log(`\n✅ Watch stopped. ${backupCount} backups created.\n`)
769
+ if (opts.logFile) writeWatchLog(opts.logPath, `Watch stopped. ${backupCount} backups created.`)
493
770
  process.exit(0)
494
771
  })
495
772
 
package/src/mcp/server.ts CHANGED
@@ -7,19 +7,49 @@ import { fileURLToPath } from "url"
7
7
  import { randomBytes, createCipheriv, createDecipheriv } from "crypto"
8
8
 
9
9
  const __dirname = dirname(fileURLToPath(import.meta.url))
10
+
11
+ /** Base directory for memory storage */
10
12
  const MEMORY_DIR = join(process.cwd(), ".opencode", "memory")
13
+
14
+ /** Main memory data file */
11
15
  const MEMORY_FILE = join(MEMORY_DIR, "data.toon")
16
+
17
+ /** Archive file for old entries */
12
18
  const ARCHIVE_FILE = join(MEMORY_DIR, "archive.toon")
19
+
20
+ /** Configuration file for encryption settings */
13
21
  const CONFIG_FILE = join(MEMORY_DIR, "config.json")
22
+
23
+ /** Maximum active entries before auto-archive */
14
24
  const MAX_ENTRIES = 100
25
+
26
+ /** Days before entries are archived */
15
27
  const ARCHIVE_DAYS = 30
28
+
29
+ /** Encryption algorithm for AES-256-GCM */
16
30
  const ALGORITHM = "aes-256-gcm"
17
31
 
32
+ /** Memory configuration with encryption settings */
18
33
  interface MemoryConfig {
34
+ /** Whether encryption is enabled */
19
35
  encrypted: boolean
36
+ /** Encryption key (hex-encoded, 64 chars for AES-256) */
20
37
  key?: string
21
38
  }
22
39
 
40
+ /**
41
+ * Load memory configuration from config.json.
42
+ *
43
+ * @returns MemoryConfig object with encryption settings
44
+ *
45
+ * @example
46
+ * ```typescript
47
+ * const config = loadConfig()
48
+ * if (config.encrypted) {
49
+ * console.log("Encryption enabled")
50
+ * }
51
+ * ```
52
+ */
23
53
  function loadConfig(): MemoryConfig {
24
54
  if (!existsSync(CONFIG_FILE)) {
25
55
  return { encrypted: false }
@@ -31,22 +61,46 @@ function loadConfig(): MemoryConfig {
31
61
  }
32
62
  }
33
63
 
64
+ /**
65
+ * Save memory configuration to config.json.
66
+ *
67
+ * @param config - MemoryConfig to save
68
+ */
34
69
  function saveConfig(config: MemoryConfig): void {
35
70
  ensureMemoryDir()
36
71
  writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2))
37
72
  }
38
73
 
39
- function ensureMemoryDir() {
74
+ /**
75
+ * Ensure memory directory exists, creating it if necessary.
76
+ */
77
+ function ensureMemoryDir(): void {
40
78
  if (!existsSync(MEMORY_DIR)) mkdirSync(MEMORY_DIR, { recursive: true })
41
79
  }
42
80
 
43
- function ensureMemoryFile() {
81
+ /**
82
+ * Ensure memory file exists with default structure.
83
+ */
84
+ function ensureMemoryFile(): void {
44
85
  ensureMemoryDir()
45
86
  if (!existsSync(MEMORY_FILE)) {
46
87
  writeFileSync(MEMORY_FILE, "version: 1\nentries[0|]{id|category|key|content|file|tags|date}:\n")
47
88
  }
48
89
  }
49
90
 
91
+ /**
92
+ * Encrypt text using AES-256-GCM.
93
+ *
94
+ * @param text - Plain text to encrypt
95
+ * @param key - Hex-encoded encryption key (64 chars)
96
+ * @returns Encrypted string in format "iv:authTag:ciphertext"
97
+ *
98
+ * @example
99
+ * ```typescript
100
+ * const encrypted = encrypt("my secret", key)
101
+ * // "a1b2c3d4...:e5f6g7h8...:i9j0k1l2..."
102
+ * ```
103
+ */
50
104
  function encrypt(text: string, key: string): string {
51
105
  const keyBuffer = Buffer.from(key, "hex")
52
106
  const iv = randomBytes(16)
@@ -60,6 +114,14 @@ function encrypt(text: string, key: string): string {
60
114
  return `${iv.toString("hex")}:${authTag.toString("hex")}:${encrypted}`
61
115
  }
62
116
 
117
+ /**
118
+ * Decrypt AES-256-GCM encrypted data.
119
+ *
120
+ * @param encryptedData - Encrypted string in format "iv:authTag:ciphertext"
121
+ * @param key - Hex-encoded encryption key (64 chars)
122
+ * @returns Decrypted plain text
123
+ * @throws If key is invalid or data is corrupted
124
+ */
63
125
  function decrypt(encryptedData: string, key: string): string {
64
126
  const keyBuffer = Buffer.from(key, "hex")
65
127
  const [ivHex, authTagHex, encrypted] = encryptedData.split(":")
@@ -75,10 +137,20 @@ function decrypt(encryptedData: string, key: string): string {
75
137
  return decrypted
76
138
  }
77
139
 
140
+ /**
141
+ * Generate a random 256-bit encryption key.
142
+ *
143
+ * @returns Hex-encoded key (64 chars)
144
+ */
78
145
  function generateKey(): string {
79
146
  return randomBytes(32).toString("hex")
80
147
  }
81
148
 
149
+ /**
150
+ * Read memory file, decrypting if encryption is enabled.
151
+ *
152
+ * @returns Memory content as string (TOON format)
153
+ */
82
154
  function readMemory(): string {
83
155
  ensureMemoryFile()
84
156
  const config = loadConfig()
@@ -95,6 +167,11 @@ function readMemory(): string {
95
167
  return data
96
168
  }
97
169
 
170
+ /**
171
+ * Write content to memory file, encrypting if encryption is enabled.
172
+ *
173
+ * @param content - Memory content to write (TOON format)
174
+ */
98
175
  function writeMemory(content: string): void {
99
176
  ensureMemoryFile()
100
177
  const config = loadConfig()
@@ -107,10 +184,29 @@ function writeMemory(content: string): void {
107
184
  }
108
185
  }
109
186
 
187
+ /**
188
+ * Generate a random 8-character hex ID for memory entries.
189
+ *
190
+ * @returns Hex string (8 chars)
191
+ */
110
192
  function generateId(): string {
111
193
  return randomBytes(4).toString("hex")
112
194
  }
113
195
 
196
+ /**
197
+ * Archive entries older than ARCHIVE_DAYS to archive.toon.
198
+ *
199
+ * Moves old entries from data.toon to archive.toon to keep
200
+ * the active memory file manageable.
201
+ *
202
+ * @returns Object with counts of archived and kept entries
203
+ *
204
+ * @example
205
+ * ```typescript
206
+ * const result = archiveOldEntries()
207
+ * console.log(`Archived: ${result.archived}, Kept: ${result.kept}`)
208
+ * ```
209
+ */
114
210
  function archiveOldEntries(): { archived: number; kept: number } {
115
211
  const data = readMemory()
116
212
  const lines = data.split("\n")
@@ -181,6 +277,20 @@ const server = new McpServer(
181
277
  { capabilities: { tools: { listChanged: true } } }
182
278
  )
183
279
 
280
+ /**
281
+ * Register the memory_remember tool.
282
+ * Saves a fact to persistent memory (decisions, patterns, bugs, knowledge).
283
+ *
284
+ * @example
285
+ * ```typescript
286
+ * // From MCP tool call:
287
+ * await callTool("memory_remember", {
288
+ * category: "decision",
289
+ * key: "use-ts-for-mcp",
290
+ * content: "Using TypeScript for MCP server implementation"
291
+ * })
292
+ * ```
293
+ */
184
294
  server.registerTool(
185
295
  "memory_remember",
186
296
  {
@@ -220,6 +330,17 @@ server.registerTool(
220
330
  }
221
331
  )
222
332
 
333
+ /**
334
+ * Register the memory_recall tool.
335
+ * Search persistent memory for relevant entries by text, category, or date range.
336
+ *
337
+ * @example
338
+ * ```typescript
339
+ * // From MCP tool call:
340
+ * await callTool("memory_recall", { query: "risk engine" })
341
+ * await callTool("memory_recall", { query: "redis", category: "bug" })
342
+ * ```
343
+ */
223
344
  server.registerTool(
224
345
  "memory_recall",
225
346
  {
@@ -264,6 +385,15 @@ server.registerTool(
264
385
  }
265
386
  )
266
387
 
388
+ /**
389
+ * Register the memory_forget tool.
390
+ * Delete a memory entry by key or ID.
391
+ *
392
+ * @example
393
+ * ```typescript
394
+ * await callTool("memory_forget", { key: "old-decision" })
395
+ * ```
396
+ */
267
397
  server.registerTool(
268
398
  "memory_forget",
269
399
  {
@@ -301,6 +431,15 @@ server.registerTool(
301
431
  }
302
432
  )
303
433
 
434
+ /**
435
+ * Register the memory_stats tool.
436
+ * Display memory statistics including entry counts and categories.
437
+ *
438
+ * @example
439
+ * ```typescript
440
+ * await callTool("memory_stats", {})
441
+ * ```
442
+ */
304
443
  server.registerTool(
305
444
  "memory_stats",
306
445
  {
@@ -340,6 +479,26 @@ server.registerTool(
340
479
  }
341
480
  )
342
481
 
482
+ /**
483
+ * Register the memory_summary tool.
484
+ * Get or set file summaries to save tokens when reading large files.
485
+ *
486
+ * @example
487
+ * ```typescript
488
+ * // Set a summary
489
+ * await callTool("memory_summary", {
490
+ * action: "set",
491
+ * file: "src/complex.ts",
492
+ * summary: "Complex module handling X, Y, Z"
493
+ * })
494
+ *
495
+ * // Get a summary
496
+ * await callTool("memory_summary", {
497
+ * action: "get",
498
+ * file: "src/complex.ts"
499
+ * })
500
+ * ```
501
+ */
343
502
  server.registerTool(
344
503
  "memory_summary",
345
504
  {
@@ -396,6 +555,16 @@ server.registerTool(
396
555
  }
397
556
  )
398
557
 
558
+ /**
559
+ * Register the memory_archive tool.
560
+ * Archive old entries (>30 days) to archive.toon to keep memory clean.
561
+ *
562
+ * @example
563
+ * ```typescript
564
+ * await callTool("memory_archive", {})
565
+ * // "📦 Archivadas 5 entradas antiguas"
566
+ * ```
567
+ */
399
568
  server.registerTool(
400
569
  "memory_archive",
401
570
  {
@@ -419,6 +588,18 @@ server.registerTool(
419
588
  }
420
589
  )
421
590
 
591
+ /**
592
+ * Register the memory_encrypt tool.
593
+ * Enable AES-256-GCM encryption for the memory file.
594
+ *
595
+ * ⚠️ Warning: Save the encryption key! It cannot be recovered.
596
+ *
597
+ * @example
598
+ * ```typescript
599
+ * await callTool("memory_encrypt", {})
600
+ * // "🔐 Encriptación habilitada\n⚠️ Guarda esta clave: ..."
601
+ * ```
602
+ */
422
603
  server.registerTool(
423
604
  "memory_encrypt",
424
605
  {
@@ -450,6 +631,16 @@ server.registerTool(
450
631
  }
451
632
  )
452
633
 
634
+ /**
635
+ * Register the memory_decrypt tool.
636
+ * Disable encryption and decrypt the memory file.
637
+ *
638
+ * @example
639
+ * ```typescript
640
+ * await callTool("memory_decrypt", { key: "your-encryption-key" })
641
+ * // "🔓 Encriptación deshabilitada"
642
+ * ```
643
+ */
453
644
  server.registerTool(
454
645
  "memory_decrypt",
455
646
  {