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.
Files changed (51) 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/audit.js +286 -0
  5. package/src/commands/auth.js +387 -0
  6. package/src/commands/browse.js +184 -0
  7. package/src/commands/did.js +376 -0
  8. package/src/commands/encrypt.js +233 -0
  9. package/src/commands/export.js +125 -0
  10. package/src/commands/git.js +215 -0
  11. package/src/commands/import.js +259 -0
  12. package/src/commands/instinct.js +202 -0
  13. package/src/commands/llm.js +155 -4
  14. package/src/commands/mcp.js +302 -0
  15. package/src/commands/memory.js +282 -0
  16. package/src/commands/note.js +187 -0
  17. package/src/commands/org.js +505 -0
  18. package/src/commands/p2p.js +274 -0
  19. package/src/commands/plugin.js +398 -0
  20. package/src/commands/search.js +237 -0
  21. package/src/commands/session.js +238 -0
  22. package/src/commands/sync.js +249 -0
  23. package/src/commands/tokens.js +214 -0
  24. package/src/commands/wallet.js +416 -0
  25. package/src/index.js +49 -1
  26. package/src/lib/audit-logger.js +364 -0
  27. package/src/lib/bm25-search.js +322 -0
  28. package/src/lib/browser-automation.js +216 -0
  29. package/src/lib/crypto-manager.js +246 -0
  30. package/src/lib/did-manager.js +270 -0
  31. package/src/lib/ensure-utf8.js +59 -0
  32. package/src/lib/git-integration.js +220 -0
  33. package/src/lib/instinct-manager.js +190 -0
  34. package/src/lib/knowledge-exporter.js +302 -0
  35. package/src/lib/knowledge-importer.js +293 -0
  36. package/src/lib/llm-providers.js +325 -0
  37. package/src/lib/mcp-client.js +413 -0
  38. package/src/lib/memory-manager.js +211 -0
  39. package/src/lib/note-versioning.js +244 -0
  40. package/src/lib/org-manager.js +424 -0
  41. package/src/lib/p2p-manager.js +317 -0
  42. package/src/lib/pdf-parser.js +96 -0
  43. package/src/lib/permission-engine.js +374 -0
  44. package/src/lib/plan-mode.js +333 -0
  45. package/src/lib/plugin-manager.js +312 -0
  46. package/src/lib/response-cache.js +156 -0
  47. package/src/lib/session-manager.js +189 -0
  48. package/src/lib/sync-manager.js +347 -0
  49. package/src/lib/token-tracker.js +200 -0
  50. package/src/lib/wallet-manager.js +348 -0
  51. package/src/repl/agent-repl.js +142 -12
package/README.md CHANGED
@@ -132,6 +132,9 @@ chainlesschain note list --category dev --tag important
132
132
  chainlesschain note show <id> # Show note by ID prefix
133
133
  chainlesschain note search "keyword" # Full-text search
134
134
  chainlesschain note delete <id> # Soft delete
135
+ chainlesschain note history <id> # Version history
136
+ chainlesschain note diff <id> <v1> <v2> # Diff between versions
137
+ chainlesschain note revert <id> <ver> # Revert to a version
135
138
  ```
136
139
 
137
140
  ### `chainlesschain chat`
@@ -166,6 +169,9 @@ chainlesschain llm models # List installed Ollama models
166
169
  chainlesschain llm models --json # JSON output
167
170
  chainlesschain llm test # Test Ollama connectivity
168
171
  chainlesschain llm test --provider openai --api-key sk-...
172
+ chainlesschain llm providers # List 7 built-in LLM providers
173
+ chainlesschain llm add-provider <name> # Add custom provider
174
+ chainlesschain llm switch <name> # Switch active provider
169
175
  ```
170
176
 
171
177
  ### `chainlesschain agent` (alias: `a`)
@@ -196,6 +202,286 @@ chainlesschain skill search "browser" # Search by keyword
196
202
  chainlesschain skill run code-review "Review this function..."
