chainlesschain 0.51.0 → 0.81.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 (70) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/.build-hash +1 -1
  3. package/src/assets/web-panel/assets/{AppLayout-Rvi759IS.js → AppLayout-6SPt_8Y_.js} +1 -1
  4. package/src/assets/web-panel/assets/{Dashboard-DBhFxXYQ.js → Dashboard-Br7kCwKJ.js} +2 -2
  5. package/src/assets/web-panel/assets/Dashboard-CKeMmCoT.css +1 -0
  6. package/src/assets/web-panel/assets/{index-uL0cZ8N_.js → index-tN-8TosE.js} +2 -2
  7. package/src/assets/web-panel/index.html +2 -2
  8. package/src/commands/a2a.js +380 -0
  9. package/src/commands/agent-network.js +785 -0
  10. package/src/commands/automation.js +654 -0
  11. package/src/commands/bi.js +348 -0
  12. package/src/commands/crosschain.js +218 -0
  13. package/src/commands/dao.js +565 -0
  14. package/src/commands/did-v2.js +620 -0
  15. package/src/commands/dlp.js +341 -0
  16. package/src/commands/economy.js +578 -0
  17. package/src/commands/evolution.js +391 -0
  18. package/src/commands/evomap.js +394 -0
  19. package/src/commands/federation.js +283 -0
  20. package/src/commands/hmemory.js +442 -0
  21. package/src/commands/inference.js +318 -0
  22. package/src/commands/lowcode.js +356 -0
  23. package/src/commands/marketplace.js +256 -0
  24. package/src/commands/perf.js +433 -0
  25. package/src/commands/pipeline.js +449 -0
  26. package/src/commands/plugin-ecosystem.js +517 -0
  27. package/src/commands/privacy.js +321 -0
  28. package/src/commands/reputation.js +261 -0
  29. package/src/commands/sandbox.js +401 -0
  30. package/src/commands/siem.js +246 -0
  31. package/src/commands/sla.js +259 -0
  32. package/src/commands/social.js +311 -0
  33. package/src/commands/sso.js +798 -0
  34. package/src/commands/stress.js +230 -0
  35. package/src/commands/terraform.js +245 -0
  36. package/src/commands/workflow.js +320 -0
  37. package/src/commands/zkp.js +562 -1
  38. package/src/index.js +21 -0
  39. package/src/lib/a2a-protocol.js +451 -0
  40. package/src/lib/agent-economy.js +479 -0
  41. package/src/lib/agent-network.js +1121 -0
  42. package/src/lib/app-builder.js +239 -0
  43. package/src/lib/automation-engine.js +948 -0
  44. package/src/lib/bi-engine.js +338 -0
  45. package/src/lib/cross-chain.js +345 -0
  46. package/src/lib/dao-governance.js +569 -0
  47. package/src/lib/did-v2-manager.js +1127 -0
  48. package/src/lib/dlp-engine.js +389 -0
  49. package/src/lib/evolution-system.js +453 -0
  50. package/src/lib/evomap-federation.js +177 -0
  51. package/src/lib/evomap-governance.js +276 -0
  52. package/src/lib/federation-hardening.js +259 -0
  53. package/src/lib/hierarchical-memory.js +481 -0
  54. package/src/lib/inference-network.js +330 -0
  55. package/src/lib/perf-tuning.js +734 -0
  56. package/src/lib/pipeline-orchestrator.js +928 -0
  57. package/src/lib/plugin-ecosystem.js +1109 -0
  58. package/src/lib/privacy-computing.js +427 -0
  59. package/src/lib/reputation-optimizer.js +299 -0
  60. package/src/lib/sandbox-v2.js +306 -0
  61. package/src/lib/siem-exporter.js +333 -0
  62. package/src/lib/skill-marketplace.js +325 -0
  63. package/src/lib/sla-manager.js +275 -0
  64. package/src/lib/social-graph-analytics.js +707 -0
  65. package/src/lib/sso-manager.js +841 -0
  66. package/src/lib/stress-tester.js +330 -0
  67. package/src/lib/terraform-manager.js +363 -0
  68. package/src/lib/workflow-engine.js +454 -1
  69. package/src/lib/zkp-engine.js +523 -20
  70. package/src/assets/web-panel/assets/Dashboard-BS-tzGNj.css +0 -1
