chainlesschain 0.47.9 → 0.51.0

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 (73) hide show
  1. package/bin/chainlesschain.js +0 -0
  2. package/package.json +1 -1
  3. package/src/assets/web-panel/.build-hash +1 -1
  4. package/src/assets/web-panel/assets/{AppLayout-6SPt_8Y_.js → AppLayout-Rvi759IS.js} +1 -1
  5. package/src/assets/web-panel/assets/Dashboard-BS-tzGNj.css +1 -0
  6. package/src/assets/web-panel/assets/{Dashboard-Br7kCwKJ.js → Dashboard-DBhFxXYQ.js} +2 -2
  7. package/src/assets/web-panel/assets/{index-tN-8TosE.js → index-uL0cZ8N_.js} +2 -2
  8. package/src/assets/web-panel/index.html +2 -2
  9. package/src/commands/codegen.js +303 -0
  10. package/src/commands/collab.js +482 -0
  11. package/src/commands/crosschain.js +382 -0
  12. package/src/commands/dbevo.js +388 -0
  13. package/src/commands/dev.js +411 -0
  14. package/src/commands/federation.js +427 -0
  15. package/src/commands/fusion.js +332 -0
  16. package/src/commands/governance.js +505 -0
  17. package/src/commands/hardening.js +110 -0
  18. package/src/commands/incentive.js +373 -0
  19. package/src/commands/inference.js +304 -0
  20. package/src/commands/infra.js +361 -0
  21. package/src/commands/ipfs.js +392 -0
  22. package/src/commands/kg.js +371 -0
  23. package/src/commands/marketplace.js +326 -0
  24. package/src/commands/mcp.js +97 -18
  25. package/src/commands/multimodal.js +404 -0
  26. package/src/commands/nlprog.js +329 -0
  27. package/src/commands/ops.js +408 -0
  28. package/src/commands/perception.js +385 -0
  29. package/src/commands/pqc.js +34 -0
  30. package/src/commands/privacy.js +345 -0
  31. package/src/commands/quantization.js +280 -0
  32. package/src/commands/recommend.js +336 -0
  33. package/src/commands/reputation.js +349 -0
  34. package/src/commands/runtime.js +500 -0
  35. package/src/commands/sla.js +352 -0
  36. package/src/commands/stress.js +252 -0
  37. package/src/commands/tech.js +268 -0
  38. package/src/commands/tenant.js +576 -0
  39. package/src/commands/trust.js +366 -0
  40. package/src/harness/mcp-client.js +330 -54
  41. package/src/index.js +118 -0
  42. package/src/lib/aiops.js +523 -0
  43. package/src/lib/autonomous-developer.js +524 -0
  44. package/src/lib/code-agent.js +442 -0
  45. package/src/lib/collaboration-governance.js +556 -0
  46. package/src/lib/community-governance.js +649 -0
  47. package/src/lib/content-recommendation.js +600 -0
  48. package/src/lib/cross-chain.js +669 -0
  49. package/src/lib/dbevo.js +669 -0
  50. package/src/lib/decentral-infra.js +445 -0
  51. package/src/lib/federation-hardening.js +587 -0
  52. package/src/lib/hardening-manager.js +409 -0
  53. package/src/lib/inference-network.js +407 -0
  54. package/src/lib/ipfs-storage.js +575 -0
  55. package/src/lib/knowledge-graph.js +530 -0
  56. package/src/lib/mcp-client.js +3 -0
  57. package/src/lib/multimodal.js +725 -0
  58. package/src/lib/nl-programming.js +595 -0
  59. package/src/lib/perception.js +500 -0
  60. package/src/lib/pqc-manager.js +141 -9
  61. package/src/lib/privacy-computing.js +575 -0
  62. package/src/lib/protocol-fusion.js +535 -0
  63. package/src/lib/quantization.js +362 -0
  64. package/src/lib/reputation-optimizer.js +509 -0
  65. package/src/lib/skill-marketplace.js +397 -0
  66. package/src/lib/sla-manager.js +484 -0
  67. package/src/lib/stress-tester.js +383 -0
  68. package/src/lib/tech-learning-engine.js +651 -0
  69. package/src/lib/tenant-saas.js +831 -0
  70. package/src/lib/token-incentive.js +513 -0
  71. package/src/lib/trust-security.js +473 -0
  72. package/src/lib/universal-runtime.js +771 -0
  73. package/src/assets/web-panel/assets/Dashboard-CKeMmCoT.css +0 -1
