chainlesschain 0.37.10 → 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 (39) hide show
  1. package/README.md +10 -10
  2. package/package.json +1 -1
  3. package/src/commands/a2a.js +374 -0
  4. package/src/commands/bi.js +240 -0
  5. package/src/commands/cowork.js +317 -0
  6. package/src/commands/economy.js +375 -0
  7. package/src/commands/evolution.js +398 -0
  8. package/src/commands/hmemory.js +273 -0
  9. package/src/commands/hook.js +260 -0
  10. package/src/commands/init.js +184 -0
  11. package/src/commands/lowcode.js +320 -0
  12. package/src/commands/plugin.js +55 -2
  13. package/src/commands/sandbox.js +366 -0
  14. package/src/commands/skill.js +254 -201
  15. package/src/commands/workflow.js +359 -0
  16. package/src/commands/zkp.js +277 -0
  17. package/src/index.js +44 -0
  18. package/src/lib/a2a-protocol.js +371 -0
  19. package/src/lib/agent-coordinator.js +273 -0
  20. package/src/lib/agent-economy.js +369 -0
  21. package/src/lib/app-builder.js +377 -0
  22. package/src/lib/bi-engine.js +299 -0
  23. package/src/lib/cowork/ab-comparator-cli.js +180 -0
  24. package/src/lib/cowork/code-knowledge-graph-cli.js +232 -0
  25. package/src/lib/cowork/debate-review-cli.js +144 -0
  26. package/src/lib/cowork/decision-kb-cli.js +153 -0
  27. package/src/lib/cowork/project-style-analyzer-cli.js +168 -0
  28. package/src/lib/cowork-adapter.js +106 -0
  29. package/src/lib/evolution-system.js +508 -0
  30. package/src/lib/hierarchical-memory.js +471 -0
  31. package/src/lib/hook-manager.js +387 -0
  32. package/src/lib/plugin-manager.js +118 -0
  33. package/src/lib/project-detector.js +53 -0
  34. package/src/lib/sandbox-v2.js +503 -0
  35. package/src/lib/service-container.js +183 -0
  36. package/src/lib/skill-loader.js +274 -0
  37. package/src/lib/workflow-engine.js +503 -0
  38. package/src/lib/zkp-engine.js +241 -0
  39. package/src/repl/agent-repl.js +117 -112
