toon-memory 1.4.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,16 +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
- - **6 MCP tools** — `memory_remember`, `memory_recall`, `memory_forget`, `memory_stats`, `memory_summary`, `memory_archive`
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
60
+ - **Encryption** — AES-256-GCM encryption for sensitive data
61
+ - **Watch mode** — Auto-backup every N minutes
33
62
 
34
63
  ---
35
64
 
@@ -81,7 +110,7 @@ memory_remember # Save important decisions
81
110
 
82
111
  ---
83
112
 
84
- ## Tools
113
+ ## MCP Tools
85
114
 
86
115
  | Tool | Description |
87
116
  |------|-------------|
@@ -91,13 +120,36 @@ memory_remember # Save important decisions
91
120
  | `memory_stats` | View memory state |
92
121
  | `memory_summary` | Save/retrieve file summaries |
93
122
  | `memory_archive` | Archive old entries (>30 days) |
123
+ | `memory_encrypt` | Enable AES-256-GCM encryption |
124
+ | `memory_decrypt` | Disable encryption |
125
+
126
+ ### Examples
94
127
 
95
- ### Date Filtering
128
+ #### Remember a decision
129
+
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
+ ```
96
149
 
97
- Search memory by date range:
150
+ #### Search with date filter
98
151
 
99
152
  ```typescript
100
- // Search with date filter
101
153
  memory_recall({
102
154
  query: "redis",
103
155
  from_date: "2026-07-01",
@@ -105,36 +157,21 @@ memory_recall({
105
157
  })
106
158
  ```
107
159
 
108
- ### Auto-Archive
109
-
110
- Entries older than 30 days are automatically archived to keep memory clean:
160
+ #### Archive old entries
111
161
 
112
162
  ```typescript
113
- // Manually trigger archiving
114
163
  memory_archive()
115
164
  // 📦 Archivadas 5 entradas antiguas
116
165
  // 📋 Quedan 42 entradas activas
117
166
  ```
118
167
 
119
- ---
120
-
121
- ## How It Works
122
-
123
- 1. **MCP Server** — runs locally, talks to your agent via stdio
124
- 2. **TOON Format** — stores data in Token-Oriented Object Notation (~40% fewer tokens than JSON)
125
- 3. **Per-project memory** — each project gets `.opencode/memory/data.toon`
126
- 4. **Zero config** — just install and use
168
+ #### Enable encryption
127
169
 
128
- ### Memory File Format
129
-
130
- ```
131
- version: 1
132
- entries[3|]{id|category|key|content|file|tags|date}:
133
- a1b2c3d4|decision|use-zod|Use Zod for validation|src/types.ts|validation;types|2026-07-10
134
- e5f6g7h8|pattern|pydantic-configs|Project uses Pydantic v2|config.py|python;patterns|2026-07-10
135
- i9j0k1l2|bug|redis-pool-fix|Added max_connections=20|redis.ts|redis;fix|2026-07-10
136
- summaries:
137
- src/services/redis.ts: Redis connection pool with retry logic
170
+ ```typescript
171
+ memory_encrypt()
172
+ // 🔐 Encriptación habilitada
173
+ // ⚠️ Guarda esta clave (no se puede recuperar):
174
+ // a1b2c3d4...
138
175
  ```
139
176
 
140
177
  ---
@@ -149,11 +186,14 @@ npx toon-memory status # Check installation status
149
186
  npx toon-memory stats # View memory statistics
150
187
  npx toon-memory export # Export memory to JSON
151
188
  npx toon-memory import <file> # Import memory from JSON
189
+ npx toon-memory watch [options] # Auto-backup with options
152
190
  npx toon-memory upgrade # Update to latest version
153
191
  npx toon-memory uninstall # Remove from all agents
154
192
  ```
155
193
 
156
- ### Stats
194
+ ### Examples
195
+
196
+ #### Stats
157
197
 
158
198
  ```bash
159
199
  $ npx toon-memory stats
@@ -171,7 +211,7 @@ Last updated: 2026-07-10
171
211
  File size: 12.4 KB
172
212
  ```
173
213
 
174
- ### Export
214
+ #### Export
175
215
 
176
216
  ```bash
177
217
  $ npx toon-memory export
@@ -182,7 +222,7 @@ Exported 45 entries to:
182
222
  /path/to/project/toon-memory-export.json
183
223
  ```
184
224
 
185
- ### Import
225
+ #### Import
186
226
 
187
227
  ```bash
188
228
  $ npx toon-memory import backup.json
@@ -193,6 +233,34 @@ Imported 3 new entries
193
233
  Skipped 2 duplicates
194
234
  ```
195
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
+
196
264
  ---
197
265
 
198
266
  ## Configuration
@@ -246,6 +314,41 @@ Add to `.vscode/mcp.json`:
246
314
 
247
315
  ---
248
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
+
249
352
  ## Why TOON?
250
353
 
251
354
  TOON (Token-Oriented Object Notation) is designed for LLMs:
@@ -257,8 +360,8 @@ TOON (Token-Oriented Object Notation) is designed for LLMs:
257
360
  | **TOON** | **60** | **76.4%** |
258
361
 
259
362
  - **40% fewer tokens** than JSON
260
- - **Lossless roundtrip** — no data loss
261
- - **Better LLM comprehension** — structured for AI consumption
363
+ - **Lossless roundtrip** — No data loss
364
+ - **Better LLM comprehension** — Structured for AI consumption
262
365
 
263
366
  ---
264
367
 
@@ -272,6 +375,30 @@ npm run build
272
375
  npm test
273
376
  ```
274
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
+
275
402
  ---
276
403
 
277
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,134 @@ ${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
+ }
397
+ function watch() {
398
+ console.log("\n\u{1F9E0} toon-memory watch\n");
399
+ const memoryFile = join(projectRoot, ".opencode", "memory", "data.toon");
400
+ const backupDir = join(projectRoot, ".opencode", "memory", "backups");
401
+ if (!existsSync(memoryFile)) {
402
+ console.log("Memory not initialized. Run 'npx toon-memory init' first.\n");
403
+ return;
404
+ }
405
+ if (!existsSync(backupDir)) mkdirSync(backupDir, { recursive: true });
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"}`);
415
+ console.log(`Press Ctrl+C to stop
416
+ `);
417
+ let lastContent = readFileSync(memoryFile, "utf-8");
418
+ let lastHash = hashContent(lastContent);
419
+ let backupCount = 0;
420
+ if (opts.logFile) writeWatchLog(opts.logPath, "Watch started");
421
+ const backup = () => {
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);
449
+ }
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
+ }
460
+ backup();
461
+ const interval = setInterval(backup, opts.interval * 60 * 1e3);
462
+ process.on("SIGINT", () => {
463
+ clearInterval(interval);
464
+ console.log(`
465
+ \u2705 Watch stopped. ${backupCount} backups created.
466
+ `);
467
+ if (opts.logFile) writeWatchLog(opts.logPath, `Watch stopped. ${backupCount} backups created.`);
468
+ process.exit(0);
469
+ });
470
+ process.on("SIGTERM", () => {
471
+ clearInterval(interval);
472
+ process.exit(0);
473
+ });
474
+ }
338
475
  const args = process.argv.slice(2);