197
203
  ```
198
204
 
205
+ ---
206
+
207
+ ## Phase 1: AI Intelligence Layer
208
+
209
+ ### `chainlesschain search <query>`
210
+
211
+ BM25 hybrid keyword search across notes.
212
+
213
+ ```bash
214
+ chainlesschain search "machine learning"
215
+ chainlesschain search "API design" --mode bm25 --top-k 10
216
+ chainlesschain search "security" --json
217
+ ```
218
+
219
+ ### `chainlesschain tokens <action>`
220
+
221
+ Token usage tracking and cost analysis.
222
+
223
+ ```bash
224
+ chainlesschain tokens show # Current usage summary
225
+ chainlesschain tokens breakdown # Per-model breakdown
226
+ chainlesschain tokens recent # Recent usage entries
227
+ chainlesschain tokens cache # Cache hit/miss stats
228
+ ```
229
+
230
+ ### `chainlesschain memory <action>`
231
+
232
+ Persistent memory management.
233
+
234
+ ```bash
235
+ chainlesschain memory show # Show all memories
236
+ chainlesschain memory add "Always use TypeScript"
237
+ chainlesschain memory search "coding" # Search memories
238
+ chainlesschain memory delete <id> # Delete by ID prefix
239
+ chainlesschain memory daily # Today's daily note
240
+ chainlesschain memory file # Show memory file path
241
+ ```
242
+
243
+ ### `chainlesschain session <action>`
244
+
245
+ Session persistence and management.
246
+
247
+ ```bash
248
+ chainlesschain session list # List saved sessions
249
+ chainlesschain session show <id> # Show session details
250
+ chainlesschain session resume <id> # Resume a session
251
+ chainlesschain session export <id> # Export as Markdown
252
+ chainlesschain session delete <id> # Delete a session
253
+ ```
254
+
255
+ ---
256
+
257
+ ## Phase 2: Knowledge & Content Management
258
+
259
+ ### `chainlesschain import <format>`
260
+
261
+ Import knowledge from external sources.
262
+
263
+ ```bash
264
+ chainlesschain import markdown ./docs # Import markdown directory
265
+ chainlesschain import evernote backup.enex # Import Evernote ENEX
266
+ chainlesschain import notion ./export # Import Notion export
267
+ chainlesschain import pdf document.pdf # Import PDF text
268
+ ```
269
+
270
+ ### `chainlesschain export <format>`
271
+
272
+ Export knowledge base.
273
+
274
+ ```bash
275
+ chainlesschain export markdown -o ./output # Export as Markdown files
276
+ chainlesschain export site -o ./site # Export as static HTML site
277
+ ```
278
+
279
+ ### `chainlesschain git <action>`
280
+
281
+ Git integration for knowledge versioning.
282
+
283
+ ```bash
284
+ chainlesschain git status # Show git status
285
+ chainlesschain git init # Initialize git repo
286
+ chainlesschain git auto-commit # Auto-commit all changes
287
+ chainlesschain git hooks # Install pre-commit hooks
288
+ chainlesschain git history-analyze # Analyze repo history
289
+ ```
290
+
291
+ ### Note Versioning
292
+
293
+ ```bash
294
+ chainlesschain note history <id> # Show version history
295
+ chainlesschain note diff <id> <v1> <v2> # Diff between versions
296
+ chainlesschain note revert <id> <ver> # Revert to version
297
+ ```
298
+
299
+ ---
300
+
301
+ ## Phase 3: MCP & External Integration
302
+
303
+ ### `chainlesschain mcp <action>`
304
+
305
+ MCP (Model Context Protocol) server management.
306
+
307
+ ```bash
308
+ chainlesschain mcp servers # List configured servers
309
+ chainlesschain mcp add <name> -c <cmd> # Add a server
310
+ chainlesschain mcp remove <name> # Remove a server
311
+ chainlesschain mcp connect <name> # Connect to server
312
+ chainlesschain mcp disconnect <name> # Disconnect
313
+ chainlesschain mcp tools # List available tools
314
+ chainlesschain mcp call <server> <tool> # Call a tool
315
+ ```
316
+
317
+ ### `chainlesschain browse <action>`
318
+
319
+ Browser automation (headless fetch-based).
320
+
321
+ ```bash
322
+ chainlesschain browse fetch <url> # Fetch page content
323
+ chainlesschain browse scrape <url> -s "h2" # Scrape CSS selector
324
+ chainlesschain browse screenshot <url> # Take screenshot (requires playwright)
325
+ ```
326
+
327
+ ### `chainlesschain instinct <action>`
328
+
329
+ Instinct learning — tracks user preferences over time.
330
+
331
+ ```bash
332
+ chainlesschain instinct show # Show learned instincts
333
+ chainlesschain instinct categories # List 6 instinct categories
334
+ chainlesschain instinct prompt # Generate system prompt from instincts
335
+ chainlesschain instinct delete <id> # Delete an instinct
336
+ chainlesschain instinct reset # Clear all instincts
337
+ chainlesschain instinct decay # Decay old instincts
338
+ ```
339
+
340
+ ---
341
+
342
+ ## Phase 4: Security & Identity
343
+
344
+ ### `chainlesschain did <action>`
345
+
346
+ DID identity management (Ed25519).
347
+
348
+ ```bash
349
+ chainlesschain did create --label "My Identity"
350
+ chainlesschain did list
351
+ chainlesschain did show <did>
352
+ chainlesschain did sign <did> "message"
353
+ chainlesschain did verify <did> "message" <signature>
354
+ chainlesschain did export <did>
355
+ chainlesschain did set-default <did>
356
+ chainlesschain did delete <did>
357
+ ```
358
+
359
+ ### `chainlesschain encrypt / decrypt`
360
+
361
+ AES-256-GCM file encryption.
362
+
363
+ ```bash
364
+ chainlesschain encrypt file <input> -o <output>
365
+ chainlesschain encrypt db
366
+ chainlesschain encrypt info <file>
367
+ chainlesschain encrypt status
368
+ chainlesschain decrypt file <input> -o <output>
369
+ chainlesschain decrypt db
370
+ ```
371
+
372
+ ### `chainlesschain auth <action>`
373
+
374
+ RBAC permission engine.
375
+
376
+ ```bash
377
+ chainlesschain auth roles # List roles
378
+ chainlesschain auth create-role <name> # Create custom role
379
+ chainlesschain auth grant <user> <role> # Assign role
380
+ chainlesschain auth check <user> <scope> # Check permission
381
+ chainlesschain auth permissions <user> # List user permissions
382
+ chainlesschain auth scopes # List all 26 scopes
383
+ ```
384
+
385
+ ### `chainlesschain audit <action>`
386
+
387
+ Audit logging and compliance.
388
+
389
+ ```bash
390
+ chainlesschain audit log # Recent events
391
+ chainlesschain audit search --type security # Search by type
392
+ chainlesschain audit stats # Statistics
393
+ chainlesschain audit export --format json # Export logs
394
+ chainlesschain audit purge --before 90 # Purge old logs
395
+ chainlesschain audit types # List event types
396
+ ```
397
+
398
+ ---
399
+
400
+ ## Phase 5: P2P, Blockchain & Enterprise
401
+
402
+ ### `chainlesschain p2p <action>`
403
+
404
+ Peer-to-peer messaging and device pairing.
405
+
406
+ ```bash
407
+ chainlesschain p2p status # P2P network status
408
+ chainlesschain p2p peers # List known peers
409
+ chainlesschain p2p send <peer-id> "message" # Send message
410
+ chainlesschain p2p inbox # View inbox
411
+ chainlesschain p2p pair <device-name> # Pair a device
412
+ chainlesschain p2p devices # List paired devices
413
+ chainlesschain p2p unpair <device-id> # Unpair a device
414
+ ```
415
+
416
+ ### `chainlesschain sync <action>`
417
+
418
+ File and knowledge synchronization.
419
+
420
+ ```bash
421
+ chainlesschain sync status # Sync status
422
+ chainlesschain sync push # Push local changes
423
+ chainlesschain sync pull # Pull remote changes
424
+ chainlesschain sync conflicts # List conflicts
425
+ chainlesschain sync resolve <id> --strategy local # Resolve conflict
426
+ chainlesschain sync log # Sync history
427
+ chainlesschain sync clear # Clear sync state
428
+ ```
429
+
430
+ ### `chainlesschain wallet <action>`
431
+
432
+ Digital wallet and asset management.
433
+
434
+ ```bash
435
+ chainlesschain wallet create --name "My Wallet" # Create wallet
436
+ chainlesschain wallet list # List wallets
437
+ chainlesschain wallet balance <address> # Check balance
438
+ chainlesschain wallet set-default <address> # Set default wallet
439
+ chainlesschain wallet delete <address> # Delete wallet
440
+ chainlesschain wallet asset <address> <type> <name> # Create asset
441
+ chainlesschain wallet assets [address] # List assets
442
+ chainlesschain wallet transfer <asset-id> <to> # Transfer asset
443
+ chainlesschain wallet history [address] # Transaction history
444
+ chainlesschain wallet summary # Overall summary
445
+ ```
446
+
447
+ ### `chainlesschain org <action>`
448
+
449
+ Organization management and workflows.
450
+
451
+ ```bash
452
+ chainlesschain org create <name> # Create organization
453
+ chainlesschain org list # List organizations
454
+ chainlesschain org show <id> # Organization details
455
+ chainlesschain org delete <id> # Delete organization
456
+ chainlesschain org invite <org-id> <user-id> # Invite member
457
+ chainlesschain org members <org-id> # List members
458
+ chainlesschain org team-create <org-id> <name> # Create team
459
+ chainlesschain org teams <org-id> # List teams
460
+ chainlesschain org approval-submit <org-id> <title> # Submit approval
461
+ chainlesschain org approvals <org-id> # List approvals
462
+ chainlesschain org approve <request-id> # Approve request
463
+ chainlesschain org reject <request-id> # Reject request
464
+ ```
465
+
466
+ ### `chainlesschain plugin <action>`
467
+
468
+ Plugin marketplace management.
469
+
470
+ ```bash
471
+ chainlesschain plugin list # List installed plugins
472
+ chainlesschain plugin install <name> --version <v> # Install plugin
473
+ chainlesschain plugin remove <name> # Remove plugin
474
+ chainlesschain plugin enable <name> # Enable plugin
475
+ chainlesschain plugin disable <name> # Disable plugin
476
+ chainlesschain plugin update <name> --version <v> # Update plugin
477
+ chainlesschain plugin info <name> # Plugin details
478
+ chainlesschain plugin search <query> # Search registry
479
+ chainlesschain plugin registry # List all registry plugins
480
+ chainlesschain plugin summary # Installation summary
481
+ ```
482
+
483
+ ---
484
+
199
485
  ## Global Options
200
486
 
201
487
  ```bash