package/README.md CHANGED
@@ -554,7 +554,7 @@ Configuration is stored at `~/.chainlesschain/config.json`. The CLI creates and
554
554
  ```bash
555
555
  cd packages/cli
556
556
  npm install
557
- npm test # Run all tests (903 tests across 47 files)
557
+ npm test # Run all tests (1009 tests across 55 files)
558
558
  npm run test:unit # Unit tests only
559
559
  npm run test:integration # Integration tests
560
560
  npm run test:e2e # End-to-end tests
@@ -562,15 +562,15 @@ npm run test:e2e # End-to-end tests
562
562
 
563
563
  ### Test Coverage
564
564
 
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** |
565
+ | Category | Files | Tests | Status |
566
+ | ------------------------ | ------ | -------- | --------------- |
567
+ | Unit — lib modules | 32 | 672 | All passing |
568
+ | Unit — commands | 2 | 43 | All passing |
569
+ | Unit — runtime | 1 | 6 | All passing |
570
+ | Integration | 3 | 7 | All passing |
571
+ | E2E | 10 | 109 | All passing |
572
+ | Core packages (external) | — | 118 | All passing |
573
+ | **CLI Total** | **55** | **1009** | **All passing** |
574
574
 
575
575
  ## License
576
576
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chainlesschain",
3
- "version": "0.37.10",
3
+ "version": "0.37.11",
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,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,240 @@
1
+ /**
2
+ * BI commands
3
+ * chainlesschain bi query|dashboard|report|schedule|anomaly|predict|templates
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
+ ensureBITables,
11
+ nlQuery,
12
+ createDashboard,
13
+ generateReport,
14
+ scheduleReport,
15
+ detectAnomaly,
16
+ predictTrend,
17
+ listTemplates,
18
+ } from "../lib/bi-engine.js";
19
+
20
+ export function registerBiCommand(program) {
21
+ const bi = program
22
+ .command("bi")
23
+ .description(
24
+ "Business intelligence — queries, dashboards, reports, analytics",
25
+ );
26
+
27
+ // bi query
28
+ bi.command("query <question>")
29
+ .description("Natural-language query (NL→SQL)")
30
+ .option("--json", "Output as JSON")
31
+ .action(async (question, options) => {
32
+ try {
33
+ const result = nlQuery(question);
34
+ if (options.json) {
35
+ console.log(JSON.stringify(result, null, 2));
36
+ } else {
37
+ logger.success("Query translated");
38
+ logger.log(
39
+ ` ${chalk.bold("SQL:")} ${chalk.cyan(result.generatedSQL)}`,
40
+ );
41
+ logger.log(` ${chalk.bold("Rows:")} ${result.rowCount}`);
42
+ logger.log(` ${chalk.bold("Visual:")} ${result.visualization.type}`);
43
+ }
44
+ } catch (err) {
45
+ logger.error(`Failed: ${err.message}`);
46
+ process.exit(1);
47
+ }
48
+ });
49
+
50
+ // bi dashboard
51
+ bi.command("dashboard <name>")
52
+ .description("Create a dashboard")
53
+ .option("--widgets <json>", "Widgets as JSON array")
54
+ .option("--layout <json>", "Layout as JSON")
55
+ .option("--json", "Output as JSON")
56
+ .action(async (name, options) => {
57
+ try {
58
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
59
+ if (!ctx.db) {
60
+ logger.error("Database not available");
61
+ process.exit(1);
62
+ }
63
+ const db = ctx.db.getDatabase();
64
+ ensureBITables(db);
65
+
66
+ const widgets = options.widgets ? JSON.parse(options.widgets) : [];
67
+ const layout = options.layout ? JSON.parse(options.layout) : undefined;
68
+ const dashboard = createDashboard(db, name, widgets, layout);
69
+
70
+ if (options.json) {
71
+ console.log(JSON.stringify(dashboard, null, 2));
72
+ } else {
73
+ logger.success(`Dashboard created: ${chalk.cyan(name)}`);
74
+ logger.log(` ${chalk.bold("ID:")} ${chalk.cyan(dashboard.id)}`);
75
+ logger.log(` ${chalk.bold("Widgets:")} ${dashboard.widgets.length}`);
76
+ }
77
+
78
+ await shutdown();
79
+ } catch (err) {
80
+ logger.error(`Failed: ${err.message}`);
81
+ process.exit(1);
82
+ }
83
+ });
84
+
85
+ // bi report
86
+ bi.command("report <name>")
87
+ .description("Generate a report")
88
+ .option("--format <pdf|excel>", "Report format", "pdf")
89
+ .option("--sections <csv>", "Comma-separated section names")
90
+ .option("--json", "Output as JSON")
91
+ .action(async (name, options) => {
92
+ try {
93
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
94
+ if (!ctx.db) {
95
+ logger.error("Database not available");
96
+ process.exit(1);
97
+ }
98
+ const db = ctx.db.getDatabase();
99
+ ensureBITables(db);
100
+
101
+ const sections = options.sections
102
+ ? options.sections.split(",").map((s) => s.trim())
103
+ : undefined;
104
+ const report = generateReport(db, name, {
105
+ format: options.format,
106
+ sections,
107
+ });
108
+
109
+ if (options.json) {
110
+ console.log(JSON.stringify(report, null, 2));
111
+ } else {
112
+ logger.success(`Report generated: ${chalk.cyan(name)}`);
113
+ logger.log(` ${chalk.bold("ID:")} ${chalk.cyan(report.id)}`);
114
+ logger.log(` ${chalk.bold("Format:")} ${report.format}`);
115
+ logger.log(` ${chalk.bold("Sections:")} ${report.sections.length}`);
116
+ }
117
+
118
+ await shutdown();
119
+ } catch (err) {
120
+ logger.error(`Failed: ${err.message}`);
121
+ process.exit(1);
122
+ }
123
+ });
124
+
125
+ // bi schedule
126
+ bi.command("schedule <report-id> <cron>")
127
+ .description("Schedule a report for recurring generation")
128
+ .option("--recipients <csv>", "Comma-separated recipient emails")
129
+ .action(async (reportId, cron, options) => {
130
+ try {
131
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
132
+ if (!ctx.db) {
133
+ logger.error("Database not available");
134
+ process.exit(1);
135
+ }
136
+ const db = ctx.db.getDatabase();
137
+ ensureBITables(db);
138
+
139
+ const recipients = options.recipients
140
+ ? options.recipients.split(",").map((s) => s.trim())
141
+ : [];
142
+ const schedule = scheduleReport(db, reportId, cron, recipients);
143
+ logger.success("Report scheduled");
144
+ logger.log(` ${chalk.bold("ID:")} ${chalk.cyan(schedule.id)}`);
145
+ logger.log(` ${chalk.bold("Cron:")} ${schedule.cron}`);
146
+ logger.log(
147
+ ` ${chalk.bold("Recipients:")} ${schedule.recipients.length}`,
148
+ );
149
+
150
+ await shutdown();
151
+ } catch (err) {
152
+ logger.error(`Failed: ${err.message}`);
153
+ process.exit(1);
154
+ }
155
+ });
156
+
157
+ // bi anomaly
158
+ bi.command("anomaly")
159
+ .description("Detect anomalies in data using Z-score")
160
+ .option("--data <json>", "Data as JSON array of numbers")
161
+ .option("--threshold <n>", "Z-score threshold", "2")
162
+ .option("--json", "Output as JSON")
163
+ .action(async (options) => {
164
+ try {
165
+ const data = options.data ? JSON.parse(options.data) : [];
166
+ const result = detectAnomaly(data, {
167
+ threshold: parseFloat(options.threshold),
168
+ });
169
+
170
+ if (options.json) {
171
+ console.log(JSON.stringify(result, null, 2));
172
+ } else {
173
+ logger.log(` ${chalk.bold("Mean:")} ${result.mean.toFixed(2)}`);
174
+ logger.log(` ${chalk.bold("Std Dev:")} ${result.std.toFixed(2)}`);
175
+ logger.log(` ${chalk.bold("Threshold:")} ${result.threshold}`);
176
+ logger.log(
177
+ ` ${chalk.bold("Anomalies:")} ${result.anomalies.length}`,
178
+ );
179
+ for (const a of result.anomalies) {
180
+ logger.log(
181
+ ` [${a.index}] value=${a.value} z=${a.zScore.toFixed(2)}`,
182
+ );
183
+ }
184
+ }
185
+ } catch (err) {
186
+ logger.error(`Failed: ${err.message}`);
187
+ process.exit(1);
188
+ }
189
+ });
190
+
191
+ // bi predict
192
+ bi.command("predict")
193
+ .description("Predict trend using linear regression")
194
+ .option("--data <json>", "Data as JSON array of numbers")
195
+ .option("--periods <n>", "Number of periods to predict", "3")
196
+ .option("--json", "Output as JSON")
197
+ .action(async (options) => {
198
+ try {
199
+ const data = options.data ? JSON.parse(options.data) : [];
200
+ const result = predictTrend(data, parseInt(options.periods));
201
+
202
+ if (options.json) {
203
+ console.log(JSON.stringify(result, null, 2));
204
+ } else {
205
+ logger.log(
206
+ ` ${chalk.bold("Trend:")} ${chalk.cyan(result.trend)}`,
207
+ );
208
+ logger.log(` ${chalk.bold("Slope:")} ${result.slope}`);
209
+ logger.log(
210
+ ` ${chalk.bold("Predictions:")} ${result.predictions.join(", ")}`,
211
+ );
212
+ }
213
+ } catch (err) {
214
+ logger.error(`Failed: ${err.message}`);
215
+ process.exit(1);
216
+ }
217
+ });
218
+
219
+ // bi templates
220
+ bi.command("templates")
221
+ .description("List available BI templates")
222
+ .option("--json", "Output as JSON")
223
+ .action(async (options) => {
224
+ try {
225
+ const templates = listTemplates();
226
+ if (options.json) {
227
+ console.log(JSON.stringify(templates, null, 2));
228
+ } else {
229
+ for (const t of templates) {
230
+ logger.log(
231
+ ` ${chalk.cyan(t.id)} ${chalk.bold(t.name)} — ${t.description}`,
232
+ );
233
+ }
234
+ }
235
+ } catch (err) {
236
+ logger.error(`Failed: ${err.message}`);
237
+ process.exit(1);
238
+ }
239
+ });
240
+ }