chainlesschain 0.37.8 → 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.
Files changed (59) hide show
  1. package/README.md +403 -8
  2. package/bin/chainlesschain.js +4 -0
  3. package/package.json +7 -2
  4. package/src/commands/agent.js +30 -0
  5. package/src/commands/ask.js +114 -0
  6. package/src/commands/audit.js +286 -0
  7. package/src/commands/auth.js +387 -0
  8. package/src/commands/browse.js +184 -0
  9. package/src/commands/chat.js +35 -0
  10. package/src/commands/db.js +152 -0
  11. package/src/commands/did.js +376 -0
  12. package/src/commands/encrypt.js +233 -0
  13. package/src/commands/export.js +125 -0
  14. package/src/commands/git.js +215 -0
  15. package/src/commands/import.js +259 -0
  16. package/src/commands/instinct.js +202 -0
  17. package/src/commands/llm.js +288 -0
  18. package/src/commands/mcp.js +302 -0
  19. package/src/commands/memory.js +282 -0
  20. package/src/commands/note.js +489 -0
  21. package/src/commands/org.js +505 -0
  22. package/src/commands/p2p.js +274 -0
  23. package/src/commands/plugin.js +398 -0
  24. package/src/commands/search.js +237 -0
  25. package/src/commands/session.js +238 -0
  26. package/src/commands/skill.js +479 -0
  27. package/src/commands/sync.js +249 -0
  28. package/src/commands/tokens.js +214 -0
  29. package/src/commands/wallet.js +416 -0
  30. package/src/index.js +65 -0
  31. package/src/lib/audit-logger.js +364 -0
  32. package/src/lib/bm25-search.js +322 -0
  33. package/src/lib/browser-automation.js +216 -0
  34. package/src/lib/crypto-manager.js +246 -0
  35. package/src/lib/did-manager.js +270 -0
  36. package/src/lib/ensure-utf8.js +59 -0
  37. package/src/lib/git-integration.js +220 -0
  38. package/src/lib/instinct-manager.js +190 -0
  39. package/src/lib/knowledge-exporter.js +302 -0
  40. package/src/lib/knowledge-importer.js +293 -0
  41. package/src/lib/llm-providers.js +325 -0
  42. package/src/lib/mcp-client.js +413 -0
  43. package/src/lib/memory-manager.js +211 -0
  44. package/src/lib/note-versioning.js +244 -0
  45. package/src/lib/org-manager.js +424 -0
  46. package/src/lib/p2p-manager.js +317 -0
  47. package/src/lib/pdf-parser.js +96 -0
  48. package/src/lib/permission-engine.js +374 -0
  49. package/src/lib/plan-mode.js +333 -0
  50. package/src/lib/platform.js +15 -0
  51. package/src/lib/plugin-manager.js +312 -0
  52. package/src/lib/response-cache.js +156 -0
  53. package/src/lib/session-manager.js +189 -0
  54. package/src/lib/sync-manager.js +347 -0
  55. package/src/lib/token-tracker.js +200 -0
  56. package/src/lib/wallet-manager.js +348 -0
  57. package/src/repl/agent-repl.js +912 -0
  58. package/src/repl/chat-repl.js +262 -0
  59. package/src/runtime/bootstrap.js +159 -0