@@ -240,13 +526,16 @@ Configuration is stored at `~/.chainlesschain/config.json`. The CLI creates and
240
526
 
241
527
  ### Supported LLM Providers
242
528
 
243
- | Provider | Default Model | API Key Required |
244
- | ------------------- | ------------- | ---------------- |
245
- | Ollama (Local) | qwen2:7b | No |
246
- | OpenAI | gpt-4o | Yes |
247
- | DashScope (Alibaba) | qwen-max | Yes |
248
- | DeepSeek | deepseek-chat | Yes |
249
- | Custom | | Yes |
529
+ | Provider | Default Model | API Key Required |
530
+ | ------------------- | ----------------- | ---------------- |
531
+ | Ollama (Local) | qwen2:7b | No |
532
+ | OpenAI | gpt-4o | Yes |
533
+ | Anthropic | claude-sonnet-4-6 | Yes |
534
+ | DashScope (Alibaba) | qwen-max | Yes |
535
+ | DeepSeek | deepseek-chat | Yes |
536
+ | Gemini (Google) | gemini-pro | Yes |
537
+ | Mistral | mistral-large | Yes |
538
+ | Custom | — | Yes |
250
539
 
251
540
  ## File Structure
252
541
 
@@ -265,22 +554,23 @@ Configuration is stored at `~/.chainlesschain/config.json`. The CLI creates and
265
554
  ```bash
266
555
  cd packages/cli
267
556
  npm install
268
- npm test # Run all tests (117 tests across 18 files)
269
- npm run test:unit # Unit tests only (10 files)
270
- npm run test:integration # Integration tests (3 files)
271
- npm run test:e2e # End-to-end tests (5 files)
557
+ npm test # Run all tests (903 tests across 47 files)
558
+ npm run test:unit # Unit tests only
559
+ npm run test:integration # Integration tests
560
+ npm run test:e2e # End-to-end tests
272
561
  ```
