chainlesschain 0.37.9 → 0.37.11

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 (84) hide show
  1. package/README.md +309 -19
  2. package/bin/chainlesschain.js +4 -0
  3. package/package.json +1 -1
  4. package/src/commands/a2a.js +374 -0
  5. package/src/commands/audit.js +286 -0
  6. package/src/commands/auth.js +387 -0
  7. package/src/commands/bi.js +240 -0
  8. package/src/commands/browse.js +184 -0
  9. package/src/commands/cowork.js +317 -0
  10. package/src/commands/did.js +376 -0
  11. package/src/commands/economy.js +375 -0
  12. package/src/commands/encrypt.js +233 -0
  13. package/src/commands/evolution.js +398 -0
  14. package/src/commands/export.js +125 -0
  15. package/src/commands/git.js +215 -0
  16. package/src/commands/hmemory.js +273 -0
  17. package/src/commands/hook.js +260 -0
  18. package/src/commands/import.js +259 -0
  19. package/src/commands/init.js +184 -0
  20. package/src/commands/instinct.js +202 -0
  21. package/src/commands/llm.js +155 -4
  22. package/src/commands/lowcode.js +320 -0
  23. package/src/commands/mcp.js +302 -0
  24. package/src/commands/memory.js +282 -0
  25. package/src/commands/note.js +187 -0
  26. package/src/commands/org.js +505 -0
  27. package/src/commands/p2p.js +274 -0
  28. package/src/commands/plugin.js +451 -0
  29. package/src/commands/sandbox.js +366 -0
  30. package/src/commands/search.js +237 -0
  31. package/src/commands/session.js +238 -0
  32. package/src/commands/skill.js +254 -201
  33. package/src/commands/sync.js +249 -0
  34. package/src/commands/tokens.js +214 -0
  35. package/src/commands/wallet.js +416 -0
  36. package/src/commands/workflow.js +359 -0
  37. package/src/commands/zkp.js +277 -0
  38. package/src/index.js +93 -1
  39. package/src/lib/a2a-protocol.js +371 -0
  40. package/src/lib/agent-coordinator.js +273 -0
  41. package/src/lib/agent-economy.js +369 -0
  42. package/src/lib/app-builder.js +377 -0
  43. package/src/lib/audit-logger.js +364 -0
  44. package/src/lib/bi-engine.js +299 -0
  45. package/src/lib/bm25-search.js +322 -0
  46. package/src/lib/browser-automation.js +216 -0
  47. package/src/lib/cowork/ab-comparator-cli.js +180 -0
  48. package/src/lib/cowork/code-knowledge-graph-cli.js +232 -0
  49. package/src/lib/cowork/debate-review-cli.js +144 -0
  50. package/src/lib/cowork/decision-kb-cli.js +153 -0
  51. package/src/lib/cowork/project-style-analyzer-cli.js +168 -0
  52. package/src/lib/cowork-adapter.js +106 -0
  53. package/src/lib/crypto-manager.js +246 -0
  54. package/src/lib/did-manager.js +270 -0
  55. package/src/lib/ensure-utf8.js +59 -0
  56. package/src/lib/evolution-system.js +508 -0
  57. package/src/lib/git-integration.js +220 -0
  58. package/src/lib/hierarchical-memory.js +471 -0
  59. package/src/lib/hook-manager.js +387 -0
  60. package/src/lib/instinct-manager.js +190 -0
  61. package/src/lib/knowledge-exporter.js +302 -0
  62. package/src/lib/knowledge-importer.js +293 -0
  63. package/src/lib/llm-providers.js +325 -0
  64. package/src/lib/mcp-client.js +413 -0
  65. package/src/lib/memory-manager.js +211 -0
  66. package/src/lib/note-versioning.js +244 -0
  67. package/src/lib/org-manager.js +424 -0
  68. package/src/lib/p2p-manager.js +317 -0
  69. package/src/lib/pdf-parser.js +96 -0
  70. package/src/lib/permission-engine.js +374 -0
  71. package/src/lib/plan-mode.js +333 -0
  72. package/src/lib/plugin-manager.js +430 -0
  73. package/src/lib/project-detector.js +53 -0
  74. package/src/lib/response-cache.js +156 -0
  75. package/src/lib/sandbox-v2.js +503 -0
  76. package/src/lib/service-container.js +183 -0
  77. package/src/lib/session-manager.js +189 -0
  78. package/src/lib/skill-loader.js +274 -0
  79. package/src/lib/sync-manager.js +347 -0
  80. package/src/lib/token-tracker.js +200 -0
  81. package/src/lib/wallet-manager.js +348 -0
  82. package/src/lib/workflow-engine.js +503 -0
  83. package/src/lib/zkp-engine.js +241 -0
  84. package/src/repl/agent-repl.js +259 -124
