toon-memory 1.5.0 → 1.6.2
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 +157 -51
- package/bin/toon-memory.js +0 -0
- package/dist/cli/setup.js +114 -21
- package/mcp/server.js +11 -11
- package/package.json +1 -1
- package/src/cli/setup.ts +306 -34
- package/src/mcp/server.ts +204 -13
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
|
[](https://www.npmjs.com/package/toon-memory)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
7
|
[](https://github.com/LuiggiVal08/toon-memory/actions/workflows/ci.yml)
|
|
8
|
+
[](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** —
|
|
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** —
|
|
29
|
-
- **Zero config** —
|
|
30
|
-
- **Auto gitignore** —
|
|
31
|
-
- **Date filtering** —
|
|
32
|
-
- **Auto-archive** —
|
|
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** —
|
|
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
|
-
###
|
|
126
|
+
### Examples
|
|
127
|
+
|
|
128
|
+
#### Remember a decision
|
|
100
129
|
|
|
101
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 [
|
|
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
|
-
###
|
|
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
|
-
|
|
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
|
-
|
|
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** —
|
|
282
|
-
- **Better LLM comprehension** —
|
|
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
|
package/bin/toon-memory.js
CHANGED
|
File without changes
|
package/dist/cli/setup.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync,
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, 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");
|
|
@@ -65,15 +66,11 @@ function detectAgents() {
|
|
|
65
66
|
return agents2;
|
|
66
67
|
}
|
|
67
68
|
function installOpenCodeTools() {
|
|
68
|
-
const toolsDir = join(projectRoot, ".opencode", "tools");
|
|
69
69
|
const memoryDir = join(projectRoot, ".opencode", "memory");
|
|
70
70
|
const memoryFile = join(memoryDir, "data.toon");
|
|
71
|
-
if (!existsSync(toolsDir)) mkdirSync(toolsDir, { recursive: true });
|
|
72
71
|
if (!existsSync(memoryDir)) mkdirSync(memoryDir, { recursive: true });
|
|
73
|
-
cpSync(join(sourceDir, "memory.ts"), join(toolsDir, "memory.ts"));
|
|
74
|
-
console.log(" Copied memory.ts to .opencode/tools/");
|
|
75
72
|
if (!existsSync(memoryFile)) {
|
|
76
|
-
writeFileSync(memoryFile, "version: 1\
|
|
73
|
+
writeFileSync(memoryFile, "version: 1\n[0|]\n");
|
|
77
74
|
console.log(" Created .opencode/memory/data.toon");
|
|
78
75
|
}
|
|
79
76
|
}
|
|
@@ -112,10 +109,18 @@ function installMCPConfig(agent, scope) {
|
|
|
112
109
|
}
|
|
113
110
|
const mcpKey = agent.mcpKey || "mcpServers";
|
|
114
111
|
if (!config[mcpKey]) config[mcpKey] = {};
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
112
|
+
if (agent.name === "opencode") {
|
|
113
|
+
config[mcpKey]["toon-memory"] = {
|
|
114
|
+
enabled: true,
|
|
115
|
+
type: "local",
|
|
116
|
+
command: ["npx", "-y", "toon-memory", "mcp"]
|
|
117
|
+
};
|
|
118
|
+
} else {
|
|
119
|
+
config[mcpKey]["toon-memory"] = {
|
|
120
|
+
command: "npx",
|
|
121
|
+
args: ["-y", "toon-memory", "mcp"]
|
|
122
|
+
};
|
|
123
|
+
}
|
|
119
124
|
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
120
125
|
console.log(` MCP server added to ${configPath}`);
|
|
121
126
|
}
|
|
@@ -335,6 +340,56 @@ ${newLines}
|
|
|
335
340
|
console.log(`Skipped ${importData.entries.length - newEntries.length} duplicates
|
|
336
341
|
`);
|
|
337
342
|
}
|
|
343
|
+
function parseWatchOptions(args2) {
|
|
344
|
+
const opts = {
|
|
345
|
+
interval: 5,
|
|
346
|
+
maxBackups: 10,
|
|
347
|
+
compress: false,
|
|
348
|
+
logFile: false,
|
|
349
|
+
logPath: ""
|
|
350
|
+
};
|
|
351
|
+
for (let i = 1; i < args2.length; i++) {
|
|
352
|
+
const arg = args2[i];
|
|
353
|
+
if (arg === "--compress" || arg === "-c") {
|
|
354
|
+
opts.compress = true;
|
|
355
|
+
} else if (arg === "--log" || arg === "-l") {
|
|
356
|
+
opts.logFile = true;
|
|
357
|
+
opts.logPath = args2[++i] || join(projectRoot, ".opencode", "memory", "watch.log");
|
|
358
|
+
} else if (arg === "--max-backups" || arg === "-m") {
|
|
359
|
+
opts.maxBackups = parseInt(args2[++i]) || 10;
|
|
360
|
+
} else if (!arg.startsWith("-")) {
|
|
361
|
+
opts.interval = parseInt(arg) || 5;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
return opts;
|
|
365
|
+
}
|
|
366
|
+
function writeWatchLog(logPath, message) {
|
|
367
|
+
if (!logPath) return;
|
|
368
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
369
|
+
const logLine = `[${timestamp}] ${message}
|
|
370
|
+
`;
|
|
371
|
+
writeFileSync(logPath, logLine, { flag: "a" });
|
|
372
|
+
}
|
|
373
|
+
function getBackupFiles(backupDir) {
|
|
374
|
+
if (!existsSync(backupDir)) return [];
|
|
375
|
+
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);
|
|
376
|
+
}
|
|
377
|
+
function pruneBackups(backupDir, maxBackups) {
|
|
378
|
+
if (maxBackups <= 0) return 0;
|
|
379
|
+
const files = getBackupFiles(backupDir);
|
|
380
|
+
const excess = files.length - maxBackups;
|
|
381
|
+
if (excess <= 0) return 0;
|
|
382
|
+
for (let i = 0; i < excess; i++) {
|
|
383
|
+
unlinkSync(files[i]);
|
|
384
|
+
}
|
|
385
|
+
return excess;
|
|
386
|
+
}
|
|
387
|
+
function compressData(data) {
|
|
388
|
+
return gzipSync(Buffer.from(data, "utf-8"));
|
|
389
|
+
}
|
|
390
|
+
function decompressData(data) {
|
|
391
|
+
return gunzipSync(data).toString("utf-8");
|
|
392
|
+
}
|
|
338
393
|
function watch() {
|
|
339
394
|
console.log("\n\u{1F9E0} toon-memory watch\n");
|
|
340
395
|
const memoryFile = join(projectRoot, ".opencode", "memory", "data.toon");
|
|
@@ -344,30 +399,68 @@ function watch() {
|
|
|
344
399
|
return;
|
|
345
400
|
}
|
|
346
401
|
if (!existsSync(backupDir)) mkdirSync(backupDir, { recursive: true });
|
|
347
|
-
const
|
|
348
|
-
|
|
402
|
+
const opts = parseWatchOptions(args);
|
|
403
|
+
if (opts.logFile) {
|
|
404
|
+
const logDir = dirname(opts.logPath);
|
|
405
|
+
if (!existsSync(logDir)) mkdirSync(logDir, { recursive: true });
|
|
406
|
+
}
|
|
407
|
+
console.log(`Watching memory file every ${opts.interval} minutes...`);
|
|
408
|
+
console.log(`Max backups: ${opts.maxBackups === 0 ? "unlimited" : opts.maxBackups}`);
|
|
409
|
+
console.log(`Compression: ${opts.compress ? "enabled" : "disabled"}`);
|
|
410
|
+
console.log(`Logging: ${opts.logFile ? `enabled (${opts.logPath})` : "disabled"}`);
|
|
349
411
|
console.log(`Press Ctrl+C to stop
|
|
350
412
|
`);
|
|
351
413
|
let lastContent = readFileSync(memoryFile, "utf-8");
|
|
414
|
+
let lastHash = hashContent(lastContent);
|
|
352
415
|
let backupCount = 0;
|
|
416
|
+
if (opts.logFile) writeWatchLog(opts.logPath, "Watch started");
|
|
353
417
|
const backup = () => {
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
const
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
418
|
+
try {
|
|
419
|
+
const currentContent = readFileSync(memoryFile, "utf-8");
|
|
420
|
+
const currentHash = hashContent(currentContent);
|
|
421
|
+
if (currentHash !== lastHash) {
|
|
422
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
423
|
+
const ext = opts.compress ? ".toon.gz" : ".toon";
|
|
424
|
+
const backupFile = join(backupDir, `backup-${timestamp}${ext}`);
|
|
425
|
+
if (opts.compress) {
|
|
426
|
+
writeFileSync(backupFile, compressData(currentContent));
|
|
427
|
+
} else {
|
|
428
|
+
writeFileSync(backupFile, currentContent);
|
|
429
|
+
}
|
|
430
|
+
backupCount++;
|
|
431
|
+
console.log(`\u{1F4E6} Backup #${backupCount} created: ${timestamp}`);
|
|
432
|
+
if (opts.logFile) writeWatchLog(opts.logPath, `Backup #${backupCount}: ${timestamp}`);
|
|
433
|
+
lastContent = currentContent;
|
|
434
|
+
lastHash = currentHash;
|
|
435
|
+
const pruned = pruneBackups(backupDir, opts.maxBackups);
|
|
436
|
+
if (pruned > 0) {
|
|
437
|
+
console.log(`\u{1F5D1}\uFE0F Pruned ${pruned} old backup(s)`);
|
|
438
|
+
if (opts.logFile) writeWatchLog(opts.logPath, `Pruned ${pruned} old backup(s)`);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
} catch (err) {
|
|
442
|
+
const msg = `Error creating backup: ${err.message}`;
|
|
443
|
+
console.error(`\u274C ${msg}`);
|
|
444
|
+
if (opts.logFile) writeWatchLog(opts.logPath, msg);
|
|
362
445
|
}
|
|
363
446
|
};
|
|
447
|
+
function hashContent(content) {
|
|
448
|
+
let hash = 0;
|
|
449
|
+
for (let i = 0; i < content.length; i++) {
|
|
450
|
+
const char = content.charCodeAt(i);
|
|
451
|
+
hash = (hash << 5) - hash + char;
|
|
452
|
+
hash = hash & hash;
|
|
453
|
+
}
|
|
454
|
+
return hash.toString(36);
|
|
455
|
+
}
|
|
364
456
|
backup();
|
|
365
|
-
const interval = setInterval(backup,
|
|
457
|
+
const interval = setInterval(backup, opts.interval * 60 * 1e3);
|
|
366
458
|
process.on("SIGINT", () => {
|
|
367
459
|
clearInterval(interval);
|
|
368
460
|
console.log(`
|
|
369
461
|
\u2705 Watch stopped. ${backupCount} backups created.
|
|
370
462
|
`);
|
|
463
|
+
if (opts.logFile) writeWatchLog(opts.logPath, `Watch stopped. ${backupCount} backups created.`);
|
|
371
464
|
process.exit(0);
|
|
372
465
|
});
|
|
373
466
|
process.on("SIGTERM", () => {
|
package/mcp/server.js
CHANGED
|
@@ -27796,16 +27796,16 @@ server.registerTool(
|
|
|
27796
27796
|
const id = generateId();
|
|
27797
27797
|
const date5 = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
27798
27798
|
const lines = data.split("\n");
|
|
27799
|
-
let headerIdx = lines.findIndex((l) => l.startsWith("entries["));
|
|
27799
|
+
let headerIdx = lines.findIndex((l) => l.startsWith("entries[") || /^\[\d+\|]/.test(l));
|
|
27800
27800
|
if (headerIdx === -1) {
|
|
27801
|
-
lines.push(`
|
|
27801
|
+
lines.push(`[0|]`);
|
|
27802
27802
|
headerIdx = lines.length - 1;
|
|
27803
27803
|
}
|
|
27804
|
-
const match = lines[headerIdx].match(
|
|
27804
|
+
const match = lines[headerIdx].match(/\[(\d+)\|/);
|
|
27805
27805
|
const count = match ? parseInt(match[1]) : 0;
|
|
27806
27806
|
const newEntry = `${id}|${category}|${key}|${content}|${file2 || ""}|${tags || ""}|${date5}`;
|
|
27807
27807
|
lines.splice(headerIdx + 1, 0, ` ${newEntry}`);
|
|
27808
|
-
lines[headerIdx] = lines[headerIdx].replace(
|
|
27808
|
+
lines[headerIdx] = lines[headerIdx].replace(/\[\d+\|/, `[${count + 1}|`);
|
|
27809
27809
|
writeMemory(lines.join("\n"));
|
|
27810
27810
|
return {
|
|
27811
27811
|
content: [{ type: "text", text: `\u{1F9E0} Guardado: ${category}/${key} (${id})
|
|
@@ -27827,7 +27827,7 @@ server.registerTool(
|
|
|
27827
27827
|
},
|
|
27828
27828
|
async ({ query, category, from_date, to_date }) => {
|
|
27829
27829
|
const data = readMemory();
|
|
27830
|
-
const lines = data.split("\n").filter((l) => l.startsWith(" ") && l.includes("|"));
|
|
27830
|
+
const lines = data.split("\n").filter((l) => l.startsWith(" ") && l.includes("|") && !l.startsWith("summaries:"));
|
|
27831
27831
|
const queryLower = query.toLowerCase();
|
|
27832
27832
|
const results = lines.map((line) => {
|
|
27833
27833
|
const trimmed = line.trim();
|
|
@@ -27862,19 +27862,19 @@ server.registerTool(
|
|
|
27862
27862
|
async ({ key }) => {
|
|
27863
27863
|
const data = readMemory();
|
|
27864
27864
|
const lines = data.split("\n");
|
|
27865
|
-
const headerIdx = lines.findIndex((l) => l.startsWith("entries["));
|
|
27865
|
+
const headerIdx = lines.findIndex((l) => l.startsWith("entries[") || /^\[\d+\|]/.test(l));
|
|
27866
27866
|
if (headerIdx === -1) {
|
|
27867
27867
|
return { content: [{ type: "text", text: "No hay entradas en memoria" }] };
|
|
27868
27868
|
}
|
|
27869
|
-
const entryLines = lines.slice(headerIdx + 1).filter((l) => l.trim().length > 0);
|
|
27869
|
+
const entryLines = lines.slice(headerIdx + 1).filter((l) => l.trim().length > 0 && !l.startsWith("summaries:"));
|
|
27870
27870
|
const filtered = entryLines.filter((l) => {
|
|
27871
27871
|
const parts = l.trim().split("|");
|
|
27872
27872
|
return parts[0] !== key && parts[2] !== key;
|
|
27873
27873
|
});
|
|
27874
27874
|
const removed = entryLines.length - filtered.length;
|
|
27875
|
-
const match = lines[headerIdx].match(
|
|
27875
|
+
const match = lines[headerIdx].match(/\[(\d+)\|/);
|
|
27876
27876
|
const count = match ? parseInt(match[1]) : 0;
|
|
27877
|
-
lines[headerIdx] = lines[headerIdx].replace(
|
|
27877
|
+
lines[headerIdx] = lines[headerIdx].replace(/\[\d+\|/, `[${count - removed}|`);
|
|
27878
27878
|
lines.splice(headerIdx + 1, entryLines.length, ...filtered.map((l) => ` ${l.trim()}`));
|
|
27879
27879
|
writeMemory(lines.join("\n"));
|
|
27880
27880
|
return {
|
|
@@ -27891,7 +27891,7 @@ server.registerTool(
|
|
|
27891
27891
|
},
|
|
27892
27892
|
async () => {
|
|
27893
27893
|
const data = readMemory();
|
|
27894
|
-
const lines = data.split("\n").filter((l) => l.startsWith(" ") && l.includes("|"));
|
|
27894
|
+
const lines = data.split("\n").filter((l) => l.startsWith(" ") && l.includes("|") && !l.startsWith(" summaries:"));
|
|
27895
27895
|
const entries = lines.map((l) => {
|
|
27896
27896
|
const parts = l.trim().split("|");
|
|
27897
27897
|
return { category: parts[1] || "unknown" };
|
|
@@ -27900,7 +27900,7 @@ server.registerTool(
|
|
|
27900
27900
|
for (const e of entries) {
|
|
27901
27901
|
byCategory[e.category] = (byCategory[e.category] || 0) + 1;
|
|
27902
27902
|
}
|
|
27903
|
-
const summaryLines = data.split("\n").filter((l) => l.includes(":") && !l.startsWith(" ") && !l.startsWith("version") && !l.startsWith("entries"));
|
|
27903
|
+
const summaryLines = data.split("\n").filter((l) => l.includes(":") && !l.startsWith(" ") && !l.startsWith("version") && !l.startsWith("entries") && !/^\[\d+\|]/.test(l));
|
|
27904
27904
|
const stats = [
|
|
27905
27905
|
`Entradas totales: ${entries.length}`,
|
|
27906
27906
|
`Res\xFAmenes de archivos: ${summaryLines.length}`,
|