@@ -9,6 +9,11 @@ import {
9
9
  TASK_STATUS,
10
10
  PRIVACY_MODE,
11
11
  DEFAULT_CONFIG,
12
+ NODE_STATUS_V2,
13
+ TASK_STATUS_V2,
14
+ PRIVACY_MODE_V2,
15
+ INFERENCE_DEFAULT_MAX_CONCURRENT_TASKS_PER_NODE,
16
+ INFERENCE_DEFAULT_HEARTBEAT_TIMEOUT_MS,
12
17
  ensureInferenceTables,
13
18
  registerNode,
14
19
  unregisterNode,
@@ -22,6 +27,20 @@ import {
22
27
  getTask,
23
28
  listTasks,
24
29
  getSchedulerStats,
30
+ setMaxConcurrentTasksPerNode,
31
+ getMaxConcurrentTasksPerNode,
32
+ setHeartbeatTimeoutMs,
33
+ getHeartbeatTimeoutMs,
34
+ getActiveTasksPerNode,
35
+ submitTaskV2,
36
+ dispatchTaskV2,
37
+ startTask,
38
+ completeTaskV2,
39
+ failTaskV2,
40
+ setTaskStatus,
41
+ autoMarkOfflineNodes,
42
+ findEligibleNodes,
43
+ getInferenceStatsV2,
25
44
  } from "../lib/inference-network.js";
26
45
 
27
46
  function _dbFromCtx(cmd) {
@@ -300,5 +319,304 @@ export function registerInferenceCommand(program) {
300
319
  if (t.avgDurationMs > 0) console.log(`Avg latency: ${t.avgDurationMs}ms`);
301
320
  });
302
321
 
322
+ /* ──────────────────────────────────────────────────
323
+ * V2 — Phase 67 surface
324
+ * ────────────────────────────────────────────────── */
325
+
326
+ inf
327
+ .command("node-statuses-v2")
328
+ .description("List V2 node statuses")
329
+ .option("--json", "JSON output")
330
+ .action((opts) => {
331
+ const v = Object.values(NODE_STATUS_V2);
332
+ if (opts.json) return console.log(JSON.stringify(v, null, 2));
333
+ for (const s of v) console.log(` ${s}`);
334
+ });
335
+
336
+ inf
337
+ .command("task-statuses-v2")
338
+ .description("List V2 task statuses")
339
+ .option("--json", "JSON output")
340
+ .action((opts) => {
341
+ const v = Object.values(TASK_STATUS_V2);
342
+ if (opts.json) return console.log(JSON.stringify(v, null, 2));
343
+ for (const s of v) console.log(` ${s}`);
344
+ });
345
+
346
+ inf
347
+ .command("privacy-modes-v2")
348
+ .description("List V2 privacy modes")
349
+ .option("--json", "JSON output")
350
+ .action((opts) => {
351
+ const v = Object.values(PRIVACY_MODE_V2);
352
+ if (opts.json) return console.log(JSON.stringify(v, null, 2));
353
+ for (const m of v) console.log(` ${m}`);
354
+ });
355
+
356
+ inf
357
+ .command("default-max-concurrent-tasks")
358
+ .description("Show default per-node concurrent task cap")
359
+ .option("--json", "JSON output")
360
+ .action((opts) => {
361
+ const v = INFERENCE_DEFAULT_MAX_CONCURRENT_TASKS_PER_NODE;
362
+ if (opts.json)
363
+ return console.log(JSON.stringify({ default: v }, null, 2));
364
+ console.log(v);
365
+ });
366
+
367
+ inf
368
+ .command("max-concurrent-tasks")
369
+ .description("Show current per-node concurrent task cap")
370
+ .option("--json", "JSON output")
371
+ .action((opts) => {
372
+ const v = getMaxConcurrentTasksPerNode();
373
+ if (opts.json) return console.log(JSON.stringify({ max: v }, null, 2));
374
+ console.log(v);
375
+ });
376
+
377
+ inf
378
+ .command("set-max-concurrent-tasks <n>")
379
+ .description("Set per-node concurrent task cap")
380
+ .option("--json", "JSON output")
381
+ .action((n, opts) => {
382
+ try {
383
+ setMaxConcurrentTasksPerNode(Number(n));
384
+ const v = getMaxConcurrentTasksPerNode();
385
+ if (opts.json) return console.log(JSON.stringify({ max: v }, null, 2));
386
+ console.log(`Max concurrent tasks per node: ${v}`);
387
+ } catch (e) {
388
+ if (opts.json)
389
+ return console.log(JSON.stringify({ error: e.message }, null, 2));
390
+ console.error(`Error: ${e.message}`);
391
+ process.exitCode = 1;
392
+ }
393
+ });
394
+
395
+ inf
396
+ .command("active-task-count <node-id>")
397
+ .description("Show current active tasks for a node")
398
+ .option("--json", "JSON output")
399
+ .action((nodeId, opts) => {
400
+ const v = getActiveTasksPerNode(nodeId);
401
+ if (opts.json)
402
+ return console.log(JSON.stringify({ nodeId, active: v }, null, 2));
403
+ console.log(v);
404
+ });
405
+
406
+ inf
407
+ .command("heartbeat-timeout")
408
+ .description("Show current heartbeat timeout (ms)")
409
+ .option("--json", "JSON output")
410
+ .action((opts) => {
411
+ const v = getHeartbeatTimeoutMs();
412
+ if (opts.json)
413
+ return console.log(JSON.stringify({ timeoutMs: v }, null, 2));
414
+ console.log(v);
415
+ });
416
+
417
+ inf
418
+ .command("set-heartbeat-timeout <ms>")
419
+ .description("Set heartbeat timeout (ms)")
420
+ .option("--json", "JSON output")
421
+ .action((ms, opts) => {
422
+ try {
423
+ setHeartbeatTimeoutMs(Number(ms));
424
+ const v = getHeartbeatTimeoutMs();
425
+ if (opts.json)
426
+ return console.log(JSON.stringify({ timeoutMs: v }, null, 2));
427
+ console.log(`Heartbeat timeout: ${v}ms`);
428
+ } catch (e) {
429
+ if (opts.json)
430
+ return console.log(JSON.stringify({ error: e.message }, null, 2));
431
+ console.error(`Error: ${e.message}`);
432
+ process.exitCode = 1;
433
+ }
434
+ });
435
+
436
+ inf
437
+ .command("submit-v2 <model>")
438
+ .description("Submit task (V2, creates queued no assignment)")
439
+ .option("-i, --input <text>", "Input data")
440
+ .option("-p, --priority <n>", "Priority (1-10)", parseInt)
441
+ .option("-m, --mode <mode>", "Privacy mode")
442
+ .option("--json", "JSON output")
443
+ .action((model, opts) => {
444
+ const db = _dbFromCtx(inf);
445
+ try {
446
+ const t = submitTaskV2(db, {
447
+ model,
448
+ input: opts.input,
449
+ privacyMode: opts.mode,
450
+ priority: opts.priority,
451
+ });
452
+ if (opts.json) return console.log(JSON.stringify(t, null, 2));
453
+ console.log(`Task submitted: ${t.id}`);
454
+ console.log(`Status: ${t.status}`);
455
+ } catch (e) {
456
+ if (opts.json)
457
+ return console.log(JSON.stringify({ error: e.message }, null, 2));
458
+ console.error(`Error: ${e.message}`);
459
+ process.exitCode = 1;
460
+ }
461
+ });
462
+
463
+ inf
464
+ .command("dispatch-v2 <task-id>")
465
+ .description("Dispatch queued task to online node")
466
+ .option("-n, --node <id>", "Specific node id (else least-loaded)")
467
+ .option("--json", "JSON output")
468
+ .action((taskId, opts) => {
469
+ const db = _dbFromCtx(inf);
470
+ try {
471
+ const t = dispatchTaskV2(db, taskId, { nodeId: opts.node });
472
+ if (opts.json) return console.log(JSON.stringify(t, null, 2));
473
+ console.log(`Dispatched ${t.id} → ${t.assigned_node}`);
474
+ } catch (e) {
475
+ if (opts.json)
476
+ return console.log(JSON.stringify({ error: e.message }, null, 2));
477
+ console.error(`Error: ${e.message}`);
478
+ process.exitCode = 1;
479
+ }
480
+ });
481
+
482
+ inf
483
+ .command("start-task <task-id>")
484
+ .description("Start dispatched task (dispatched → running)")
485
+ .option("--json", "JSON output")
486
+ .action((taskId, opts) => {
487
+ const db = _dbFromCtx(inf);
488
+ try {
489
+ const t = startTask(db, taskId);
490
+ if (opts.json) return console.log(JSON.stringify(t, null, 2));
491
+ console.log(`Task started at ${t.started_at}`);
492
+ } catch (e) {
493
+ if (opts.json)
494
+ return console.log(JSON.stringify({ error: e.message }, null, 2));
495
+ console.error(`Error: ${e.message}`);
496
+ process.exitCode = 1;
497
+ }
498
+ });
499
+
500
+ inf
501
+ .command("complete-v2 <task-id>")
502
+ .description("Complete running task (running → complete)")
503
+ .option("-o, --output <text>", "Task output")
504
+ .option("-d, --duration <ms>", "Duration in ms", parseInt)
505
+ .option("--json", "JSON output")
506
+ .action((taskId, opts) => {
507
+ const db = _dbFromCtx(inf);
508
+ try {
509
+ const t = completeTaskV2(db, taskId, {
510
+ output: opts.output,
511
+ durationMs: opts.duration,
512
+ });
513
+ if (opts.json) return console.log(JSON.stringify(t, null, 2));
514
+ console.log(`Completed ${t.id} (${t.duration_ms}ms)`);
515
+ } catch (e) {
516
+ if (opts.json)
517
+ return console.log(JSON.stringify({ error: e.message }, null, 2));
518
+ console.error(`Error: ${e.message}`);
519
+ process.exitCode = 1;
520
+ }
521
+ });
522
+
523
+ inf
524
+ .command("fail-v2 <task-id>")
525
+ .description("Fail task (any non-terminal → failed)")
526
+ .option("-e, --error <text>", "Error message")
527
+ .option("--json", "JSON output")
528
+ .action((taskId, opts) => {
529
+ const db = _dbFromCtx(inf);
530
+ try {
531
+ const t = failTaskV2(db, taskId, { error: opts.error });
532
+ if (opts.json) return console.log(JSON.stringify(t, null, 2));
533
+ console.log(`Failed ${t.id}: ${t.error_message || ""}`);
534
+ } catch (e) {
535
+ if (opts.json)
536
+ return console.log(JSON.stringify({ error: e.message }, null, 2));
537
+ console.error(`Error: ${e.message}`);
538
+ process.exitCode = 1;
539
+ }
540
+ });
541
+
542
+ inf
543
+ .command("set-task-status <task-id> <status>")
544
+ .description("Generic state-machine-guarded task status setter")
545
+ .option("-o, --output <text>", "Patch: output")
546
+ .option("-d, --duration <ms>", "Patch: durationMs", parseInt)
547
+ .option("-e, --error <text>", "Patch: errorMessage")
548
+ .option("--json", "JSON output")
549
+ .action((taskId, status, opts) => {
550
+ const db = _dbFromCtx(inf);
551
+ const patch = {};
552
+ if (opts.output !== undefined) patch.output = opts.output;
553
+ if (opts.duration !== undefined) patch.durationMs = opts.duration;
554
+ if (opts.error !== undefined) patch.errorMessage = opts.error;
555
+ try {
556
+ const t = setTaskStatus(db, taskId, status, patch);
557
+ if (opts.json) return console.log(JSON.stringify(t, null, 2));
558
+ console.log(`Task ${t.id} → ${t.status}`);
559
+ } catch (e) {
560
+ if (opts.json)
561
+ return console.log(JSON.stringify({ error: e.message }, null, 2));
562
+ console.error(`Error: ${e.message}`);
563
+ process.exitCode = 1;
564
+ }
565
+ });
566
+
567
+ inf
568
+ .command("auto-offline")
569
+ .description("Mark nodes with stale heartbeat as offline")
570
+ .option("--json", "JSON output")
571
+ .action((opts) => {
572
+ const db = _dbFromCtx(inf);
573
+ const offlined = autoMarkOfflineNodes(db);
574
+ if (opts.json) return console.log(JSON.stringify(offlined, null, 2));
575
+ if (offlined.length === 0) return console.log("No stale nodes.");
576
+ for (const n of offlined)
577
+ console.log(` ${n.node_id.padEnd(20)} → offline`);
578
+ });
579
+
580
+ inf
581
+ .command("eligible-nodes")
582
+ .description("List online, under-cap nodes matching capability")
583
+ .option("-c, --capability <cap>", "Required capability")
584
+ .option("-m, --mode <mode>", "Privacy mode (reserved)")
585
+ .option("--json", "JSON output")
586
+ .action((opts) => {
587
+ const nodes = findEligibleNodes({
588
+ capability: opts.capability,
589
+ privacyMode: opts.mode,
590
+ });
591
+ if (opts.json) return console.log(JSON.stringify(nodes, null, 2));
592
+ if (nodes.length === 0) return console.log("No eligible nodes.");
593
+ for (const n of nodes)
594
+ console.log(
595
+ ` ${n.node_id.padEnd(20)} load=${getActiveTasksPerNode(n.id)} ${n.id.slice(0, 8)}`,
596
+ );
597
+ });
598
+
599
+ inf
600
+ .command("stats-v2")
601
+ .description("V2 inference statistics (all-enum-key zero init)")
602
+ .option("--json", "JSON output")
603
+ .action((opts) => {
604
+ const s = getInferenceStatsV2();
605
+ if (opts.json) return console.log(JSON.stringify(s, null, 2));
606
+ console.log(`Nodes: ${s.totalNodes} Tasks: ${s.totalTasks}`);
607
+ console.log(`Max concurrent/node: ${s.maxConcurrentTasksPerNode}`);
608
+ console.log(`Heartbeat timeout: ${s.heartbeatTimeoutMs}ms`);
609
+ console.log(`Avg duration: ${s.avgDurationMs}ms`);
610
+ console.log("Nodes by status:");
611
+ for (const [k, v] of Object.entries(s.nodesByStatus))
612
+ console.log(` ${k.padEnd(12)} ${v}`);
613
+ console.log("Tasks by status:");
614
+ for (const [k, v] of Object.entries(s.tasksByStatus))
615
+ console.log(` ${k.padEnd(12)} ${v}`);
616
+ console.log("Tasks by privacy:");
617
+ for (const [k, v] of Object.entries(s.tasksByPrivacyMode))
618
+ console.log(` ${k.padEnd(12)} ${v}`);
619
+ });
620
+
303
621
  program.addCommand(inf);
304
622
  }