@@ -0,0 +1,489 @@
1
+ /**
2
+ * Note/knowledge base management commands
3
+ * chainlesschain note add|list|show|search|delete|export
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
+ ensureVersionsTable,
11
+ saveVersion,
12
+ getHistory,
13
+ getVersion,
14
+ simpleDiff,
15
+ formatDiff,
16
+ revertToVersion,
17
+ } from "../lib/note-versioning.js";
18
+
19
+ /**
20
+ * Ensure the notes table exists in the database
21
+ */
22
+ function ensureNotesTable(db) {
23
+ db.exec(`
24
+ CREATE TABLE IF NOT EXISTS notes (
25
+ id TEXT PRIMARY KEY,
26
+ title TEXT NOT NULL,
27
+ content TEXT DEFAULT '',
28
+ tags TEXT DEFAULT '[]',
29
+ category TEXT DEFAULT 'general',
30
+ created_at TEXT DEFAULT (datetime('now')),
31
+ updated_at TEXT DEFAULT (datetime('now')),
32
+ deleted_at TEXT DEFAULT NULL
33
+ )
34
+ `);
35
+ }
36
+
37
+ export function registerNoteCommand(program) {
38
+ const note = program
39
+ .command("note")
40
+ .description("Note and knowledge base management");
41
+
42
+ // note add
43
+ note
44
+ .command("add")
45
+ .description("Add a new note")
46
+ .argument("<title>", "Note title")
47
+ .option("-c, --content <content>", "Note content")
48
+ .option("-t, --tags <tags>", "Comma-separated tags")
49
+ .option("--category <category>", "Note category", "general")
50
+ .option("--json", "Output as JSON")
51
+ .action(async (title, options) => {
52
+ try {
53
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
54
+ if (!ctx.db) {
55
+ logger.error("Database not available");
56
+ process.exit(1);
57
+ }
58
+
59
+ const rawDb = ctx.db.getDatabase();
60
+ ensureNotesTable(rawDb);
61
+
62
+ const { randomUUID } = await import("crypto");
63
+ const id = randomUUID();
64
+ const tags = options.tags
65
+ ? JSON.stringify(options.tags.split(",").map((t) => t.trim()))
66
+ : "[]";
67
+
68
+ rawDb
69
+ .prepare(
70
+ "INSERT INTO notes (id, title, content, tags, category) VALUES (?, ?, ?, ?, ?)",
71
+ )
72
+ .run(id, title, options.content || "", tags, options.category);
73
+
74
+ if (options.json) {
75
+ console.log(
76
+ JSON.stringify({
77
+ id,
78
+ title,
79
+ tags: JSON.parse(tags),
80
+ category: options.category,
81
+ }),
82
+ );
83
+ } else {
84
+ logger.success(
85
+ `Note created: ${chalk.cyan(title)} (${chalk.gray(id.slice(0, 8))})`,
86
+ );
87
+ }
88
+
89
+ await shutdown();
90
+ } catch (err) {
91
+ logger.error(`Failed to add note: ${err.message}`);
92
+ process.exit(1);
93
+ }
94
+ });
95
+
96
+ // note list
97
+ note
98
+ .command("list")
99
+ .description("List notes")
100
+ .option("-n, --limit <n>", "Max notes to show", "20")
101
+ .option("--category <category>", "Filter by category")
102
+ .option("--tag <tag>", "Filter by tag")
103
+ .option("--json", "Output as JSON")
104
+ .action(async (options) => {
105
+ try {
106
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
107
+ if (!ctx.db) {
108
+ logger.error("Database not available");
109
+ process.exit(1);
110
+ }
111
+
112
+ const rawDb = ctx.db.getDatabase();
113
+ ensureNotesTable(rawDb);
114
+
115
+ let sql =
116
+ "SELECT id, title, tags, category, created_at FROM notes WHERE deleted_at IS NULL";
117
+ const params = [];
118
+
119
+ if (options.category) {
120
+ sql += " AND category = ?";
121
+ params.push(options.category);
122
+ }
123
+
124
+ sql += " ORDER BY created_at DESC LIMIT ?";
125
+ params.push(parseInt(options.limit));
126
+
127
+ let notes = rawDb.prepare(sql).all(...params);
128
+
129
+ // Filter by tag in-memory (tags stored as JSON array)
130
+ if (options.tag) {
131
+ notes = notes.filter((n) => {
132
+ try {
133
+ const tags = JSON.parse(n.tags || "[]");
134
+ return tags.includes(options.tag);
135
+ } catch {
136
+ return false;
137
+ }
138
+ });
139
+ }
140
+
141
+ if (options.json) {
142
+ console.log(JSON.stringify(notes, null, 2));
143
+ } else if (notes.length === 0) {
144
+ logger.info("No notes found");
145
+ } else {
146
+ logger.log(chalk.bold(`Notes (${notes.length}):\n`));
147
+ for (const n of notes) {
148
+ const tags = JSON.parse(n.tags || "[]");
149
+ const tagStr =
150
+ tags.length > 0 ? chalk.gray(` [${tags.join(", ")}]`) : "";
151
+ logger.log(
152
+ ` ${chalk.gray(n.id.slice(0, 8))} ${chalk.white(n.title)}${tagStr} ${chalk.gray(n.created_at)}`,
153
+ );
154
+ }
155
+ }
156
+
157
+ await shutdown();
158
+ } catch (err) {
159
+ logger.error(`Failed to list notes: ${err.message}`);
160
+ process.exit(1);
161
+ }
162
+ });
163
+
164
+ // note show
165
+ note
166
+ .command("show")
167
+ .description("Show note content")
168
+ .argument("<id>", "Note ID (or prefix)")
169
+ .option("--json", "Output as JSON")
170
+ .action(async (id, options) => {
171
+ try {
172
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
173
+ if (!ctx.db) {
174
+ logger.error("Database not available");
175
+ process.exit(1);
176
+ }
177
+
178
+ const rawDb = ctx.db.getDatabase();
179
+ ensureNotesTable(rawDb);
180
+
181
+ const note = rawDb
182
+ .prepare("SELECT * FROM notes WHERE id LIKE ? AND deleted_at IS NULL")
183
+ .get(`${id}%`);
184
+
185
+ if (!note) {
186
+ logger.error(`Note not found: ${id}`);
187
+ process.exit(1);
188
+ }
189
+
190
+ if (options.json) {
191
+ console.log(JSON.stringify(note, null, 2));
192
+ } else {
193
+ logger.log(chalk.bold(note.title));
194
+ logger.log(chalk.gray(`ID: ${note.id}`));
195
+ logger.log(
196
+ chalk.gray(
197
+ `Category: ${note.category} Created: ${note.created_at}`,
198
+ ),
199
+ );
200
+ const tags = JSON.parse(note.tags || "[]");
201
+ if (tags.length > 0) {
202
+ logger.log(chalk.gray(`Tags: ${tags.join(", ")}`));
203
+ }
204
+ logger.log("");
205
+ logger.log(note.content || chalk.gray("(empty)"));
206
+ }
207
+
208
+ await shutdown();
209
+ } catch (err) {
210
+ logger.error(`Failed to show note: ${err.message}`);
211
+ process.exit(1);
212
+ }
213
+ });
214
+
215
+ // note search
216
+ note
217
+ .command("search")
218
+ .description("Search notes")
219
+ .argument("<query>", "Search query")
220
+ .option("--json", "Output as JSON")
221
+ .action(async (query, options) => {
222
+ try {
223
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
224
+ if (!ctx.db) {
225
+ logger.error("Database not available");
226
+ process.exit(1);
227
+ }
228
+
229
+ const rawDb = ctx.db.getDatabase();
230
+ ensureNotesTable(rawDb);
231
+
232
+ const pattern = `%${query}%`;
233
+ const notes = rawDb
234
+ .prepare(
235
+ "SELECT id, title, category, created_at FROM notes WHERE deleted_at IS NULL AND (title LIKE ? OR content LIKE ?) ORDER BY created_at DESC LIMIT 50",
236
+ )
237
+ .all(pattern, pattern);
238
+
239
+ if (options.json) {
240
+ console.log(JSON.stringify(notes, null, 2));
241
+ } else if (notes.length === 0) {
242
+ logger.info(`No notes matching "${query}"`);
243
+ } else {
244
+ logger.log(
245
+ chalk.bold(`Search results for "${query}" (${notes.length}):\n`),
246
+ );
247
+ for (const n of notes) {
248
+ logger.log(
249
+ ` ${chalk.gray(n.id.slice(0, 8))} ${chalk.white(n.title)} ${chalk.gray(n.created_at)}`,
250
+ );
251
+ }
252
+ }
253
+
254
+ await shutdown();
255
+ } catch (err) {
256
+ logger.error(`Search failed: ${err.message}`);
257
+ process.exit(1);
258
+ }
259
+ });
260
+
261
+ // note delete
262
+ note
263
+ .command("delete")
264
+ .description("Delete a note (soft delete)")
265
+ .argument("<id>", "Note ID (or prefix)")
266
+ .option("--force", "Skip confirmation")
267
+ .action(async (id, options) => {
268
+ try {
269
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
270
+ if (!ctx.db) {
271
+ logger.error("Database not available");
272
+ process.exit(1);
273
+ }
274
+
275
+ const rawDb = ctx.db.getDatabase();
276
+ ensureNotesTable(rawDb);
277
+
278
+ const note = rawDb
279
+ .prepare(
280
+ "SELECT id, title FROM notes WHERE id LIKE ? AND deleted_at IS NULL",
281
+ )
282
+ .get(`${id}%`);
283
+
284
+ if (!note) {
285
+ logger.error(`Note not found: ${id}`);
286
+ process.exit(1);
287
+ }
288
+
289
+ if (!options.force) {
290
+ const { confirm } = await import("@inquirer/prompts");
291
+ const ok = await confirm({
292
+ message: `Delete note "${note.title}"?`,
293
+ });
294
+ if (!ok) {
295
+ logger.info("Cancelled");
296
+ return;
297
+ }
298
+ }
299
+
300
+ rawDb
301
+ .prepare("UPDATE notes SET deleted_at = datetime('now') WHERE id = ?")
302
+ .run(note.id);
303
+
304
+ logger.success(`Note deleted: ${chalk.cyan(note.title)}`);
305
+ await shutdown();
306
+ } catch (err) {
307
+ logger.error(`Failed to delete note: ${err.message}`);
308
+ process.exit(1);
309
+ }
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
+ });
489
+ }