@@ -0,0 +1,352 @@
1
+ /**
2
+ * SLA commands
3
+ * chainlesschain sla tiers|create|list|show|terminate|record|metrics|check|violations|compensate|report
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
+ ensureSlaTables,
11
+ listTiers,
12
+ createSLA,
13
+ listSLAs,
14
+ getSLA,
15
+ terminateSLA,
16
+ recordMetric,
17
+ getSLAMetrics,
18
+ checkViolations,
19
+ listViolations,
20
+ calculateCompensation,
21
+ generateReport,
22
+ SLA_TERMS,
23
+ VIOLATION_SEVERITY,
24
+ } from "../lib/sla-manager.js";
25
+
26
+ function _dbFromCtx(ctx) {
27
+ if (!ctx.db) {
28
+ logger.error("Database not available");
29
+ process.exit(1);
30
+ }
31
+ const db = ctx.db.getDatabase();
32
+ ensureSlaTables(db);
33
+ return db;
34
+ }
35
+
36
+ export function registerSlaCommand(program) {
37
+ const sla = program
38
+ .command("sla")
39
+ .description(
40
+ "Cross-org SLA management — contracts, metrics, violations, compensation",
41
+ );
42
+
43
+ sla
44
+ .command("tiers")
45
+ .description("List built-in SLA tiers")
46
+ .option("--json", "Output as JSON")
47
+ .action((options) => {
48
+ const tiers = listTiers();
49
+ if (options.json) {
50
+ console.log(JSON.stringify(tiers, null, 2));
51
+ } else {
52
+ for (const t of tiers) {
53
+ logger.log(
54
+ ` ${chalk.cyan(t.name.padEnd(8))} availability=${t.availability} p95<=${t.maxResponseTime}ms rps>=${t.minThroughput} comp=${(t.compensationRate * 100).toFixed(1)}%`,
55
+ );
56
+ }
57
+ }
58
+ });
59
+
60
+ sla
61
+ .command("create <org-id>")
62
+ .description("Create a new SLA contract")
63
+ .option("-t, --tier <tier>", "SLA tier (gold|silver|bronze)", "silver")
64
+ .option("-d, --duration <ms>", "Contract duration in ms", parseInt)
65
+ .option("-f, --fee <amount>", "Monthly fee", parseFloat, 0)
66
+ .option("--json", "Output as JSON")
67
+ .action(async (orgId, options) => {
68
+ try {
69
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
70
+ const db = _dbFromCtx(ctx);
71
+ const contract = createSLA(db, {
72
+ orgId,
73
+ tier: options.tier,
74
+ duration: options.duration,
75
+ monthlyFee: options.fee,
76
+ });
77
+ if (options.json) {
78
+ console.log(JSON.stringify(contract, null, 2));
79
+ } else {
80
+ logger.success(`SLA contract created`);
81
+ logger.log(
82
+ ` ${chalk.bold("SLA ID:")} ${chalk.cyan(contract.slaId)}`,
83
+ );
84
+ logger.log(` ${chalk.bold("Org:")} ${contract.orgId}`);
85
+ logger.log(` ${chalk.bold("Tier:")} ${contract.tier}`);
86
+ logger.log(` ${chalk.bold("Fee:")} ${contract.monthlyFee}/month`);
87
+ logger.log(
88
+ ` ${chalk.bold("Expires:")} ${new Date(contract.endDate).toISOString()}`,
89
+ );
90
+ }
91
+ await shutdown();
92
+ } catch (err) {
93
+ logger.error(`Failed: ${err.message}`);
94
+ process.exit(1);
95
+ }
96
+ });
97
+
98
+ sla
99
+ .command("list")
100
+ .description("List SLA contracts")
101
+ .option("-o, --org <id>", "Filter by orgId")
102
+ .option("-t, --tier <tier>", "Filter by tier")
103
+ .option(
104
+ "-s, --status <status>",
105
+ "Filter by status (active|expired|terminated)",
106
+ )
107
+ .option("--limit <n>", "Maximum entries", parseInt, 50)
108
+ .option("--json", "Output as JSON")
109
+ .action(async (options) => {
110
+ try {
111
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
112
+ _dbFromCtx(ctx);
113
+ const rows = listSLAs({
114
+ orgId: options.org,
115
+ tier: options.tier,
116
+ status: options.status,
117
+ limit: options.limit,
118
+ });
119
+ if (options.json) {
120
+ console.log(JSON.stringify(rows, null, 2));
121
+ } else if (rows.length === 0) {
122
+ logger.info("No SLA contracts recorded.");
123
+ } else {
124
+ for (const r of rows) {
125
+ logger.log(
126
+ ` ${chalk.cyan(r.slaId.slice(0, 8))} org=${r.orgId.padEnd(16)} tier=${r.tier.padEnd(7)} [${r.status}]`,
127
+ );
128
+ }
129
+ }
130
+ await shutdown();
131
+ } catch (err) {
132
+ logger.error(`Failed: ${err.message}`);
133
+ process.exit(1);
134
+ }
135
+ });
136
+
137
+ sla
138
+ .command("show <sla-id>")
139
+ .description("Show a single SLA contract")
140
+ .option("--json", "Output as JSON")
141
+ .action(async (slaId, options) => {
142
+ try {
143
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
144
+ _dbFromCtx(ctx);
145
+ const r = getSLA(slaId);
146
+ if (options.json) {
147
+ console.log(JSON.stringify(r, null, 2));
148
+ } else {
149
+ logger.log(` ${chalk.bold("SLA ID:")} ${chalk.cyan(r.slaId)}`);
150
+ logger.log(` ${chalk.bold("Org:")} ${r.orgId}`);
151
+ logger.log(` ${chalk.bold("Tier:")} ${r.tier}`);
152
+ logger.log(` ${chalk.bold("Status:")} ${r.status}`);
153
+ logger.log(` ${chalk.bold("Terms:")} ${JSON.stringify(r.terms)}`);
154
+ }
155
+ await shutdown();
156
+ } catch (err) {
157
+ logger.error(`Failed: ${err.message}`);
158
+ process.exit(1);
159
+ }
160
+ });
161
+
162
+ sla
163
+ .command("terminate <sla-id>")
164
+ .description("Terminate an SLA contract")
165
+ .action(async (slaId) => {
166
+ try {
167
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
168
+ const db = _dbFromCtx(ctx);
169
+ const r = terminateSLA(db, slaId);
170
+ logger.success(`SLA ${slaId.slice(0, 8)} → ${r.status}`);
171
+ await shutdown();
172
+ } catch (err) {
173
+ logger.error(`Failed: ${err.message}`);
174
+ process.exit(1);
175
+ }
176
+ });
177
+
178
+ sla
179
+ .command("record <sla-id> <term> <value>")
180
+ .description(
181
+ `Record a metric (term: ${Object.values(SLA_TERMS).join("|")})`,
182
+ )
183
+ .option("--json", "Output as JSON")
184
+ .action(async (slaId, term, value, options) => {
185
+ try {
186
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
187
+ const db = _dbFromCtx(ctx);
188
+ const m = recordMetric(db, slaId, term, Number(value));
189
+ if (options.json) {
190
+ console.log(JSON.stringify(m, null, 2));
191
+ } else {
192
+ logger.success(`Metric recorded`);
193
+ logger.log(` ${chalk.bold("Term:")} ${m.term}`);
194
+ logger.log(` ${chalk.bold("Value:")} ${m.value}`);
195
+ }
196
+ await shutdown();
197
+ } catch (err) {
198
+ logger.error(`Failed: ${err.message}`);
199
+ process.exit(1);
200
+ }
201
+ });
202
+
203
+ sla
204
+ .command("metrics <sla-id>")
205
+ .description("Show aggregated metrics for an SLA")
206
+ .option("--json", "Output as JSON")
207
+ .action(async (slaId, options) => {
208
+ try {
209
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
210
+ _dbFromCtx(ctx);
211
+ const m = getSLAMetrics(slaId);
212
+ if (options.json) {
213
+ console.log(JSON.stringify(m, null, 2));
214
+ } else {
215
+ logger.log(` ${chalk.bold("Samples:")} ${m.totalSamples}`);
216
+ for (const [term, agg] of Object.entries(m.byTerm)) {
217
+ logger.log(
218
+ ` ${chalk.cyan(term.padEnd(15))} mean=${agg.mean.toFixed(4)} p95=${agg.p95.toFixed(4)} (n=${agg.count})`,
219
+ );
220
+ }
221
+ }
222
+ await shutdown();
223
+ } catch (err) {
224
+ logger.error(`Failed: ${err.message}`);
225
+ process.exit(1);
226
+ }
227
+ });
228
+
229
+ sla
230
+ .command("check <sla-id>")
231
+ .description("Detect violations against the contract's terms")
232
+ .option("--json", "Output as JSON")
233
+ .action(async (slaId, options) => {
234
+ try {
235
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
236
+ const db = _dbFromCtx(ctx);
237
+ const r = checkViolations(db, slaId);
238
+ if (options.json) {
239
+ console.log(JSON.stringify(r, null, 2));
240
+ } else if (r.totalViolations === 0) {
241
+ logger.success("No violations detected.");
242
+ } else {
243
+ logger.warn(`${r.totalViolations} violation(s) detected`);
244
+ for (const v of r.violations) {
245
+ logger.log(
246
+ ` ${chalk.yellow(v.term.padEnd(15))} [${v.severity}] expected=${v.expectedValue} actual=${v.actualValue} dev=${v.deviationPercent}%`,
247
+ );
248
+ }
249
+ }
250
+ await shutdown();
251
+ } catch (err) {
252
+ logger.error(`Failed: ${err.message}`);
253
+ process.exit(1);
254
+ }
255
+ });
256
+
257
+ sla
258
+ .command("violations")
259
+ .description("List recorded violations")
260
+ .option("-s, --sla <id>", "Filter by SLA ID")
261
+ .option(
262
+ "-S, --severity <level>",
263
+ `Filter by severity (${Object.values(VIOLATION_SEVERITY).join("|")})`,
264
+ )
265
+ .option("--limit <n>", "Maximum entries", parseInt, 50)
266
+ .option("--json", "Output as JSON")
267
+ .action(async (options) => {
268
+ try {
269
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
270
+ _dbFromCtx(ctx);
271
+ const rows = listViolations({
272
+ slaId: options.sla,
273
+ severity: options.severity,
274
+ limit: options.limit,
275
+ });
276
+ if (options.json) {
277
+ console.log(JSON.stringify(rows, null, 2));
278
+ } else if (rows.length === 0) {
279
+ logger.info("No violations recorded.");
280
+ } else {
281
+ for (const v of rows) {
282
+ logger.log(
283
+ ` ${chalk.cyan(v.violationId.slice(0, 8))} ${v.term.padEnd(15)} [${v.severity}] dev=${v.deviationPercent}%`,
284
+ );
285
+ }
286
+ }
287
+ await shutdown();
288
+ } catch (err) {
289
+ logger.error(`Failed: ${err.message}`);
290
+ process.exit(1);
291
+ }
292
+ });
293
+
294
+ sla
295
+ .command("compensate <violation-id>")
296
+ .description("Calculate compensation for a violation")
297
+ .option("--json", "Output as JSON")
298
+ .action(async (violationId, options) => {
299
+ try {
300
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
301
+ const db = _dbFromCtx(ctx);
302
+ const r = calculateCompensation(db, violationId);
303
+ if (options.json) {
304
+ console.log(JSON.stringify(r, null, 2));
305
+ } else {
306
+ logger.log(` ${chalk.bold("Base:")} ${r.base}`);
307
+ logger.log(` ${chalk.bold("Multiplier:")} ${r.multiplier}`);
308
+ logger.log(` ${chalk.bold("Amount:")} ${chalk.green(r.amount)}`);
309
+ }
310
+ await shutdown();
311
+ } catch (err) {
312
+ logger.error(`Failed: ${err.message}`);
313
+ process.exit(1);
314
+ }
315
+ });
316
+
317
+ sla
318
+ .command("report <sla-id>")
319
+ .description("Generate an SLA compliance report")
320
+ .option("--start <ms>", "Start of window (ms since epoch)", parseInt)
321
+ .option("--end <ms>", "End of window (ms since epoch)", parseInt)
322
+ .option("--json", "Output as JSON")
323
+ .action(async (slaId, options) => {
324
+ try {
325
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
326
+ _dbFromCtx(ctx);
327
+ const r = generateReport(slaId, {
328
+ startDate: options.start,
329
+ endDate: options.end,
330
+ });
331
+ if (options.json) {
332
+ console.log(JSON.stringify(r, null, 2));
333
+ } else {
334
+ logger.log(` ${chalk.bold("SLA:")} ${chalk.cyan(r.slaId)}`);
335
+ logger.log(` ${chalk.bold("Tier:")} ${r.tier}`);
336
+ logger.log(
337
+ ` ${chalk.bold("Compliance:")} ${(r.compliance * 100).toFixed(2)}%`,
338
+ );
339
+ logger.log(
340
+ ` ${chalk.bold("Violations:")} ${r.violations.total} (comp=${r.violations.totalCompensation})`,
341
+ );
342
+ for (const [sev, n] of Object.entries(r.violations.bySeverity)) {
343
+ if (n > 0) logger.log(` ${chalk.yellow(sev.padEnd(10))} ${n}`);
344
+ }
345
+ }
346
+ await shutdown();
347
+ } catch (err) {
348
+ logger.error(`Failed: ${err.message}`);
349
+ process.exit(1);
350
+ }
351
+ });
352
+ }
@@ -0,0 +1,252 @@
1
+ /**
2
+ * Stress commands
3
+ * chainlesschain stress run|list|show|analyze|plan|stop|levels
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
+ ensureStressTables,
11
+ startStressTest,
12
+ stopStressTest,
13
+ getTestResults,
14
+ listTestHistory,
15
+ analyzeBottlenecks,
16
+ generateCapacityPlan,
17
+ listLoadLevels,
18
+ } from "../lib/stress-tester.js";
19
+
20
+ function _dbFromCtx(ctx) {
21
+ if (!ctx.db) {
22
+ logger.error("Database not available");
23
+ process.exit(1);
24
+ }
25
+ const db = ctx.db.getDatabase();
26
+ ensureStressTables(db);
27
+ return db;
28
+ }
29
+
30
+ export function registerStressCommand(program) {
31
+ const stress = program
32
+ .command("stress")
33
+ .description(
34
+ "Federation stress testing — load simulation, bottleneck & capacity planning",
35
+ );
36
+
37
+ stress
38
+ .command("run")
39
+ .description("Run a stress test at a given load level")
40
+ .option(
41
+ "-l, --level <level>",
42
+ "Load level (light|medium|heavy|extreme)",
43
+ "medium",
44
+ )
45
+ .option("-c, --concurrency <n>", "Override concurrency", parseInt)
46
+ .option("-r, --rps <n>", "Override requests per second", parseInt)
47
+ .option("-d, --duration <ms>", "Override duration in ms", parseInt)
48
+ .option("--json", "Output as JSON")
49
+ .action(async (options) => {
50
+ try {
51
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
52
+ const db = _dbFromCtx(ctx);
53
+ const run = startStressTest(db, {
54
+ level: options.level,
55
+ concurrency: options.concurrency,
56
+ requestsPerSecond: options.rps,
57
+ duration: options.duration,
58
+ });
59
+ if (options.json) {
60
+ console.log(JSON.stringify(run, null, 2));
61
+ } else {
62
+ logger.success(`Stress test ${run.status}`);
63
+ logger.log(
64
+ ` ${chalk.bold("ID:")} ${chalk.cyan(run.testId)}`,
65
+ );
66
+ logger.log(` ${chalk.bold("Level:")} ${run.loadLevel}`);
67
+ logger.log(
68
+ ` ${chalk.bold("Load:")} ${run.concurrency} concurrent, ${run.requestsPerSecond} rps, ${run.duration}ms`,
69
+ );
70
+ const m = run.result;
71
+ logger.log(` ${chalk.bold("TPS:")} ${m.tps}`);
72
+ logger.log(
73
+ ` ${chalk.bold("Latency:")} p50=${m.p50ResponseTime}ms p95=${m.p95ResponseTime}ms p99=${m.p99ResponseTime}ms`,
74
+ );
75
+ logger.log(
76
+ ` ${chalk.bold("Error rate:")} ${(m.errorRate * 100).toFixed(2)}%`,
77
+ );
78
+ if (m.bottlenecks.length) {
79
+ logger.log(chalk.yellow(` Bottlenecks:`));
80
+ for (const b of m.bottlenecks) {
81
+ logger.log(
82
+ ` ${chalk.yellow("•")} ${b.kind} (${b.severity}): ${b.detail}`,
83
+ );
84
+ }
85
+ }
86
+ }
87
+ await shutdown();
88
+ } catch (err) {
89
+ logger.error(`Failed: ${err.message}`);
90
+ process.exit(1);
91
+ }
92
+ });
93
+
94
+ stress
95
+ .command("list")
96
+ .description("List stress test history")
97
+ .option("-l, --level <level>", "Filter by load level")
98
+ .option(
99
+ "-s, --status <status>",
100
+ "Filter by status (running|complete|stopped)",
101
+ )
102
+ .option("--limit <n>", "Maximum entries", parseInt, 10)
103
+ .option("--json", "Output as JSON")
104
+ .action(async (options) => {
105
+ try {
106
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
107
+ _dbFromCtx(ctx);
108
+ const rows = listTestHistory({
109
+ level: options.level,
110
+ status: options.status,
111
+ limit: options.limit,
112
+ });
113
+ if (options.json) {
114
+ console.log(JSON.stringify(rows, null, 2));
115
+ } else if (rows.length === 0) {
116
+ logger.info("No stress tests recorded.");
117
+ } else {
118
+ for (const r of rows) {
119
+ logger.log(
120
+ ` ${chalk.cyan(r.testId.slice(0, 8))} ${r.loadLevel.padEnd(8)} [${r.status}] c=${r.concurrency} rps=${r.requestsPerSecond}`,
121
+ );
122
+ }
123
+ }
124
+ await shutdown();
125
+ } catch (err) {
126
+ logger.error(`Failed: ${err.message}`);
127
+ process.exit(1);
128
+ }
129
+ });
130
+
131
+ stress
132
+ .command("show <test-id>")
133
+ .description("Show full results for a stress test")
134
+ .option("--json", "Output as JSON")
135
+ .action(async (testId, options) => {
136
+ try {
137
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
138
+ _dbFromCtx(ctx);
139
+ const r = getTestResults(testId);
140
+ if (options.json) {
141
+ console.log(JSON.stringify(r, null, 2));
142
+ } else {
143
+ logger.log(` ${chalk.bold("ID:")} ${chalk.cyan(r.testId)}`);
144
+ logger.log(` ${chalk.bold("Level:")} ${r.loadLevel}`);
145
+ logger.log(` ${chalk.bold("Status:")} ${r.status}`);
146
+ if (r.result) {
147
+ const m = r.result;
148
+ logger.log(` ${chalk.bold("TPS:")} ${m.tps}`);
149
+ logger.log(
150
+ ` ${chalk.bold("Latency:")} p50=${m.p50ResponseTime}ms p95=${m.p95ResponseTime}ms p99=${m.p99ResponseTime}ms`,
151
+ );
152
+ logger.log(
153
+ ` ${chalk.bold("Error rate:")} ${(m.errorRate * 100).toFixed(2)}%`,
154
+ );
155
+ }
156
+ }
157
+ await shutdown();
158
+ } catch (err) {
159
+ logger.error(`Failed: ${err.message}`);
160
+ process.exit(1);
161
+ }
162
+ });
163
+
164
+ stress
165
+ .command("analyze <test-id>")
166
+ .description("Analyze bottlenecks for a stress test")
167
+ .option("--json", "Output as JSON")
168
+ .action(async (testId, options) => {
169
+ try {
170
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
171
+ _dbFromCtx(ctx);
172
+ const r = analyzeBottlenecks(testId);
173
+ if (options.json) {
174
+ console.log(JSON.stringify(r, null, 2));
175
+ } else {
176
+ logger.log(` ${chalk.bold("Summary:")} ${r.summary}`);
177
+ for (const b of r.bottlenecks) {
178
+ logger.log(
179
+ ` ${chalk.yellow(b.kind)} (${b.severity}): ${b.detail}`,
180
+ );
181
+ }
182
+ }
183
+ await shutdown();
184
+ } catch (err) {
185
+ logger.error(`Failed: ${err.message}`);
186
+ process.exit(1);
187
+ }
188
+ });
189
+
190
+ stress
191
+ .command("plan <test-id>")
192
+ .description("Generate capacity plan recommendations")
193
+ .option("--json", "Output as JSON")
194
+ .action(async (testId, options) => {
195
+ try {
196
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
197
+ _dbFromCtx(ctx);
198
+ const plan = generateCapacityPlan(testId);
199
+ if (options.json) {
200
+ console.log(JSON.stringify(plan, null, 2));
201
+ } else {
202
+ logger.log(` ${chalk.bold("Level:")} ${plan.loadLevel}`);
203
+ logger.log(
204
+ ` ${chalk.bold("Target/Got:")} ${plan.targetRps} rps → ${plan.realizedTps} rps`,
205
+ );
206
+ logger.log(
207
+ ` ${chalk.bold("Scale:")} ${plan.scale}× (${plan.headroom})`,
208
+ );
209
+ for (const rec of plan.recommendations) {
210
+ logger.log(` ${chalk.green("→")} ${rec}`);
211
+ }
212
+ }
213
+ await shutdown();
214
+ } catch (err) {
215
+ logger.error(`Failed: ${err.message}`);
216
+ process.exit(1);
217
+ }
218
+ });
219
+
220
+ stress
221
+ .command("stop <test-id>")
222
+ .description("Mark a running stress test as stopped")
223
+ .action(async (testId) => {
224
+ try {
225
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
226
+ _dbFromCtx(ctx);
227
+ const r = stopStressTest(testId);
228
+ logger.success(`Test ${r.testId.slice(0, 8)} → ${r.status}`);
229
+ await shutdown();
230
+ } catch (err) {
231
+ logger.error(`Failed: ${err.message}`);
232
+ process.exit(1);
233
+ }
234
+ });
235
+
236
+ stress
237
+ .command("levels")
238
+ .description("List built-in load levels")
239
+ .option("--json", "Output as JSON")
240
+ .action((options) => {
241
+ const levels = listLoadLevels();
242
+ if (options.json) {
243
+ console.log(JSON.stringify(levels, null, 2));
244
+ } else {
245
+ for (const l of levels) {
246
+ logger.log(
247
+ ` ${chalk.cyan(l.name.padEnd(8))} concurrency=${l.concurrency} rps=${l.requestsPerSecond} duration=${l.duration}ms`,
248
+ );
249
+ }
250
+ }
251
+ });
252
+ }