chainlesschain 0.37.12 → 0.40.1

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 (48) hide show
  1. package/package.json +3 -2
  2. package/src/commands/agent.js +7 -1
  3. package/src/commands/ask.js +24 -9
  4. package/src/commands/chat.js +7 -1
  5. package/src/commands/cli-anything.js +266 -0
  6. package/src/commands/compliance.js +216 -0
  7. package/src/commands/dao.js +312 -0
  8. package/src/commands/dlp.js +278 -0
  9. package/src/commands/evomap.js +558 -0
  10. package/src/commands/hardening.js +230 -0
  11. package/src/commands/matrix.js +168 -0
  12. package/src/commands/nostr.js +185 -0
  13. package/src/commands/pqc.js +162 -0
  14. package/src/commands/scim.js +218 -0
  15. package/src/commands/serve.js +109 -0
  16. package/src/commands/siem.js +156 -0
  17. package/src/commands/social.js +480 -0
  18. package/src/commands/terraform.js +148 -0
  19. package/src/constants.js +1 -0
  20. package/src/index.js +60 -0
  21. package/src/lib/autonomous-agent.js +487 -0
  22. package/src/lib/cli-anything-bridge.js +379 -0
  23. package/src/lib/cli-context-engineering.js +472 -0
  24. package/src/lib/compliance-manager.js +290 -0
  25. package/src/lib/content-recommender.js +205 -0
  26. package/src/lib/dao-governance.js +296 -0
  27. package/src/lib/dlp-engine.js +304 -0
  28. package/src/lib/evomap-client.js +135 -0
  29. package/src/lib/evomap-federation.js +240 -0
  30. package/src/lib/evomap-governance.js +250 -0
  31. package/src/lib/evomap-manager.js +227 -0
  32. package/src/lib/git-integration.js +1 -1
  33. package/src/lib/hardening-manager.js +275 -0
  34. package/src/lib/llm-providers.js +14 -1
  35. package/src/lib/matrix-bridge.js +196 -0
  36. package/src/lib/nostr-bridge.js +195 -0
  37. package/src/lib/permanent-memory.js +370 -0
  38. package/src/lib/plan-mode.js +211 -0
  39. package/src/lib/pqc-manager.js +196 -0
  40. package/src/lib/scim-manager.js +212 -0
  41. package/src/lib/session-manager.js +38 -0
  42. package/src/lib/siem-exporter.js +137 -0
  43. package/src/lib/social-manager.js +283 -0
  44. package/src/lib/task-model-selector.js +232 -0
  45. package/src/lib/terraform-manager.js +201 -0
  46. package/src/lib/ws-server.js +474 -0
  47. package/src/repl/agent-repl.js +796 -41
  48. package/src/repl/chat-repl.js +14 -6