@@ -20,7 +20,22 @@ import {
20
20
  exportApp,
21
21
  listApps,
22
22
  deployApp,
23
+ // Phase 93 V2 surface
24
+ COMPONENT_CATEGORY,
25
+ DATASOURCE_TYPE,
26
+ APP_STATUS,
27
+ listComponentsV2,
28
+ registerDataSourceV2,
29
+ testDataSourceConnection,
30
+ updateAppStatus,
31
+ archiveApp,
32
+ getStatusHistory,
33
+ cloneApp,
34
+ exportAppJSON,
35
+ importAppJSON,
36
+ getLowcodeStatsV2,
23
37
  } from "../lib/app-builder.js";
38
+ import fs from "fs";
24
39
 
25
40
  export function registerLowcodeCommand(program) {
26
41
  const lowcode = program
@@ -305,6 +320,347 @@ export function registerLowcodeCommand(program) {
305
320
  }
306
321
  });
307
322
 
323
+ // ─── Phase 93 V2 subcommands ────────────────────────────────
324
+
325
+ // lowcode categories
326
+ lowcode
327
+ .command("categories")
328
+ .description("List component categories (V2)")
329
+ .option("--json", "Output as JSON")
330
+ .action((options) => {
331
+ const categories = Object.values(COMPONENT_CATEGORY);
332
+ if (options.json) {
333
+ console.log(JSON.stringify(categories, null, 2));
334
+ } else {
335
+ logger.log(chalk.bold("Component Categories:"));
336
+ for (const c of categories) logger.log(` ${chalk.cyan(c)}`);
337
+ }
338
+ });
339
+
340
+ // lowcode datasource-types
341
+ lowcode
342
+ .command("datasource-types")
343
+ .description("List supported data source types (V2)")
344
+ .option("--json", "Output as JSON")
345
+ .action((options) => {
346
+ const types = Object.values(DATASOURCE_TYPE);
347
+ if (options.json) {
348
+ console.log(JSON.stringify(types, null, 2));
349
+ } else {
350
+ logger.log(chalk.bold("Data Source Types:"));
351
+ for (const t of types) logger.log(` ${chalk.cyan(t)}`);
352
+ }
353
+ });
354
+
355
+ // lowcode statuses
356
+ lowcode
357
+ .command("statuses")
358
+ .description("List canonical app statuses (V2)")
359
+ .option("--json", "Output as JSON")
360
+ .action((options) => {
361
+ const statuses = Object.values(APP_STATUS);
362
+ if (options.json) {
363
+ console.log(JSON.stringify(statuses, null, 2));
364
+ } else {
365
+ logger.log(chalk.bold("App Statuses:"));
366
+ for (const s of statuses) logger.log(` ${chalk.cyan(s)}`);
367
+ }
368
+ });
369
+
370
+ // lowcode components-v2
371
+ lowcode
372
+ .command("components-v2")
373
+ .description("List built-in components with category filter (V2)")
374
+ .option("-c, --category <cat>", "Filter by category")
375
+ .option("--json", "Output as JSON")
376
+ .action((options) => {
377
+ try {
378
+ const list = listComponentsV2(
379
+ options.category ? { category: options.category } : {},
380
+ );
381
+ if (options.json) {
382
+ console.log(JSON.stringify(list, null, 2));
383
+ } else {
384
+ logger.log(chalk.bold(`Components (${list.length}):`));
385
+ for (const c of list) {
386
+ logger.log(
387
+ ` ${chalk.cyan(c.name)} [${c.category}] props: ${c.props.join(", ")}`,
388
+ );
389
+ }
390
+ }
391
+ } catch (err) {
392
+ logger.error(err.message);
393
+ process.exit(1);
394
+ }
395
+ });
396
+
397
+ // lowcode datasource-v2 <app-id> <name> <type>
398
+ lowcode
399
+ .command("datasource-v2")
400
+ .description("Register a validated data source (V2)")
401
+ .argument("<app-id>", "Application ID")
402
+ .argument("<name>", "Data source name")
403
+ .argument("<type>", "Type: rest|graphql|database|csv")
404
+ .option("--config <json>", "Config as JSON string", "{}")
405
+ .action(async (appId, name, type, options) => {
406
+ const spinner = ora("Registering data source...").start();
407
+ try {
408
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
409
+ if (!ctx.db) {
410
+ spinner.fail("Database not available");
411
+ process.exit(1);
412
+ }
413
+ const db = ctx.db.getDatabase();
414
+ ensureLowcodeTables(db);
415
+
416
+ let config;
417
+ try {
418
+ config = JSON.parse(options.config);
419
+ } catch (_err) {
420
+ config = {};
421
+ }
422
+
423
+ const result = registerDataSourceV2(db, {
424
+ appId,
425
+ name,
426
+ type,
427
+ config,
428
+ });
429
+ spinner.succeed(
430
+ `Data source "${chalk.cyan(name)}" registered (${type})`,
431
+ );
432
+ logger.log(` ID: ${chalk.gray(result.id)}`);
433
+
434
+ await shutdown();
435
+ } catch (err) {
436
+ spinner.fail(err.message);
437
+ process.exit(1);
438
+ }
439
+ });
440
+
441
+ // lowcode test-connection <datasource-id>
442
+ lowcode
443
+ .command("test-connection")
444
+ .description("Heuristic connection check for a data source (V2)")
445
+ .argument("<datasource-id>", "Data source ID")
446
+ .option("--json", "Output as JSON")
447
+ .action((dsId, options) => {
448
+ const result = testDataSourceConnection(dsId);
449
+ if (options.json) {
450
+ console.log(JSON.stringify(result, null, 2));
451
+ } else {
452
+ const icon = result.ok ? chalk.green("✓") : chalk.red("✗");
453
+ logger.log(
454
+ `${icon} ${chalk.cyan(dsId)} (${result.type || "?"}): ${result.reason}`,
455
+ );
456
+ }
457
+ });
458
+
459
+ // lowcode set-status <app-id> <status>
460
+ lowcode
461
+ .command("set-status")
462
+ .description("Update app status with validated transition (V2)")
463
+ .argument("<app-id>", "Application ID")
464
+ .argument("<status>", "New status: draft|published|archived")
465
+ .action(async (appId, status) => {
466
+ const spinner = ora(`Transitioning to ${status}...`).start();
467
+ try {
468
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
469
+ if (!ctx.db) {
470
+ spinner.fail("Database not available");
471
+ process.exit(1);
472
+ }
473
+ const db = ctx.db.getDatabase();
474
+ ensureLowcodeTables(db);
475
+
476
+ const result = updateAppStatus(db, { appId, status });
477
+ spinner.succeed(
478
+ `${chalk.cyan(appId)}: ${result.previous} → ${chalk.bold(result.status)}`,
479
+ );
480
+
481
+ await shutdown();
482
+ } catch (err) {
483
+ spinner.fail(err.message);
484
+ process.exit(1);
485
+ }
486
+ });
487
+
488
+ // lowcode archive <app-id>
489
+ lowcode
490
+ .command("archive")
491
+ .description("Archive an application (V2)")
492
+ .argument("<app-id>", "Application ID")
493
+ .action(async (appId) => {
494
+ const spinner = ora("Archiving...").start();
495
+ try {
496
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
497
+ if (!ctx.db) {
498
+ spinner.fail("Database not available");
499
+ process.exit(1);
500
+ }
501
+ const db = ctx.db.getDatabase();
502
+ ensureLowcodeTables(db);
503
+
504
+ archiveApp(db, appId);
505
+ spinner.succeed(`${chalk.cyan(appId)} archived`);
506
+
507
+ await shutdown();
508
+ } catch (err) {
509
+ spinner.fail(err.message);
510
+ process.exit(1);
511
+ }
512
+ });
513
+
514
+ // lowcode status-history <app-id>
515
+ lowcode
516
+ .command("status-history")
517
+ .description("Show status transition history for an app (V2)")
518
+ .argument("<app-id>", "Application ID")
519
+ .option("--json", "Output as JSON")
520
+ .action((appId, options) => {
521
+ const hist = getStatusHistory(appId);
522
+ if (options.json) {
523
+ console.log(JSON.stringify(hist, null, 2));
524
+ } else if (hist.length === 0) {
525
+ logger.info("No status transitions recorded");
526
+ } else {
527
+ logger.log(chalk.bold(`Status History (${hist.length}):`));
528
+ for (const h of hist) {
529
+ logger.log(` ${chalk.gray(h.at)} ${h.from} → ${chalk.cyan(h.to)}`);
530
+ }
531
+ }
532
+ });
533
+
534
+ // lowcode clone <source-id>
535
+ lowcode
536
+ .command("clone")
537
+ .description("Clone an application (V2)")
538
+ .argument("<source-id>", "Source app ID")
539
+ .option("--name <name>", "New name for the clone")
540
+ .action(async (sourceId, options) => {
541
+ const spinner = ora("Cloning...").start();
542
+ try {
543
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
544
+ if (!ctx.db) {
545
+ spinner.fail("Database not available");
546
+ process.exit(1);
547
+ }
548
+ const db = ctx.db.getDatabase();
549
+ ensureLowcodeTables(db);
550
+
551
+ const result = cloneApp(db, {
552
+ sourceId,
553
+ newName: options.name,
554
+ });
555
+ spinner.succeed(`Cloned ${chalk.cyan(sourceId)} → ${result.clonedId}`);
556
+ logger.log(` Name: ${result.name}`);
557
+
558
+ await shutdown();
559
+ } catch (err) {
560
+ spinner.fail(err.message);
561
+ process.exit(1);
562
+ }
563
+ });
564
+
565
+ // lowcode export-json <app-id>
566
+ lowcode
567
+ .command("export-json")
568
+ .description("Export an app as canonical JSON (V2)")
569
+ .argument("<app-id>", "Application ID")
570
+ .option("-o, --output <file>", "Write to file instead of stdout")
571
+ .action(async (appId, options) => {
572
+ try {
573
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
574
+ if (!ctx.db) {
575
+ logger.error("Database not available");
576
+ process.exit(1);
577
+ }
578
+ const db = ctx.db.getDatabase();
579
+ ensureLowcodeTables(db);
580
+
581
+ const result = exportAppJSON(db, appId);
582
+ const json = JSON.stringify(result, null, 2);
583
+ if (options.output) {
584
+ fs.writeFileSync(options.output, json, "utf-8");
585
+ logger.log(chalk.green(`Wrote ${options.output}`));
586
+ } else {
587
+ console.log(json);
588
+ }
589
+
590
+ await shutdown();
591
+ } catch (err) {
592
+ logger.error(err.message);
593
+ process.exit(1);
594
+ }
595
+ });
596
+
597
+ // lowcode import-json <file>
598
+ lowcode
599
+ .command("import-json")
600
+ .description("Import an app from a canonical JSON file (V2)")
601
+ .argument("<file>", "Path to JSON export")
602
+ .action(async (file) => {
603
+ const spinner = ora("Importing...").start();
604
+ try {
605
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
606
+ if (!ctx.db) {
607
+ spinner.fail("Database not available");
608
+ process.exit(1);
609
+ }
610
+ const db = ctx.db.getDatabase();
611
+ ensureLowcodeTables(db);
612
+
613
+ const raw = fs.readFileSync(file, "utf-8");
614
+ const json = JSON.parse(raw);
615
+ const result = importAppJSON(db, json);
616
+ spinner.succeed(
617
+ `Imported "${chalk.cyan(result.name)}" → ${result.importedId}`,
618
+ );
619
+ logger.log(` Data sources: ${result.dataSources}`);
620
+
621
+ await shutdown();
622
+ } catch (err) {
623
+ spinner.fail(err.message);
624
+ process.exit(1);
625
+ }
626
+ });
627
+
628
+ // lowcode stats-v2
629
+ lowcode
630
+ .command("stats-v2")
631
+ .description("Low-code platform statistics (V2)")
632
+ .option("--json", "Output as JSON")
633
+ .action(async (options) => {
634
+ try {
635
+ const ctx = await bootstrap({ verbose: program.opts().verbose });
636
+ if (!ctx.db) {
637
+ logger.error("Database not available");
638
+ process.exit(1);
639
+ }
640
+ const db = ctx.db.getDatabase();
641
+ ensureLowcodeTables(db);
642
+
643
+ const stats = getLowcodeStatsV2(db);
644
+ if (options.json) {
645
+ console.log(JSON.stringify(stats, null, 2));
646
+ } else {
647
+ logger.log(chalk.bold("Low-Code Stats (V2):"));
648
+ logger.log(` Total apps: ${chalk.cyan(stats.totalApps)}`);
649
+ logger.log(` By status: ${JSON.stringify(stats.byStatus)}`);
650
+ logger.log(` By platform: ${JSON.stringify(stats.byPlatform)}`);
651
+ logger.log(
652
+ ` Data sources: ${stats.dataSources.total} (${JSON.stringify(stats.dataSources.byType)})`,
653
+ );
654
+ logger.log(` Components: ${stats.componentsAvailable}`);
655
+ }
656
+
657
+ await shutdown();
658
+ } catch (err) {
659
+ logger.error(err.message);
660
+ process.exit(1);
661
+ }
662
+ });
663
+
308
664
  // lowcode deploy <app-id>
309
665
  lowcode
310
666
  .command("deploy")