273
562
 
274
563
  ### Test Coverage
275
564
 
276
- | Category | Files | Tests | Status |
277
- | ------------------ | ------ | ------- | --------------- |
278
- | Unit — lib modules | 8 | 52 | All passing |
279
- | Unit — commands | 3 | 31 | All passing |
280
- | Unit — runtime | 1 | 6 | All passing |
281
- | Integration | 3 | 7 | All passing |
282
- | E2E | 3 | 21 | All passing |
283
- | **Total** | **18** | **117** | **All passing** |
565
+ | Category | Files | Tests | Status |
566
+ | ------------------------ | ------ | ------- | --------------- |
567
+ | Unit — lib modules | 25 | 578 | All passing |
568
+ | Unit — commands | 2 | 43 | All passing |
569
+ | Unit — runtime | 1 | 6 | All passing |
570
+ | Integration | 3 | 7 | All passing |
571
+ | E2E | 9 | 88 | All passing |
572
+ | Core packages (external) | — | 118 | All passing |
573
+ | **CLI Total** | **47** | **903** | **All passing** |
284
574
 
285
575
  ## License
286
576
 
@@ -1,5 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ // Ensure UTF-8 encoding on Windows to prevent Chinese character garbling (乱码)
4
+ import { ensureUtf8 } from "../src/lib/ensure-utf8.js";
5
+ ensureUtf8();
6
+
3
7
  import { createProgram } from "../src/index.js";
4
8
 
5
9
  const program = createProgram();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chainlesschain",
3
- "version": "0.37.9",
3
+ "version": "0.37.10",
4
4
  "description": "CLI for ChainlessChain - install, configure, and manage your personal AI management system",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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
+ }