@@ -0,0 +1,162 @@
1
+ /**
2
+ * PQC commands
3
+ * chainlesschain pqc keys|generate|migration-status|migrate
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
+ ensurePQCTables,
11
+ listKeys,
12
+ generateKey,
13
+ getMigrationStatus,
14
+ migrate,
15
+ } from "../lib/pqc-manager.js";
16
+
17
+ export function registerPqcCommand(program) {
18
+ const pqc = program
19
+ .command("pqc")
20
+ .description("Post-quantum cryptography — key management and migration");
21
+
22
+ // pqc keys
23
+ pqc
24
+ .command("keys")
25
+ .description("List PQC keys")
26
+ .option("-a, --algorithm <algo>", "Filter by algorithm")
27
+ .option("--json", "Output as JSON")
28
+ .action(async (options) => {
29
+ try {
30
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
31
+ if (!ctx.db) {
32
+ logger.error("Database not available");
33
+ process.exit(1);
34
+ }
35
+ const db = ctx.db.getDatabase();
36
+ ensurePQCTables(db);
37
+
38
+ const keys = listKeys({ algorithm: options.algorithm });
39
+ if (options.json) {
40
+ console.log(JSON.stringify(keys, null, 2));
41
+ } else if (keys.length === 0) {
42
+ logger.info("No PQC keys. Use `pqc generate` to create one.");
43
+ } else {
44
+ for (const k of keys) {
45
+ logger.log(
46
+ ` ${chalk.cyan(k.id.slice(0, 8))} ${k.algorithm} [${k.purpose}] size=${k.keySize} hybrid=${k.hybridMode}`,
47
+ );
48
+ }
49
+ }
50
+
51
+ await shutdown();
52
+ } catch (err) {
53
+ logger.error(`Failed: ${err.message}`);
54
+ process.exit(1);
55
+ }
56
+ });
57
+
58
+ // pqc generate
59
+ pqc
60
+ .command("generate <algorithm>")
61
+ .description("Generate a PQC key pair")
62
+ .option(
63
+ "-p, --purpose <purpose>",
64
+ "Key purpose: encryption, signing, key_exchange",
65
+ "encryption",
66
+ )
67
+ .action(async (algorithm, options) => {
68
+ try {
69
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
70
+ if (!ctx.db) {
71
+ logger.error("Database not available");
72
+ process.exit(1);
73
+ }
74
+ const db = ctx.db.getDatabase();
75
+ ensurePQCTables(db);
76
+
77
+ const key = generateKey(db, algorithm, options.purpose);
78
+ logger.success("PQC key generated");
79
+ logger.log(` ${chalk.bold("ID:")} ${chalk.cyan(key.id)}`);
80
+ logger.log(` ${chalk.bold("Algorithm:")} ${key.algorithm}`);
81
+ logger.log(` ${chalk.bold("Purpose:")} ${key.purpose}`);
82
+ logger.log(` ${chalk.bold("Key Size:")} ${key.keySize}`);
83
+ logger.log(` ${chalk.bold("Hybrid:")} ${key.hybridMode}`);
84
+ if (key.classicalAlgorithm) {
85
+ logger.log(` ${chalk.bold("Classical:")} ${key.classicalAlgorithm}`);
86
+ }
87
+
88
+ await shutdown();
89
+ } catch (err) {
90
+ logger.error(`Failed: ${err.message}`);
91
+ process.exit(1);
92
+ }
93
+ });
94
+
95
+ // pqc migration-status
96
+ pqc
97
+ .command("migration-status")
98
+ .description("Show PQC migration status")
99
+ .option("--json", "Output as JSON")
100
+ .action(async (options) => {
101
+ try {
102
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
103
+ if (!ctx.db) {
104
+ logger.error("Database not available");
105
+ process.exit(1);
106
+ }
107
+ const db = ctx.db.getDatabase();
108
+ ensurePQCTables(db);
109
+
110
+ const plans = getMigrationStatus();
111
+ if (options.json) {
112
+ console.log(JSON.stringify(plans, null, 2));
113
+ } else if (plans.length === 0) {
114
+ logger.info("No migration plans.");
115
+ } else {
116
+ for (const p of plans) {
117
+ logger.log(
118
+ ` ${chalk.cyan(p.id.slice(0, 8))} ${p.planName} ${p.sourceAlgorithm}→${p.targetAlgorithm} [${p.status}] ${p.migratedKeys}/${p.totalKeys}`,
119
+ );
120
+ }
121
+ }
122
+
123
+ await shutdown();
124
+ } catch (err) {
125
+ logger.error(`Failed: ${err.message}`);
126
+ process.exit(1);
127
+ }
128
+ });
129
+
130
+ // pqc migrate
131
+ pqc
132
+ .command("migrate <plan-name> <target-algorithm>")
133
+ .description("Execute PQC key migration")
134
+ .option("-s, --source <algorithm>", "Source algorithm to migrate from")
135
+ .action(async (planName, targetAlgorithm, options) => {
136
+ try {
137
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
138
+ if (!ctx.db) {
139
+ logger.error("Database not available");
140
+ process.exit(1);
141
+ }
142
+ const db = ctx.db.getDatabase();
143
+ ensurePQCTables(db);
144
+
145
+ const plan = migrate(db, planName, options.source, targetAlgorithm);
146
+ logger.success("Migration completed");
147
+ logger.log(` ${chalk.bold("ID:")} ${chalk.cyan(plan.id)}`);
148
+ logger.log(` ${chalk.bold("Plan:")} ${plan.planName}`);
149
+ logger.log(` ${chalk.bold("Source:")} ${plan.sourceAlgorithm}`);
150
+ logger.log(` ${chalk.bold("Target:")} ${plan.targetAlgorithm}`);
151
+ logger.log(
152
+ ` ${chalk.bold("Migrated:")} ${plan.migratedKeys}/${plan.totalKeys}`,
153
+ );
154
+ logger.log(` ${chalk.bold("Status:")} ${plan.status}`);
155
+
156
+ await shutdown();
157
+ } catch (err) {
158
+ logger.error(`Failed: ${err.message}`);
159
+ process.exit(1);
160
+ }
161
+ });
162
+ }
@@ -0,0 +1,218 @@
1
+ /**
2
+ * SCIM commands
3
+ * chainlesschain scim users|connectors|sync|status
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
+ ensureSCIMTables,
11
+ listUsers,
12
+ createUser,
13
+ getUser,
14
+ deleteUser,
15
+ listConnectors,
16
+ addConnector,
17
+ syncProvision,
18
+ getStatus,
19
+ } from "../lib/scim-manager.js";
20
+
21
+ export function registerScimCommand(program) {
22
+ const scim = program
23
+ .command("scim")
24
+ .description("SCIM provisioning — user management, connectors, sync");
25
+
26
+ // scim users
27
+ const users = scim.command("users").description("SCIM user management");
28
+
29
+ users
30
+ .command("list")
31
+ .description("List SCIM users")
32
+ .option("--json", "Output as JSON")
33
+ .action(async (options) => {
34
+ try {
35
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
36
+ if (!ctx.db) {
37
+ logger.error("Database not available");
38
+ process.exit(1);
39
+ }
40
+ const db = ctx.db.getDatabase();
41
+ ensureSCIMTables(db);
42
+
43
+ const result = listUsers();
44
+ if (options.json) {
45
+ console.log(JSON.stringify(result, null, 2));
46
+ } else if (result.resources.length === 0) {
47
+ logger.info("No SCIM users.");
48
+ } else {
49
+ for (const u of result.resources) {
50
+ logger.log(
51
+ ` ${chalk.cyan(u.id.slice(0, 8))} ${u.userName} (${u.displayName}) active=${u.active}`,
52
+ );
53
+ }
54
+ logger.log(`\n${result.totalResults} user(s) total.`);
55
+ }
56
+ await shutdown();
57
+ } catch (err) {
58
+ logger.error(`Failed: ${err.message}`);
59
+ process.exit(1);
60
+ }
61
+ });
62
+
63
+ users
64
+ .command("create <username>")
65
+ .description("Create a SCIM user")
66
+ .option("-n, --name <display-name>", "Display name")
67
+ .option("-e, --email <email>", "Email address")
68
+ .action(async (username, options) => {
69
+ try {
70
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
71
+ if (!ctx.db) {
72
+ logger.error("Database not available");
73
+ process.exit(1);
74
+ }
75
+ const db = ctx.db.getDatabase();
76
+ ensureSCIMTables(db);
77
+
78
+ const user = createUser(db, username, options.name, options.email);
79
+ logger.success("User created");
80
+ logger.log(` ${chalk.bold("ID:")} ${chalk.cyan(user.id)}`);
81
+ logger.log(` ${chalk.bold("Username:")} ${user.userName}`);
82
+ logger.log(` ${chalk.bold("Name:")} ${user.displayName}`);
83
+ await shutdown();
84
+ } catch (err) {
85
+ logger.error(`Failed: ${err.message}`);
86
+ process.exit(1);
87
+ }
88
+ });
89
+
90
+ users
91
+ .command("get <user-id>")
92
+ .description("Get a SCIM user by ID")
93
+ .option("--json", "Output as JSON")
94
+ .action(async (userId, options) => {
95
+ try {
96
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
97
+ if (!ctx.db) {
98
+ logger.error("Database not available");
99
+ process.exit(1);
100
+ }
101
+ const db = ctx.db.getDatabase();
102
+ ensureSCIMTables(db);
103
+
104
+ const user = getUser(userId);
105
+ if (!user) {
106
+ logger.error(`User not found: ${userId}`);
107
+ process.exit(1);
108
+ }
109
+ if (options.json) {
110
+ console.log(JSON.stringify(user, null, 2));
111
+ } else {
112
+ logger.log(` ${chalk.bold("ID:")} ${chalk.cyan(user.id)}`);
113
+ logger.log(` ${chalk.bold("Username:")} ${user.userName}`);
114
+ logger.log(` ${chalk.bold("Name:")} ${user.displayName}`);
115
+ logger.log(` ${chalk.bold("Email:")} ${user.email || "N/A"}`);
116
+ logger.log(` ${chalk.bold("Active:")} ${user.active}`);
117
+ }
118
+ await shutdown();
119
+ } catch (err) {
120
+ logger.error(`Failed: ${err.message}`);
121
+ process.exit(1);
122
+ }
123
+ });
124
+
125
+ users
126
+ .command("delete <user-id>")
127
+ .description("Delete a SCIM user")
128
+ .action(async (userId) => {
129
+ try {
130
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
131
+ if (!ctx.db) {
132
+ logger.error("Database not available");
133
+ process.exit(1);
134
+ }
135
+ const db = ctx.db.getDatabase();
136
+ ensureSCIMTables(db);
137
+
138
+ deleteUser(db, userId);
139
+ logger.success(`User ${chalk.cyan(userId.slice(0, 8))} deleted`);
140
+ await shutdown();
141
+ } catch (err) {
142
+ logger.error(`Failed: ${err.message}`);
143
+ process.exit(1);
144
+ }
145
+ });
146
+
147
+ // scim connectors
148
+ scim
149
+ .command("connectors")
150
+ .description("List SCIM connectors")
151
+ .option("--json", "Output as JSON")
152
+ .action(async (options) => {
153
+ try {
154
+ const connectors = listConnectors();
155
+ if (options.json) {
156
+ console.log(JSON.stringify(connectors, null, 2));
157
+ } else if (connectors.length === 0) {
158
+ logger.info("No SCIM connectors configured.");
159
+ } else {
160
+ for (const c of connectors) {
161
+ logger.log(
162
+ ` ${chalk.cyan(c.id.slice(0, 8))} ${c.name} [${c.provider}] status=${c.status}`,
163
+ );
164
+ }
165
+ }
166
+ } catch (err) {
167
+ logger.error(`Failed: ${err.message}`);
168
+ process.exit(1);
169
+ }
170
+ });
171
+
172
+ // scim sync
173
+ scim
174
+ .command("sync <connector-id>")
175
+ .description("Trigger SCIM provisioning sync")
176
+ .action(async (connectorId) => {
177
+ try {
178
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
179
+ if (!ctx.db) {
180
+ logger.error("Database not available");
181
+ process.exit(1);
182
+ }
183
+ const db = ctx.db.getDatabase();
184
+ ensureSCIMTables(db);
185
+
186
+ const result = syncProvision(db, connectorId);
187
+ logger.success(`Sync completed via ${chalk.cyan(result.connector)}`);
188
+ await shutdown();
189
+ } catch (err) {
190
+ logger.error(`Failed: ${err.message}`);
191
+ process.exit(1);
192
+ }
193
+ });
194
+
195
+ // scim status
196
+ scim
197
+ .command("status")
198
+ .description("Show SCIM provisioning status")
199
+ .option("--json", "Output as JSON")
200
+ .action(async (options) => {
201
+ try {
202
+ const status = getStatus();
203
+ if (options.json) {
204
+ console.log(JSON.stringify(status, null, 2));
205
+ } else {
206
+ logger.log(` ${chalk.bold("Users:")} ${status.users}`);
207
+ logger.log(` ${chalk.bold("Connectors:")} ${status.connectors}`);
208
+ logger.log(` ${chalk.bold("Sync Ops:")} ${status.syncOperations}`);
209
+ logger.log(
210
+ ` ${chalk.bold("Last Sync:")} ${status.lastSync || "never"}`,
211
+ );
212
+ }
213
+ } catch (err) {
214
+ logger.error(`Failed: ${err.message}`);
215
+ process.exit(1);
216
+ }
217
+ });
218
+ }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * serve command — start a WebSocket server for remote CLI access
3
+ * chainlesschain serve [--port] [--host] [--token] [--max-connections] [--timeout] [--allow-remote]
4
+ */
5
+
6
+ import chalk from "chalk";
7
+ import { logger } from "../lib/logger.js";
8
+ import { ChainlessChainWSServer } from "../lib/ws-server.js";
9
+
10
+ export function registerServeCommand(program) {
11
+ program
12
+ .command("serve")
13
+ .description("Start WebSocket server for remote CLI access")
14
+ .option("-p, --port <port>", "Port number", "18800")
15
+ .option("-H, --host <host>", "Bind host", "127.0.0.1")
16
+ .option(
17
+ "--token <token>",
18
+ "Authentication token (required for remote access)",
19
+ )
20
+ .option("--max-connections <n>", "Maximum concurrent connections", "10")
21
+ .option(
22
+ "--timeout <ms>",
23
+ "Command execution timeout in milliseconds",
24
+ "30000",
25
+ )
26
+ .option(
27
+ "--allow-remote",
28
+ "Allow non-localhost connections (requires --token)",
29
+ )
30
+ .action(async (opts) => {
31
+ const port = parseInt(opts.port, 10);
32
+ const maxConnections = parseInt(opts.maxConnections, 10);
33
+ const timeout = parseInt(opts.timeout, 10);
34
+ let host = opts.host;
35
+
36
+ // Validation
37
+ if (isNaN(port) || port < 1 || port > 65535) {
38
+ logger.error("Invalid port number. Must be between 1 and 65535.");
39
+ process.exit(1);
40
+ }
41
+
42
+ if (opts.allowRemote) {
43
+ if (!opts.token) {
44
+ logger.error("--allow-remote requires --token for security.");
45
+ process.exit(1);
46
+ }
47
+ host = "0.0.0.0";
48
+ }
49
+
50
+ const server = new ChainlessChainWSServer({
51
+ port,
52
+ host,
53
+ token: opts.token || null,
54
+ maxConnections,
55
+ timeout,
56
+ });
57
+
58
+ // Event logging
59
+ server.on("connection", ({ clientId, ip }) => {
60
+ logger.log(chalk.green(` + Client connected: ${clientId} (${ip})`));
61
+ });
62
+
63
+ server.on("disconnection", ({ clientId, reason }) => {
64
+ const extra = reason ? ` (${reason})` : "";
65
+ logger.log(
66
+ chalk.yellow(` - Client disconnected: ${clientId}${extra}`),
67
+ );
68
+ });
69
+
70
+ server.on("command:start", ({ id, command }) => {
71
+ logger.log(chalk.cyan(` > [${id}] ${command}`));
72
+ });
73
+
74
+ server.on("command:end", ({ id, exitCode }) => {
75
+ const color = exitCode === 0 ? chalk.green : chalk.red;
76
+ logger.log(color(` < [${id}] exit ${exitCode}`));
77
+ });
78
+
79
+ // Graceful shutdown
80
+ const shutdown = async () => {
81
+ logger.log("\n" + chalk.yellow("Shutting down WebSocket server..."));
82
+ await server.stop();
83
+ process.exit(0);
84
+ };
85
+
86
+ process.on("SIGINT", shutdown);
87
+ process.on("SIGTERM", shutdown);
88
+
89
+ try {
90
+ await server.start();
91
+
92
+ logger.log("");
93
+ logger.log(chalk.bold(" ChainlessChain WebSocket Server"));
94
+ logger.log("");
95
+ logger.log(` Address: ${chalk.cyan(`ws://${host}:${port}`)}`);
96
+ logger.log(
97
+ ` Auth: ${opts.token ? chalk.green("enabled") : chalk.yellow("disabled")}`,
98
+ );
99
+ logger.log(` Max conn: ${maxConnections}`);
100
+ logger.log(` Timeout: ${timeout}ms`);
101
+ logger.log("");
102
+ logger.log(chalk.dim(" Press Ctrl+C to stop"));
103
+ logger.log("");
104
+ } catch (err) {
105
+ logger.error(`Failed to start server: ${err.message}`);
106
+ process.exit(1);
107
+ }
108
+ });
109
+ }
@@ -0,0 +1,156 @@
1
+ /**
2
+ * SIEM commands
3
+ * chainlesschain siem targets|add-target|export|stats
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
+ ensureSIEMTables,
11
+ listTargets,
12
+ addTarget,
13
+ exportLogs,
14
+ getSIEMStats,
15
+ } from "../lib/siem-exporter.js";
16
+
17
+ export function registerSiemCommand(program) {
18
+ const siem = program
19
+ .command("siem")
20
+ .description("SIEM integration — log export to external targets");
21
+
22
+ // siem targets
23
+ siem
24
+ .command("targets")
25
+ .description("List SIEM export targets")
26
+ .option("--json", "Output as JSON")
27
+ .action(async (options) => {
28
+ try {
29
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
30
+ if (!ctx.db) {
31
+ logger.error("Database not available");
32
+ process.exit(1);
33
+ }
34
+ const db = ctx.db.getDatabase();
35
+ ensureSIEMTables(db);
36
+
37
+ const targets = listTargets();
38
+ if (options.json) {
39
+ console.log(JSON.stringify(targets, null, 2));
40
+ } else if (targets.length === 0) {
41
+ logger.info("No SIEM targets configured.");
42
+ } else {
43
+ for (const t of targets) {
44
+ logger.log(
45
+ ` ${chalk.cyan(t.id.slice(0, 8))} ${t.type} → ${t.url} [${t.format}] exported=${t.exportedCount}`,
46
+ );
47
+ }
48
+ }
49
+
50
+ await shutdown();
51
+ } catch (err) {
52
+ logger.error(`Failed: ${err.message}`);
53
+ process.exit(1);
54
+ }
55
+ });
56
+
57
+ // siem add-target
58
+ siem
59
+ .command("add-target <type> <url>")
60
+ .description(
61
+ "Add a SIEM target (splunk_hec, elasticsearch, azure_sentinel)",
62
+ )
63
+ .option("-f, --format <fmt>", "Export format: json, cef, leef", "json")
64
+ .action(async (type, url, options) => {
65
+ try {
66
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
67
+ if (!ctx.db) {
68
+ logger.error("Database not available");
69
+ process.exit(1);
70
+ }
71
+ const db = ctx.db.getDatabase();
72
+ ensureSIEMTables(db);
73
+
74
+ const target = addTarget(db, type, url, options.format);
75
+ logger.success("SIEM target added");
76
+ logger.log(` ${chalk.bold("ID:")} ${chalk.cyan(target.id)}`);
77
+ logger.log(` ${chalk.bold("Type:")} ${target.type}`);
78
+ logger.log(` ${chalk.bold("URL:")} ${target.url}`);
79
+ logger.log(` ${chalk.bold("Format:")} ${target.format}`);
80
+
81
+ await shutdown();
82
+ } catch (err) {
83
+ logger.error(`Failed: ${err.message}`);
84
+ process.exit(1);
85
+ }
86
+ });
87
+
88
+ // siem export
89
+ siem
90
+ .command("export <target-id>")
91
+ .description("Export logs to a SIEM target")
92
+ .action(async (targetId) => {
93
+ try {
94
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
95
+ if (!ctx.db) {
96
+ logger.error("Database not available");
97
+ process.exit(1);
98
+ }
99
+ const db = ctx.db.getDatabase();
100
+ ensureSIEMTables(db);
101
+
102
+ // Export with simulated log batch
103
+ const result = exportLogs(db, targetId, [
104
+ {
105
+ id: `log-${Date.now()}`,
106
+ level: "info",
107
+ message: "CLI export test",
108
+ },
109
+ ]);
110
+ logger.success(`Exported ${result.exported} log(s)`);
111
+ logger.log(
112
+ ` ${chalk.bold("Last ID:")} ${chalk.cyan(result.lastId || "N/A")}`,
113
+ );
114
+
115
+ await shutdown();
116
+ } catch (err) {
117
+ logger.error(`Failed: ${err.message}`);
118
+ process.exit(1);
119
+ }
120
+ });
121
+
122
+ // siem stats
123
+ siem
124
+ .command("stats")
125
+ .description("Show SIEM export statistics")
126
+ .option("--json", "Output as JSON")
127
+ .action(async (options) => {
128
+ try {
129
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
130
+ if (!ctx.db) {
131
+ logger.error("Database not available");
132
+ process.exit(1);
133
+ }
134
+ const db = ctx.db.getDatabase();
135
+ ensureSIEMTables(db);
136
+
137
+ const stats = getSIEMStats();
138
+ if (options.json) {
139
+ console.log(JSON.stringify(stats, null, 2));
140
+ } else if (stats.length === 0) {
141
+ logger.info("No SIEM targets configured.");
142
+ } else {
143
+ for (const s of stats) {
144
+ logger.log(
145
+ ` ${chalk.cyan(s.id.slice(0, 8))} ${s.type} [${s.format}] exported=${s.exportedCount} status=${s.status}`,
146
+ );
147
+ }
148
+ }
149
+
150
+ await shutdown();
151
+ } catch (err) {
152
+ logger.error(`Failed: ${err.message}`);
153
+ process.exit(1);
154
+ }
155
+ });
156
+ }