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
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
|
|
244
|
-
| ------------------- |
|
|
245
|
-
| Ollama (Local) | qwen2:7b
|
|
246
|
-
| OpenAI | gpt-4o
|
|
247
|
-
|
|
|
248
|
-
|
|
|
249
|
-
|
|
|
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 (
|
|
269
|
-
npm run test:unit # Unit tests only
|
|
270
|
-
npm run test:integration # Integration tests
|
|
271
|
-
npm run test:e2e # End-to-end tests
|
|
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
|
|
277
|
-
|
|
|
278
|
-
| Unit — lib modules
|
|
279
|
-
| Unit — commands
|
|
280
|
-
| Unit — runtime
|
|
281
|
-
| Integration
|
|
282
|
-
| E2E
|
|
283
|
-
|
|
|
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
|
|
package/bin/chainlesschain.js
CHANGED
|
@@ -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
|
@@ -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
|
+
}
|