chainlesschain 0.37.9 → 0.37.10
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 +309 -19
- package/bin/chainlesschain.js +4 -0
- package/package.json +1 -1
- package/src/commands/audit.js +286 -0
- package/src/commands/auth.js +387 -0
- package/src/commands/browse.js +184 -0
- package/src/commands/did.js +376 -0
- package/src/commands/encrypt.js +233 -0
- package/src/commands/export.js +125 -0
- package/src/commands/git.js +215 -0
- package/src/commands/import.js +259 -0
- package/src/commands/instinct.js +202 -0
- package/src/commands/llm.js +155 -4
- package/src/commands/mcp.js +302 -0
- package/src/commands/memory.js +282 -0
- package/src/commands/note.js +187 -0
- package/src/commands/org.js +505 -0
- package/src/commands/p2p.js +274 -0
- package/src/commands/plugin.js +398 -0
- package/src/commands/search.js +237 -0
- package/src/commands/session.js +238 -0
- package/src/commands/sync.js +249 -0
- package/src/commands/tokens.js +214 -0
- package/src/commands/wallet.js +416 -0
- package/src/index.js +49 -1
- package/src/lib/audit-logger.js +364 -0
- package/src/lib/bm25-search.js +322 -0
- package/src/lib/browser-automation.js +216 -0
- package/src/lib/crypto-manager.js +246 -0
- package/src/lib/did-manager.js +270 -0
- package/src/lib/ensure-utf8.js +59 -0
- package/src/lib/git-integration.js +220 -0
- package/src/lib/instinct-manager.js +190 -0
- package/src/lib/knowledge-exporter.js +302 -0
- package/src/lib/knowledge-importer.js +293 -0
- package/src/lib/llm-providers.js +325 -0
- package/src/lib/mcp-client.js +413 -0
- package/src/lib/memory-manager.js +211 -0
- package/src/lib/note-versioning.js +244 -0
- package/src/lib/org-manager.js +424 -0
- package/src/lib/p2p-manager.js +317 -0
- package/src/lib/pdf-parser.js +96 -0
- package/src/lib/permission-engine.js +374 -0
- package/src/lib/plan-mode.js +333 -0
- package/src/lib/plugin-manager.js +312 -0
- package/src/lib/response-cache.js +156 -0
- package/src/lib/session-manager.js +189 -0
- package/src/lib/sync-manager.js +347 -0
- package/src/lib/token-tracker.js +200 -0
- package/src/lib/wallet-manager.js +348 -0
- package/src/repl/agent-repl.js +142 -12
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistent memory management commands
|
|
3
|
+
* chainlesschain memory show|add|search|daily|file
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
import { logger } from "../lib/logger.js";
|
|
8
|
+
import { bootstrap, shutdown } from "../runtime/bootstrap.js";
|
|
9
|
+
import {
|
|
10
|
+
getMemoryDir,
|
|
11
|
+
addMemory,
|
|
12
|
+
searchMemory,
|
|
13
|
+
listMemory,
|
|
14
|
+
deleteMemory,
|
|
15
|
+
appendDailyNote,
|
|
16
|
+
getDailyNote,
|
|
17
|
+
listDailyNotes,
|
|
18
|
+
getMemoryFile,
|
|
19
|
+
updateMemoryFile,
|
|
20
|
+
} from "../lib/memory-manager.js";
|
|
21
|
+
|
|
22
|
+
export function registerMemoryCommand(program) {
|
|
23
|
+
const memory = program
|
|
24
|
+
.command("memory")
|
|
25
|
+
.description("Persistent memory and daily notes");
|
|
26
|
+
|
|
27
|
+
// memory show
|
|
28
|
+
memory
|
|
29
|
+
.command("show", { isDefault: true })
|
|
30
|
+
.description("Show memory entries")
|
|
31
|
+
.option("-n, --limit <n>", "Max entries", "20")
|
|
32
|
+
.option("--category <cat>", "Filter by category")
|
|
33
|
+
.option("--json", "Output as JSON")
|
|
34
|
+
.action(async (options) => {
|
|
35
|
+
try {
|
|
36
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
37
|
+
if (!ctx.db) {
|
|
38
|
+
logger.error("Database not available");
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
const db = ctx.db.getDatabase();
|
|
42
|
+
const entries = listMemory(db, {
|
|
43
|
+
limit: Math.max(1, parseInt(options.limit) || 20),
|
|
44
|
+
category: options.category,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
if (options.json) {
|
|
48
|
+
console.log(JSON.stringify(entries, null, 2));
|
|
49
|
+
} else if (entries.length === 0) {
|
|
50
|
+
logger.info("No memory entries. Use 'memory add' to create one.");
|
|
51
|
+
} else {
|
|
52
|
+
logger.log(chalk.bold(`Memory (${entries.length} entries):\n`));
|
|
53
|
+
for (const e of entries) {
|
|
54
|
+
const stars =
|
|
55
|
+
"★".repeat(e.importance) + "☆".repeat(5 - e.importance);
|
|
56
|
+
logger.log(
|
|
57
|
+
` ${chalk.gray(e.id.slice(0, 12))} ${chalk.yellow(stars)} ${chalk.cyan(e.category)}`,
|
|
58
|
+
);
|
|
59
|
+
logger.log(
|
|
60
|
+
` ${chalk.white(e.content.substring(0, 120).replace(/\n/g, " "))}`,
|
|
61
|
+
);
|
|
62
|
+
logger.log(` ${chalk.gray(e.created_at)}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
await shutdown();
|
|
67
|
+
} catch (err) {
|
|
68
|
+
logger.error(`Failed: ${err.message}`);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// memory add
|
|
74
|
+
memory
|
|
75
|
+
.command("add")
|
|
76
|
+
.description("Add a memory entry")
|
|
77
|
+
.argument("<text>", "Memory content")
|
|
78
|
+
.option("--category <cat>", "Category", "general")
|
|
79
|
+
.option("--importance <n>", "Importance 1-5", "3")
|
|
80
|
+
.option("--json", "Output as JSON")
|
|
81
|
+
.action(async (text, options) => {
|
|
82
|
+
try {
|
|
83
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
84
|
+
if (!ctx.db) {
|
|
85
|
+
logger.error("Database not available");
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
const db = ctx.db.getDatabase();
|
|
89
|
+
const entry = addMemory(db, text, {
|
|
90
|
+
category: options.category,
|
|
91
|
+
importance: Math.max(
|
|
92
|
+
1,
|
|
93
|
+
Math.min(5, parseInt(options.importance) || 3),
|
|
94
|
+
),
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
if (options.json) {
|
|
98
|
+
console.log(JSON.stringify(entry, null, 2));
|
|
99
|
+
} else {
|
|
100
|
+
logger.success(
|
|
101
|
+
`Memory added: ${chalk.gray(entry.id.slice(0, 12))} [${chalk.cyan(entry.category)}]`,
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
await shutdown();
|
|
106
|
+
} catch (err) {
|
|
107
|
+
logger.error(`Failed: ${err.message}`);
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// memory search
|
|
113
|
+
memory
|
|
114
|
+
.command("search")
|
|
115
|
+
.description("Search memory entries")
|
|
116
|
+
.argument("<query>", "Search query")
|
|
117
|
+
.option("-n, --limit <n>", "Max results", "20")
|
|
118
|
+
.option("--json", "Output as JSON")
|
|
119
|
+
.action(async (query, options) => {
|
|
120
|
+
try {
|
|
121
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
122
|
+
if (!ctx.db) {
|
|
123
|
+
logger.error("Database not available");
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
const db = ctx.db.getDatabase();
|
|
127
|
+
const results = searchMemory(db, query, {
|
|
128
|
+
limit: Math.max(1, parseInt(options.limit) || 20),
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
if (options.json) {
|
|
132
|
+
console.log(JSON.stringify(results, null, 2));
|
|
133
|
+
} else if (results.length === 0) {
|
|
134
|
+
logger.info(`No memory entries matching "${query}"`);
|
|
135
|
+
} else {
|
|
136
|
+
logger.log(
|
|
137
|
+
chalk.bold(
|
|
138
|
+
`Memory search "${query}" (${results.length} results):\n`,
|
|
139
|
+
),
|
|
140
|
+
);
|
|
141
|
+
for (const e of results) {
|
|
142
|
+
logger.log(
|
|
143
|
+
` ${chalk.gray(e.id.slice(0, 12))} ${chalk.cyan(e.category)} ${chalk.gray(e.created_at)}`,
|
|
144
|
+
);
|
|
145
|
+
logger.log(
|
|
146
|
+
` ${chalk.white(e.content.substring(0, 120).replace(/\n/g, " "))}`,
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
await shutdown();
|
|
152
|
+
} catch (err) {
|
|
153
|
+
logger.error(`Failed: ${err.message}`);
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// memory delete
|
|
159
|
+
memory
|
|
160
|
+
.command("delete")
|
|
161
|
+
.description("Delete a memory entry")
|
|
162
|
+
.argument("<id>", "Entry ID (or prefix)")
|
|
163
|
+
.action(async (id) => {
|
|
164
|
+
try {
|
|
165
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
166
|
+
if (!ctx.db) {
|
|
167
|
+
logger.error("Database not available");
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
const db = ctx.db.getDatabase();
|
|
171
|
+
const ok = deleteMemory(db, id);
|
|
172
|
+
if (ok) {
|
|
173
|
+
logger.success("Memory entry deleted");
|
|
174
|
+
} else {
|
|
175
|
+
logger.error(`Memory entry not found: ${id}`);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
await shutdown();
|
|
179
|
+
} catch (err) {
|
|
180
|
+
logger.error(`Failed: ${err.message}`);
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// memory daily
|
|
186
|
+
memory
|
|
187
|
+
.command("daily")
|
|
188
|
+
.description("View or append to daily notes")
|
|
189
|
+
.argument("[date]", "Date (YYYY-MM-DD, default: today)")
|
|
190
|
+
.option("-a, --append <text>", "Append text to daily note")
|
|
191
|
+
.option("--list", "List available daily notes")
|
|
192
|
+
.option("--json", "Output as JSON")
|
|
193
|
+
.action(async (date, options) => {
|
|
194
|
+
try {
|
|
195
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
196
|
+
const memoryDir = getMemoryDir(ctx.env.dataDir);
|
|
197
|
+
|
|
198
|
+
if (options.list) {
|
|
199
|
+
const notes = listDailyNotes(memoryDir);
|
|
200
|
+
if (options.json) {
|
|
201
|
+
console.log(JSON.stringify(notes, null, 2));
|
|
202
|
+
} else if (notes.length === 0) {
|
|
203
|
+
logger.info("No daily notes yet");
|
|
204
|
+
} else {
|
|
205
|
+
logger.log(chalk.bold("Daily Notes:\n"));
|
|
206
|
+
for (const n of notes) {
|
|
207
|
+
logger.log(
|
|
208
|
+
` ${chalk.cyan(n.date)} ${chalk.gray(n.size + " bytes")}`,
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
await shutdown();
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (options.append) {
|
|
217
|
+
const result = appendDailyNote(memoryDir, options.append);
|
|
218
|
+
logger.success(`Added to daily note: ${chalk.cyan(result.date)}`);
|
|
219
|
+
await shutdown();
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Show daily note
|
|
224
|
+
const targetDate = date || new Date().toISOString().slice(0, 10);
|
|
225
|
+
const content = getDailyNote(memoryDir, targetDate);
|
|
226
|
+
|
|
227
|
+
if (!content) {
|
|
228
|
+
logger.info(`No daily note for ${targetDate}`);
|
|
229
|
+
} else if (options.json) {
|
|
230
|
+
console.log(JSON.stringify({ date: targetDate, content }, null, 2));
|
|
231
|
+
} else {
|
|
232
|
+
logger.log(content);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
await shutdown();
|
|
236
|
+
} catch (err) {
|
|
237
|
+
logger.error(`Failed: ${err.message}`);
|
|
238
|
+
process.exit(1);
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
// memory file
|
|
243
|
+
memory
|
|
244
|
+
.command("file")
|
|
245
|
+
.description("View or edit the long-term MEMORY.md file")
|
|
246
|
+
.option("--edit", "Open in $EDITOR")
|
|
247
|
+
.option("--json", "Output as JSON")
|
|
248
|
+
.action(async (options) => {
|
|
249
|
+
try {
|
|
250
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
251
|
+
const memoryDir = getMemoryDir(ctx.env.dataDir);
|
|
252
|
+
const content = getMemoryFile(memoryDir);
|
|
253
|
+
|
|
254
|
+
if (options.edit) {
|
|
255
|
+
const { execSync } = await import("child_process");
|
|
256
|
+
const editor = process.env.EDITOR || process.env.VISUAL || "nano";
|
|
257
|
+
const filePath = `${memoryDir}/MEMORY.md`;
|
|
258
|
+
try {
|
|
259
|
+
execSync(`${editor} "${filePath}"`, { stdio: "inherit" });
|
|
260
|
+
logger.success("Memory file updated");
|
|
261
|
+
} catch {
|
|
262
|
+
logger.error(
|
|
263
|
+
`Failed to open editor. Set $EDITOR environment variable.`,
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
} else if (options.json) {
|
|
267
|
+
console.log(JSON.stringify({ content }, null, 2));
|
|
268
|
+
} else if (!content) {
|
|
269
|
+
logger.info(
|
|
270
|
+
"MEMORY.md is empty. Use 'memory file --edit' to add content.",
|
|
271
|
+
);
|
|
272
|
+
} else {
|
|
273
|
+
logger.log(content);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
await shutdown();
|
|
277
|
+
} catch (err) {
|
|
278
|
+
logger.error(`Failed: ${err.message}`);
|
|
279
|
+
process.exit(1);
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
}
|
package/src/commands/note.js
CHANGED
|
@@ -6,6 +6,15 @@
|
|
|
6
6
|
import chalk from "chalk";
|
|
7
7
|
import { logger } from "../lib/logger.js";
|
|
8
8
|
import { bootstrap, shutdown } from "../runtime/bootstrap.js";
|
|
9
|
+
import {
|
|
10
|
+
ensureVersionsTable,
|
|
11
|
+
saveVersion,
|
|
12
|
+
getHistory,
|
|
13
|
+
getVersion,
|
|
14
|
+
simpleDiff,
|
|
15
|
+
formatDiff,
|
|
16
|
+
revertToVersion,
|
|
17
|
+
} from "../lib/note-versioning.js";
|
|
9
18
|
|
|
10
19
|
/**
|
|
11
20
|
* Ensure the notes table exists in the database
|
|
@@ -299,4 +308,182 @@ export function registerNoteCommand(program) {
|
|
|
299
308
|
process.exit(1);
|
|
300
309
|
}
|
|
301
310
|
});
|
|
311
|
+
|
|
312
|
+
// note history
|
|
313
|
+
note
|
|
314
|
+
.command("history")
|
|
315
|
+
.description("Show version history for a note")
|
|
316
|
+
.argument("<id>", "Note ID (or prefix)")
|
|
317
|
+
.option("--json", "Output as JSON")
|
|
318
|
+
.action(async (id, options) => {
|
|
319
|
+
try {
|
|
320
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
321
|
+
if (!ctx.db) {
|
|
322
|
+
logger.error("Database not available");
|
|
323
|
+
process.exit(1);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const rawDb = ctx.db.getDatabase();
|
|
327
|
+
ensureNotesTable(rawDb);
|
|
328
|
+
ensureVersionsTable(rawDb);
|
|
329
|
+
|
|
330
|
+
// Find the note
|
|
331
|
+
const noteRow = rawDb
|
|
332
|
+
.prepare(
|
|
333
|
+
"SELECT id FROM notes WHERE id LIKE ? AND deleted_at IS NULL",
|
|
334
|
+
)
|
|
335
|
+
.get(`${id}%`);
|
|
336
|
+
|
|
337
|
+
if (!noteRow) {
|
|
338
|
+
logger.error(`Note not found: ${id}`);
|
|
339
|
+
process.exit(1);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const history = getHistory(rawDb, noteRow.id);
|
|
343
|
+
|
|
344
|
+
if (options.json) {
|
|
345
|
+
console.log(JSON.stringify(history, null, 2));
|
|
346
|
+
} else if (history.length === 0) {
|
|
347
|
+
logger.info("No version history found");
|
|
348
|
+
} else {
|
|
349
|
+
logger.log(
|
|
350
|
+
chalk.bold(`Version history (${history.length} versions):\n`),
|
|
351
|
+
);
|
|
352
|
+
for (const v of history) {
|
|
353
|
+
logger.log(
|
|
354
|
+
` ${chalk.cyan(`v${v.version}`)} ${chalk.white(v.title)} ${chalk.gray(v.change_type)} ${chalk.gray(v.created_at || "")}`,
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
await shutdown();
|
|
360
|
+
} catch (err) {
|
|
361
|
+
logger.error(`Failed to get history: ${err.message}`);
|
|
362
|
+
process.exit(1);
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
// note diff
|
|
367
|
+
note
|
|
368
|
+
.command("diff")
|
|
369
|
+
.description("Show diff between two versions of a note")
|
|
370
|
+
.argument("<id>", "Note ID (or prefix)")
|
|
371
|
+
.argument("<v1>", "First version number")
|
|
372
|
+
.argument("<v2>", "Second version number")
|
|
373
|
+
.option("--json", "Output as JSON")
|
|
374
|
+
.action(async (id, v1, v2, options) => {
|
|
375
|
+
try {
|
|
376
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
377
|
+
if (!ctx.db) {
|
|
378
|
+
logger.error("Database not available");
|
|
379
|
+
process.exit(1);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const rawDb = ctx.db.getDatabase();
|
|
383
|
+
ensureNotesTable(rawDb);
|
|
384
|
+
ensureVersionsTable(rawDb);
|
|
385
|
+
|
|
386
|
+
const noteRow = rawDb
|
|
387
|
+
.prepare(
|
|
388
|
+
"SELECT id FROM notes WHERE id LIKE ? AND deleted_at IS NULL",
|
|
389
|
+
)
|
|
390
|
+
.get(`${id}%`);
|
|
391
|
+
|
|
392
|
+
if (!noteRow) {
|
|
393
|
+
logger.error(`Note not found: ${id}`);
|
|
394
|
+
process.exit(1);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const ver1 = getVersion(rawDb, noteRow.id, parseInt(v1));
|
|
398
|
+
const ver2 = getVersion(rawDb, noteRow.id, parseInt(v2));
|
|
399
|
+
|
|
400
|
+
if (!ver1) {
|
|
401
|
+
logger.error(`Version ${v1} not found`);
|
|
402
|
+
process.exit(1);
|
|
403
|
+
}
|
|
404
|
+
if (!ver2) {
|
|
405
|
+
logger.error(`Version ${v2} not found`);
|
|
406
|
+
process.exit(1);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const diff = simpleDiff(ver1.content, ver2.content);
|
|
410
|
+
|
|
411
|
+
if (options.json) {
|
|
412
|
+
console.log(
|
|
413
|
+
JSON.stringify(
|
|
414
|
+
{ v1: parseInt(v1), v2: parseInt(v2), diff },
|
|
415
|
+
null,
|
|
416
|
+
2,
|
|
417
|
+
),
|
|
418
|
+
);
|
|
419
|
+
} else {
|
|
420
|
+
logger.log(chalk.bold(`Diff: v${v1} → v${v2}\n`));
|
|
421
|
+
logger.log(formatDiff(diff));
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
await shutdown();
|
|
425
|
+
} catch (err) {
|
|
426
|
+
logger.error(`Failed to compute diff: ${err.message}`);
|
|
427
|
+
process.exit(1);
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
// note revert
|
|
432
|
+
note
|
|
433
|
+
.command("revert")
|
|
434
|
+
.description("Revert a note to a specific version")
|
|
435
|
+
.argument("<id>", "Note ID (or prefix)")
|
|
436
|
+
.argument("<version>", "Version number to revert to")
|
|
437
|
+
.option("--force", "Skip confirmation")
|
|
438
|
+
.action(async (id, version, options) => {
|
|
439
|
+
try {
|
|
440
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
441
|
+
if (!ctx.db) {
|
|
442
|
+
logger.error("Database not available");
|
|
443
|
+
process.exit(1);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const rawDb = ctx.db.getDatabase();
|
|
447
|
+
ensureNotesTable(rawDb);
|
|
448
|
+
ensureVersionsTable(rawDb);
|
|
449
|
+
|
|
450
|
+
const noteRow = rawDb
|
|
451
|
+
.prepare(
|
|
452
|
+
"SELECT id, title FROM notes WHERE id LIKE ? AND deleted_at IS NULL",
|
|
453
|
+
)
|
|
454
|
+
.get(`${id}%`);
|
|
455
|
+
|
|
456
|
+
if (!noteRow) {
|
|
457
|
+
logger.error(`Note not found: ${id}`);
|
|
458
|
+
process.exit(1);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
if (!options.force) {
|
|
462
|
+
const { confirm } = await import("@inquirer/prompts");
|
|
463
|
+
const ok = await confirm({
|
|
464
|
+
message: `Revert note "${noteRow.title}" to version ${version}?`,
|
|
465
|
+
});
|
|
466
|
+
if (!ok) {
|
|
467
|
+
logger.info("Cancelled");
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const result = revertToVersion(rawDb, noteRow.id, parseInt(version));
|
|
473
|
+
|
|
474
|
+
if (!result) {
|
|
475
|
+
logger.error(`Version ${version} not found or note unavailable`);
|
|
476
|
+
process.exit(1);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
logger.success(
|
|
480
|
+
`Note reverted to v${result.reverted_to} → new version v${result.new_version}`,
|
|
481
|
+
);
|
|
482
|
+
|
|
483
|
+
await shutdown();
|
|
484
|
+
} catch (err) {
|
|
485
|
+
logger.error(`Failed to revert: ${err.message}`);
|
|
486
|
+
process.exit(1);
|
|
487
|
+
}
|
|
488
|
+
});
|
|
302
489
|
}
|