339
476
  if (args[0] === "uninstall") {
340
477
  uninstall();
@@ -364,6 +501,10 @@ if (args[0] === "import") {
364
501
  importMemory();
365
502
  process.exit(0);
366
503
  }
504
+ if (args[0] === "watch") {
505
+ watch();
506
+ process.exit(0);
507
+ }
367
508
  const agents = detectAgents();
368
509
  console.log("\n\u{1F9E0} toon-memory installer\n");
369
510
  console.log("Supported agents:");
package/mcp/server.js CHANGED
@@ -27642,25 +27642,82 @@ var StdioServerTransport = class {
27642
27642
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
27643
27643
  import { dirname, join } from "path";
27644
27644
  import { fileURLToPath } from "url";
27645
- import { randomBytes } from "crypto";
27645
+ import { randomBytes, createCipheriv, createDecipheriv } from "crypto";
27646
27646
  var __dirname = dirname(fileURLToPath(import.meta.url));
27647
27647
  var MEMORY_DIR = join(process.cwd(), ".opencode", "memory");
27648
27648
  var MEMORY_FILE = join(MEMORY_DIR, "data.toon");
27649
27649
  var ARCHIVE_FILE = join(MEMORY_DIR, "archive.toon");
27650
+ var CONFIG_FILE = join(MEMORY_DIR, "config.json");
27650
27651
  var ARCHIVE_DAYS = 30;
27651
- function ensureMemoryFile() {
27652
+ var ALGORITHM = "aes-256-gcm";
27653
+ function loadConfig() {
27654
+ if (!existsSync(CONFIG_FILE)) {
27655
+ return { encrypted: false };
27656
+ }
27657
+ try {
27658
+ return JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
27659
+ } catch {
27660
+ return { encrypted: false };
27661
+ }
27662
+ }
27663
+ function saveConfig(config2) {
27664
+ ensureMemoryDir();
27665
+ writeFileSync(CONFIG_FILE, JSON.stringify(config2, null, 2));
27666
+ }
27667
+ function ensureMemoryDir() {
27652
27668
  if (!existsSync(MEMORY_DIR)) mkdirSync(MEMORY_DIR, { recursive: true });
27669
+ }
27670
+ function ensureMemoryFile() {
27671
+ ensureMemoryDir();
27653
27672
  if (!existsSync(MEMORY_FILE)) {
27654
27673
  writeFileSync(MEMORY_FILE, "version: 1\nentries[0|]{id|category|key|content|file|tags|date}:\n");
27655
27674
  }
27656
27675
  }
27676
+ function encrypt(text, key) {
27677
+ const keyBuffer = Buffer.from(key, "hex");
27678
+ const iv = randomBytes(16);
27679
+ const cipher = createCipheriv(ALGORITHM, keyBuffer, iv);
27680
+ let encrypted = cipher.update(text, "utf8", "hex");
27681
+ encrypted += cipher.final("hex");
27682
+ const authTag = cipher.getAuthTag();
27683
+ return `${iv.toString("hex")}:${authTag.toString("hex")}:${encrypted}`;
27684
+ }
27685
+ function decrypt(encryptedData, key) {
27686
+ const keyBuffer = Buffer.from(key, "hex");
27687
+ const [ivHex, authTagHex, encrypted] = encryptedData.split(":");
27688
+ const iv = Buffer.from(ivHex, "hex");
27689
+ const authTag = Buffer.from(authTagHex, "hex");
27690
+ const decipher = createDecipheriv(ALGORITHM, keyBuffer, iv);
27691
+ decipher.setAuthTag(authTag);
27692
+ let decrypted = decipher.update(encrypted, "hex", "utf8");
27693
+ decrypted += decipher.final("utf8");
27694
+ return decrypted;
27695
+ }
27696
+ function generateKey() {
27697
+ return randomBytes(32).toString("hex");
27698
+ }
27657
27699
  function readMemory() {
27658
27700
  ensureMemoryFile();
27659
- return readFileSync(MEMORY_FILE, "utf-8");
27701
+ const config2 = loadConfig();
27702
+ const data = readFileSync(MEMORY_FILE, "utf-8");
27703
+ if (config2.encrypted && config2.key) {
27704
+ try {
27705
+ return decrypt(data, config2.key);
27706
+ } catch {
27707
+ return data;
27708
+ }
27709
+ }
27710
+ return data;
27660
27711
  }
27661
27712
  function writeMemory(content) {
27662
27713
  ensureMemoryFile();
27663
- writeFileSync(MEMORY_FILE, content);
27714
+ const config2 = loadConfig();
27715
+ if (config2.encrypted && config2.key) {
27716
+ const encrypted = encrypt(content, config2.key);
27717
+ writeFileSync(MEMORY_FILE, encrypted);
27718
+ } else {
27719
+ writeFileSync(MEMORY_FILE, content);
27720
+ }
27664
27721
  }
27665
27722
  function generateId() {
27666
27723
  return randomBytes(4).toString("hex");
@@ -27928,6 +27985,60 @@ server.registerTool(
27928
27985
  };
27929
27986
  }
27930
27987
  );
27988
+ server.registerTool(
27989
+ "memory_encrypt",
27990
+ {
27991
+ title: "Enable Encryption",
27992
+ description: "Habilita encriptaci\xF3n AES-256-GCM para la memoria. La clave se genera autom\xE1ticamente.",
27993
+ inputSchema: {}
27994
+ },
27995
+ async () => {
27996
+ const config2 = loadConfig();
27997
+ if (config2.encrypted) {
27998
+ return { content: [{ type: "text", text: "La encriptaci\xF3n ya est\xE1 habilitada" }] };
27999
+ }
28000
+ const key = generateKey();
28001
+ const data = readFileSync(MEMORY_FILE, "utf-8");
28002
+ const encrypted = encrypt(data, key);
28003
+ writeFileSync(MEMORY_FILE, encrypted);
28004
+ saveConfig({ encrypted: true, key });
28005
+ return {
28006
+ content: [{
28007
+ type: "text",
28008
+ text: `\u{1F510} Encriptaci\xF3n habilitada
28009
+ \u26A0\uFE0F Guarda esta clave (no se puede recuperar):
28010
+ ${key}`
28011
+ }]
28012
+ };
28013
+ }
28014
+ );
28015
+ server.registerTool(
28016
+ "memory_decrypt",
28017
+ {
28018
+ title: "Disable Encryption",
28019
+ description: "Deshabilita la encriptaci\xF3n y decodifica la memoria.",
28020
+ inputSchema: {
28021
+ key: external_exports.string().describe("Clave de encriptaci\xF3n")
28022
+ }
28023
+ },
28024
+ async ({ key }) => {
28025
+ const config2 = loadConfig();
28026
+ if (!config2.encrypted) {
28027
+ return { content: [{ type: "text", text: "La encriptaci\xF3n no est\xE1 habilitada" }] };
28028
+ }
28029
+ try {
28030
+ const data = readFileSync(MEMORY_FILE, "utf-8");
28031
+ const decrypted = decrypt(data, key);
28032
+ writeFileSync(MEMORY_FILE, decrypted);
28033
+ saveConfig({ encrypted: false });
28034
+ return {
28035
+ content: [{ type: "text", text: "\u{1F513} Encriptaci\xF3n deshabilitada" }]
28036
+ };
28037
+ } catch {
28038
+ return { content: [{ type: "text", text: "\u274C Clave incorrecta o datos corruptos" }] };
28039
+ }
28040
+ }
28041
+ );
27931
28042
  var transport = new StdioServerTransport();
27932
28043
  await server.connect(transport);
27933
28044
  /*! Bundled license information:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toon-memory",
3
- "version": "1.4.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": {