@@ -0,0 +1,374 @@
1
+ /**
2
+ * A2A (Agent-to-Agent) Protocol commands
3
+ * chainlesschain a2a register|discover|submit|status|complete|fail|peers|cards|negotiate
4
+ */
5
+
6
+ import chalk from "chalk";
7
+ import ora from "ora";
8
+ import { logger } from "../lib/logger.js";
9
+ import { bootstrap, shutdown } from "../runtime/bootstrap.js";
10
+ import {
11
+ registerCard,
12
+ updateCard,
13
+ discoverAgents,
14
+ sendTask,
15
+ completeTask,
16
+ failTask,
17
+ getTaskStatus,
18
+ negotiateCapability,
19
+ listPeers,
20
+ } from "../lib/a2a-protocol.js";
21
+
22
+ export function registerA2aCommand(program) {
23
+ const a2a = program
24
+ .command("a2a")
25
+ .description("A2A Protocol — agent-to-agent communication");
26
+
27
+ // a2a register <name>
28
+ a2a
29
+ .command("register")
30
+ .description("Register an agent card")
31
+ .argument("<name>", "Agent name")
32
+ .option("--description <desc>", "Agent description", "")
33
+ .option("--url <url>", "Agent endpoint URL", "")
34
+ .option("--capabilities <csv>", "Comma-separated capabilities", "")
35
+ .option("--skills <csv>", "Comma-separated skills", "")
36
+ .option("--json", "Output as JSON")
37
+ .action(async (name, options) => {
38
+ try {
39
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
40
+ if (!ctx.db) {
41
+ logger.error("Database not available");
42
+ process.exit(1);
43
+ }
44
+ const db = ctx.db.getDatabase();
45
+ const card = registerCard(db, {
46
+ name,
47
+ description: options.description,
48
+ url: options.url,
49
+ capabilities: options.capabilities
50
+ ? options.capabilities.split(",").map((s) => s.trim())
51
+ : [],
52
+ skills: options.skills
53
+ ? options.skills.split(",").map((s) => s.trim())
54
+ : [],
55
+ });
56
+
57
+ if (options.json) {
58
+ console.log(JSON.stringify(card, null, 2));
59
+ } else {
60
+ logger.success(
61
+ `Agent registered: ${chalk.cyan(card.name)} ${chalk.gray(card.id)}`,
62
+ );
63
+ }
64
+
65
+ await shutdown();
66
+ } catch (err) {
67
+ logger.error(`Failed: ${err.message}`);
68
+ process.exit(1);
69
+ }
70
+ });
71
+
72
+ // a2a discover
73
+ a2a
74
+ .command("discover")
75
+ .description("Discover agents by capability or skill")
76
+ .option("--capability <name>", "Filter by capability")
77
+ .option("--skill <name>", "Filter by skill")
78
+ .option("--name <filter>", "Filter by name")
79
+ .option("--json", "Output as JSON")
80
+ .action(async (options) => {
81
+ try {
82
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
83
+ if (!ctx.db) {
84
+ logger.error("Database not available");
85
+ process.exit(1);
86
+ }
87
+ const db = ctx.db.getDatabase();
88
+ const agents = discoverAgents(db, {
89
+ capability: options.capability,
90
+ skill: options.skill,
91
+ name: options.name,
92
+ });
93
+
94
+ if (options.json) {
95
+ console.log(JSON.stringify(agents, null, 2));
96
+ } else if (agents.length === 0) {
97
+ logger.info("No agents found matching criteria");
98
+ } else {
99
+ logger.log(chalk.bold(`Discovered ${agents.length} agents:\n`));
100
+ for (const a of agents) {
101
+ logger.log(` ${chalk.cyan(a.name)} ${chalk.gray(a.id)}`);
102
+ if (a.description) logger.log(` ${chalk.white(a.description)}`);
103
+ if (a.capabilities.length)
104
+ logger.log(
105
+ ` Capabilities: ${chalk.yellow(a.capabilities.join(", "))}`,
106
+ );
107
+ if (a.skills.length)
108
+ logger.log(` Skills: ${chalk.yellow(a.skills.join(", "))}`);
109
+ }
110
+ }
111
+
112
+ await shutdown();
113
+ } catch (err) {
114
+ logger.error(`Failed: ${err.message}`);
115
+ process.exit(1);
116
+ }
117
+ });
118
+
119
+ // a2a submit <agent-id> <input>
120
+ a2a
121
+ .command("submit")
122
+ .description("Submit a task to an agent")
123
+ .argument("<agent-id>", "Target agent ID")
124
+ .argument("<input>", "Task input")
125
+ .option("--json", "Output as JSON")
126
+ .action(async (agentId, input, options) => {
127
+ try {
128
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
129
+ if (!ctx.db) {
130
+ logger.error("Database not available");
131
+ process.exit(1);
132
+ }
133
+ const db = ctx.db.getDatabase();
134
+ const result = sendTask(db, agentId, input);
135
+
136
+ if (options.json) {
137
+ console.log(JSON.stringify(result, null, 2));
138
+ } else {
139
+ logger.success(
140
+ `Task submitted: ${chalk.gray(result.taskId)} [${chalk.yellow(result.status)}]`,
141
+ );
142
+ }
143
+
144
+ await shutdown();
145
+ } catch (err) {
146
+ logger.error(`Failed: ${err.message}`);
147
+ process.exit(1);
148
+ }
149
+ });
150
+
151
+ // a2a status <task-id>
152
+ a2a
153
+ .command("status")
154
+ .description("Get task status")
155
+ .argument("<task-id>", "Task ID")
156
+ .option("--json", "Output as JSON")
157
+ .action(async (taskId, options) => {
158
+ try {
159
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
160
+ if (!ctx.db) {
161
+ logger.error("Database not available");
162
+ process.exit(1);
163
+ }
164
+ const db = ctx.db.getDatabase();
165
+ const task = getTaskStatus(db, taskId);
166
+
167
+ if (options.json) {
168
+ console.log(JSON.stringify(task, null, 2));
169
+ } else {
170
+ logger.log(chalk.bold("Task Status:\n"));
171
+ logger.log(` ID: ${chalk.gray(task.id)}`);
172
+ logger.log(` Agent: ${chalk.cyan(task.agent_id)}`);
173
+ logger.log(` Status: ${chalk.yellow(task.status)}`);
174
+ if (task.input)
175
+ logger.log(
176
+ ` Input: ${chalk.white(task.input.substring(0, 100))}`,
177
+ );
178
+ if (task.output)
179
+ logger.log(
180
+ ` Output: ${chalk.green(task.output.substring(0, 100))}`,
181
+ );
182
+ if (task.error) logger.log(` Error: ${chalk.red(task.error)}`);
183
+ if (task.history.length) {
184
+ logger.log(" History:");
185
+ for (const h of task.history) {
186
+ logger.log(
187
+ ` ${chalk.gray(h.timestamp)} → ${chalk.yellow(h.status)}`,
188
+ );
189
+ }
190
+ }
191
+ }
192
+
193
+ await shutdown();
194
+ } catch (err) {
195
+ logger.error(`Failed: ${err.message}`);
196
+ process.exit(1);
197
+ }
198
+ });
199
+
200
+ // a2a complete <task-id> <output>
201
+ a2a
202
+ .command("complete")
203
+ .description("Mark a task as completed")
204
+ .argument("<task-id>", "Task ID")
205
+ .argument("<output>", "Task output")
206
+ .action(async (taskId, output) => {
207
+ try {
208
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
209
+ if (!ctx.db) {
210
+ logger.error("Database not available");
211
+ process.exit(1);
212
+ }
213
+ const db = ctx.db.getDatabase();
214
+ const result = completeTask(db, taskId, output);
215
+ logger.success(
216
+ `Task ${chalk.gray(taskId)} → ${chalk.green(result.status)}`,
217
+ );
218
+
219
+ await shutdown();
220
+ } catch (err) {
221
+ logger.error(`Failed: ${err.message}`);
222
+ process.exit(1);
223
+ }
224
+ });
225
+
226
+ // a2a fail <task-id> <error>
227
+ a2a
228
+ .command("fail")
229
+ .description("Mark a task as failed")
230
+ .argument("<task-id>", "Task ID")
231
+ .argument("<error>", "Error message")
232
+ .action(async (taskId, error) => {
233
+ try {
234
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
235
+ if (!ctx.db) {
236
+ logger.error("Database not available");
237
+ process.exit(1);
238
+ }
239
+ const db = ctx.db.getDatabase();
240
+ const result = failTask(db, taskId, error);
241
+ logger.success(
242
+ `Task ${chalk.gray(taskId)} → ${chalk.red(result.status)}`,
243
+ );
244
+
245
+ await shutdown();
246
+ } catch (err) {
247
+ logger.error(`Failed: ${err.message}`);
248
+ process.exit(1);
249
+ }
250
+ });
251
+
252
+ // a2a peers
253
+ a2a
254
+ .command("peers")
255
+ .description("List all registered agents")
256
+ .option("--json", "Output as JSON")
257
+ .action(async (options) => {
258
+ try {
259
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
260
+ if (!ctx.db) {
261
+ logger.error("Database not available");
262
+ process.exit(1);
263
+ }
264
+ const db = ctx.db.getDatabase();
265
+ const peers = listPeers(db);
266
+
267
+ if (options.json) {
268
+ console.log(JSON.stringify(peers, null, 2));
269
+ } else if (peers.length === 0) {
270
+ logger.info("No agents registered. Use 'a2a register' to add one.");
271
+ } else {
272
+ logger.log(chalk.bold(`${peers.length} registered agents:\n`));
273
+ for (const p of peers) {
274
+ const statusColor =
275
+ p.status === "active" ? chalk.green : chalk.gray;
276
+ logger.log(
277
+ ` ${chalk.cyan(p.name)} ${chalk.gray(p.id)} ${statusColor(p.status)}`,
278
+ );
279
+ }
280
+ }
281
+
282
+ await shutdown();
283
+ } catch (err) {
284
+ logger.error(`Failed: ${err.message}`);
285
+ process.exit(1);
286
+ }
287
+ });
288
+
289
+ // a2a cards
290
+ a2a
291
+ .command("cards")
292
+ .description("List all agent cards with details")
293
+ .option("--json", "Output as JSON")
294
+ .action(async (options) => {
295
+ try {
296
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
297
+ if (!ctx.db) {
298
+ logger.error("Database not available");
299
+ process.exit(1);
300
+ }
301
+ const db = ctx.db.getDatabase();
302
+ const peers = listPeers(db);
303
+
304
+ if (options.json) {
305
+ console.log(JSON.stringify(peers, null, 2));
306
+ } else if (peers.length === 0) {
307
+ logger.info("No agent cards registered.");
308
+ } else {
309
+ logger.log(chalk.bold(`${peers.length} agent cards:\n`));
310
+ for (const p of peers) {
311
+ logger.log(` ${chalk.cyan(p.name)} ${chalk.gray(p.id)}`);
312
+ if (p.description) logger.log(` Description: ${p.description}`);
313
+ if (p.url) logger.log(` URL: ${chalk.blue(p.url)}`);
314
+ logger.log(` Auth: ${p.auth_type}`);
315
+ if (p.capabilities.length)
316
+ logger.log(
317
+ ` Capabilities: ${chalk.yellow(p.capabilities.join(", "))}`,
318
+ );
319
+ if (p.skills.length)
320
+ logger.log(` Skills: ${chalk.yellow(p.skills.join(", "))}`);
321
+ logger.log("");
322
+ }
323
+ }
324
+
325
+ await shutdown();
326
+ } catch (err) {
327
+ logger.error(`Failed: ${err.message}`);
328
+ process.exit(1);
329
+ }
330
+ });
331
+
332
+ // a2a negotiate <agent-id> <capabilities>
333
+ a2a
334
+ .command("negotiate")
335
+ .description("Check if an agent supports required capabilities")
336
+ .argument("<agent-id>", "Agent ID")
337
+ .argument("<capabilities>", "Comma-separated required capabilities")
338
+ .option("--json", "Output as JSON")
339
+ .action(async (agentId, capabilities, options) => {
340
+ try {
341
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
342
+ if (!ctx.db) {
343
+ logger.error("Database not available");
344
+ process.exit(1);
345
+ }
346
+ const db = ctx.db.getDatabase();
347
+ const required = capabilities.split(",").map((s) => s.trim());
348
+ const result = negotiateCapability(db, agentId, required);
349
+
350
+ if (options.json) {
351
+ console.log(JSON.stringify(result, null, 2));
352
+ } else {
353
+ if (result.compatible) {
354
+ logger.success("Agent is fully compatible");
355
+ } else {
356
+ logger.warn("Agent is not fully compatible");
357
+ }
358
+ if (result.supported.length) {
359
+ logger.log(
360
+ ` Supported: ${chalk.green(result.supported.join(", "))}`,
361
+ );
362
+ }
363
+ if (result.missing.length) {
364
+ logger.log(` Missing: ${chalk.red(result.missing.join(", "))}`);
365
+ }
366
+ }
367
+
368
+ await shutdown();
369
+ } catch (err) {
370
+ logger.error(`Failed: ${err.message}`);
371
+ process.exit(1);
372
+ }
373
+ });
374
+ }
@@ -0,0 +1,286 @@
1
+ /**
2
+ * Audit log commands
3
+ * chainlesschain audit log|search|stats|export|purge
4
+ */
5
+
6
+ import chalk from "chalk";
7
+ import fs from "fs";
8
+ import { logger } from "../lib/logger.js";
9
+ import { bootstrap, shutdown } from "../runtime/bootstrap.js";
10
+ import {
11
+ getRecentEvents,
12
+ queryLogs,
13
+ getStatistics,
14
+ exportLogs,
15
+ purgeLogs,
16
+ EVENT_TYPES,
17
+ RISK_LEVELS,
18
+ } from "../lib/audit-logger.js";
19
+
20
+ const RISK_COLORS = {
21
+ low: chalk.gray,
22
+ medium: chalk.yellow,
23
+ high: chalk.red,
24
+ critical: chalk.bgRed.white,
25
+ };
26
+
27
+ function formatLogEntry(log) {
28
+ const riskColor = RISK_COLORS[log.risk_level] || chalk.gray;
29
+ const status = log.success ? chalk.green("OK") : chalk.red("FAIL");
30
+ const time = log.created_at || "";
31
+
32
+ return [
33
+ ` ${chalk.gray(log.id.slice(0, 8))} ${chalk.gray(time)}`,
34
+ ` ${chalk.cyan(log.event_type.padEnd(12))} ${chalk.white(log.operation)} ${status} ${riskColor(`[${log.risk_level}]`)}`,
35
+ log.actor ? ` ${chalk.gray("actor:")} ${log.actor}` : null,
36
+ log.target ? ` ${chalk.gray("target:")} ${log.target}` : null,
37
+ log.error_message
38
+ ? ` ${chalk.red("error:")} ${log.error_message}`
39
+ : null,
40
+ ]
41
+ .filter(Boolean)
42
+ .join("\n");
43
+ }
44
+
45
+ export function registerAuditCommand(program) {
46
+ const audit = program
47
+ .command("audit")
48
+ .description("Audit log — security event tracking and compliance");
49
+
50
+ // audit log (default)
51
+ audit
52
+ .command("log", { isDefault: true })
53
+ .description("Show recent audit events")
54
+ .option("-n, --limit <n>", "Number of events to show", "20")
55
+ .option("--type <type>", "Filter by event type")
56
+ .option("--risk <level>", "Filter by risk level")
57
+ .option("--json", "Output as JSON")
58
+ .action(async (options) => {
59
+ try {
60
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
61
+ if (!ctx.db) {
62
+ logger.error("Database not available");
63
+ process.exit(1);
64
+ }
65
+ const db = ctx.db.getDatabase();
66
+
67
+ const filters = {
68
+ limit: parseInt(options.limit) || 20,
69
+ };
70
+ if (options.type) filters.eventType = options.type;
71
+ if (options.risk) filters.riskLevel = options.risk;
72
+
73
+ const logs = queryLogs(db, filters);
74
+
75
+ if (options.json) {
76
+ console.log(JSON.stringify(logs, null, 2));
77
+ } else if (logs.length === 0) {
78
+ logger.info("No audit events found");
79
+ } else {
80
+ logger.log(chalk.bold(`Audit Log (${logs.length} events):\n`));
81
+ for (const log of logs) {
82
+ logger.log(formatLogEntry(log));
83
+ }
84
+ }
85
+
86
+ await shutdown();
87
+ } catch (err) {
88
+ logger.error(`Failed: ${err.message}`);
89
+ process.exit(1);
90
+ }
91
+ });
92
+
93
+ // audit search
94
+ audit
95
+ .command("search")
96
+ .description("Search audit logs")
97
+ .argument("<query>", "Search query")
98
+ .option("-n, --limit <n>", "Max results", "50")
99
+ .option("--type <type>", "Filter by event type")
100
+ .option("--risk <level>", "Filter by risk level")
101
+ .option("--from <date>", "Start date (ISO 8601)")
102
+ .option("--to <date>", "End date (ISO 8601)")
103
+ .option("--failures", "Show only failed events")
104
+ .option("--json", "Output as JSON")
105
+ .action(async (query, options) => {
106
+ try {
107
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
108
+ if (!ctx.db) {
109
+ logger.error("Database not available");
110
+ process.exit(1);
111
+ }
112
+ const db = ctx.db.getDatabase();
113
+
114
+ const filters = {
115
+ search: query,
116
+ limit: parseInt(options.limit) || 50,
117
+ };
118
+ if (options.type) filters.eventType = options.type;
119
+ if (options.risk) filters.riskLevel = options.risk;
120
+ if (options.from) filters.startDate = options.from;
121
+ if (options.to) filters.endDate = options.to;
122
+ if (options.failures) filters.success = false;
123
+
124
+ const logs = queryLogs(db, filters);
125
+
126
+ if (options.json) {
127
+ console.log(JSON.stringify(logs, null, 2));
128
+ } else if (logs.length === 0) {
129
+ logger.info(`No audit events matching "${query}"`);
130
+ } else {
131
+ logger.log(chalk.bold(`Search Results (${logs.length}):\n`));
132
+ for (const log of logs) {
133
+ logger.log(formatLogEntry(log));
134
+ }
135
+ }
136
+
137
+ await shutdown();
138
+ } catch (err) {
139
+ logger.error(`Failed: ${err.message}`);
140
+ process.exit(1);
141
+ }
142
+ });
143
+
144
+ // audit stats
145
+ audit
146
+ .command("stats")
147
+ .description("Show audit statistics")
148
+ .option("--from <date>", "Start date")
149
+ .option("--to <date>", "End date")
150
+ .option("--json", "Output as JSON")
151
+ .action(async (options) => {
152
+ try {
153
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
154
+ if (!ctx.db) {
155
+ logger.error("Database not available");
156
+ process.exit(1);
157
+ }
158
+ const db = ctx.db.getDatabase();
159
+ const stats = getStatistics(db, options.from, options.to);
160
+
161
+ if (options.json) {
162
+ console.log(JSON.stringify(stats, null, 2));
163
+ } else {
164
+ logger.log(chalk.bold("Audit Statistics:\n"));
165
+ logger.log(` ${chalk.bold("Total events:")} ${stats.total}`);
166
+ logger.log(
167
+ ` ${chalk.bold("Failures:")} ${chalk.red(stats.failures)}`,
168
+ );
169
+ logger.log(
170
+ ` ${chalk.bold("High risk:")} ${chalk.red(stats.highRisk)}`,
171
+ );
172
+
173
+ if (Object.keys(stats.byEventType).length > 0) {
174
+ logger.log(`\n ${chalk.bold("By Event Type:")}`);
175
+ for (const [type, count] of Object.entries(stats.byEventType)) {
176
+ logger.log(` ${chalk.cyan(type.padEnd(15))} ${count}`);
177
+ }
178
+ }
179
+
180
+ if (Object.keys(stats.byRiskLevel).length > 0) {
181
+ logger.log(`\n ${chalk.bold("By Risk Level:")}`);
182
+ for (const [level, count] of Object.entries(stats.byRiskLevel)) {
183
+ const color = RISK_COLORS[level] || chalk.gray;
184
+ logger.log(` ${color(level.padEnd(15))} ${count}`);
185
+ }
186
+ }
187
+ }
188
+
189
+ await shutdown();
190
+ } catch (err) {
191
+ logger.error(`Failed: ${err.message}`);
192
+ process.exit(1);
193
+ }
194
+ });
195
+
196
+ // audit export
197
+ audit
198
+ .command("export")
199
+ .description("Export audit logs to file")
200
+ .option("-o, --output <path>", "Output file path")
201
+ .option("-f, --format <fmt>", "Format: json or csv", "json")
202
+ .option("--from <date>", "Start date")
203
+ .option("--to <date>", "End date")
204
+ .option("-n, --limit <n>", "Max events", "10000")
205
+ .action(async (options) => {
206
+ try {
207
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
208
+ if (!ctx.db) {
209
+ logger.error("Database not available");
210
+ process.exit(1);
211
+ }
212
+ const db = ctx.db.getDatabase();
213
+
214
+ const filters = { limit: parseInt(options.limit) || 10000 };
215
+ if (options.from) filters.startDate = options.from;
216
+ if (options.to) filters.endDate = options.to;
217
+
218
+ const data = exportLogs(db, options.format, filters);
219
+
220
+ if (options.output) {
221
+ fs.writeFileSync(options.output, data, "utf8");
222
+ logger.success(`Exported to ${options.output}`);
223
+ } else {
224
+ console.log(data);
225
+ }
226
+
227
+ await shutdown();
228
+ } catch (err) {
229
+ logger.error(`Failed: ${err.message}`);
230
+ process.exit(1);
231
+ }
232
+ });
233
+
234
+ // audit purge
235
+ audit
236
+ .command("purge")
237
+ .description("Delete old audit logs")
238
+ .option("--days <n>", "Keep logs from last N days", "90")
239
+ .option("--force", "Skip confirmation")
240
+ .action(async (options) => {
241
+ try {
242
+ const days = parseInt(options.days) || 90;
243
+
244
+ if (!options.force) {
245
+ const { confirm } = await import("@inquirer/prompts");
246
+ const ok = await confirm({
247
+ message: `Delete audit logs older than ${days} days? This cannot be undone.`,
248
+ });
249
+ if (!ok) {
250
+ logger.info("Cancelled");
251
+ return;
252
+ }
253
+ }
254
+
255
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
256
+ if (!ctx.db) {
257
+ logger.error("Database not available");
258
+ process.exit(1);
259
+ }
260
+ const db = ctx.db.getDatabase();
261
+ const deleted = purgeLogs(db, days);
262
+ logger.success(`Purged ${deleted} old audit events`);
263
+
264
+ await shutdown();
265
+ } catch (err) {
266
+ logger.error(`Failed: ${err.message}`);
267
+ process.exit(1);
268
+ }
269
+ });
270
+
271
+ // audit types
272
+ audit
273
+ .command("types")
274
+ .description("List available event types and risk levels")
275
+ .action(async () => {
276
+ logger.log(chalk.bold("Event Types:\n"));
277
+ for (const [key, value] of Object.entries(EVENT_TYPES)) {
278
+ logger.log(` ${chalk.cyan(value.padEnd(15))} ${chalk.gray(key)}`);
279
+ }
280
+ logger.log(chalk.bold("\nRisk Levels:\n"));
281
+ for (const [key, value] of Object.entries(RISK_LEVELS)) {
282
+ const color = RISK_COLORS[value] || chalk.gray;
283
+ logger.log(` ${color(value.padEnd(15))} ${chalk.gray(key)}`);
284
+ }
285
+ });
286
+ }