clawmatrix 0.5.1 → 0.6.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.
@@ -645,27 +645,56 @@ async function cmdApprovalRevoke(nodeId) {
645
645
 
646
646
  function cmdHelpJson() {
647
647
  const commands = [
648
+ // Cluster
648
649
  { name: "status", args: "", options: ["--json"], description: "Show cluster topology and peer status" },
649
650
  { name: "peers", args: "", options: [], description: "List known peers (JSON)" },
650
651
  { name: "check", args: "<nodeId>", options: [], description: "Check if a specific node is reachable" },
652
+ { name: "config", args: "", options: ["--json"], description: "Show local node configuration" },
653
+ { name: "config read", args: "<path>", options: [], description: "Read a config file under ~/.openclaw/" },
654
+ { name: "config write", args: "<path> <content|->", options: [], description: "Write a config file under ~/.openclaw/" },
655
+ { name: "availability", args: "[range]", options: ["--json"], description: "Show node uptime (24h|7d|90d)" },
656
+ // Tools & Execution
651
657
  { name: "tools", args: "[node]", options: ["--json", "-v", "-f <keyword>", "-d <tool>"], description: "List available tools on remote nodes" },
652
658
  { name: "call", args: "<node> <tool> [json]", options: ["-t <ms>"], description: "Invoke a tool on a remote node" },
653
659
  { name: "batch", args: "<node> [json]", options: ["--no-stop-on-error", "-t <ms>"], description: "Invoke multiple tools in sequence" },
654
660
  { name: "models", args: "", options: ["--json", "-n <nodeId>"], description: "List all cluster models" },
655
- { name: "events", args: "", options: ["--json", "-t <type>", "-s <source>", "-l <n>", "-a", "--consume <ids>"], description: "Query and consume events" },
661
+ // Agents
656
662
  { name: "send", args: "<node> <message>", options: [], description: "Send a message to a remote node" },
657
663
  { name: "handoff", args: "<task>", options: ["--agent <agent>", "--node <node>", "-t <ms>"], description: "Delegate a task to a remote agent" },
658
- { name: "acp", args: "<action>", options: ["-n <node>", "-a <agent>", "--session <id>", "--mode <mode>", "--cwd <dir>"], description: "Manage ACP agent sessions" },
664
+ { name: "acp", args: "<action>", options: ["-n <node>", "-a <agent>", "--session <id>", "--mode <mode>", "--cwd <dir>"], description: "Manage ACP agent sessions (list|prompt|resume|cancel|close)" },
665
+ // Events & Automations
666
+ { name: "events", args: "", options: ["--json", "-t <type>", "-s <source>", "-l <n>", "-a", "--consume <ids>"], description: "Query and consume events" },
667
+ { name: "events ingest", args: "<json>", options: [], description: "Ingest events from external sources" },
668
+ { name: "automations rules", args: "", options: ["--json"], description: "List automation rules" },
669
+ { name: "automations history", args: "", options: ["--json", "--limit <n>"], description: "Show automation execution history" },
670
+ { name: "automations run", args: "<ruleId>", options: ["--json"], description: "Manually trigger an automation rule" },
671
+ { name: "automations replay", args: "<executionId>", options: ["--json"], description: "Re-run a historical execution" },
672
+ { name: "automations save", args: "<json-file|->", options: [], description: "Save automation rules from JSON" },
673
+ // Infrastructure
659
674
  { name: "diagnostic", args: "<node>", options: ["--action status|exec", "-c <command>", "-t <seconds>"], description: "Diagnose a remote node via sentinel" },
660
675
  { name: "notify", args: "<title>", options: ["--detail <text>", "--progress <0-100>", "--action start|update|end", "--task-id <id>", "--tool <name>"], description: "Push notification to mobile devices" },
661
676
  { name: "terminal", args: "<node>", options: ["--shell <shell>", "--cwd <dir>", "--action list|close|read|input", "--session <id>"], description: "Open/manage remote terminal sessions" },
662
677
  { name: "transfer", args: "<node> <path> [remote]", options: ["--pull", "-t <ms>"], description: "Transfer files to/from a remote node" },
678
+ // Knowledge & Kanban
679
+ { name: "kb files", args: "", options: ["--json"], description: "List all synced knowledge files" },
680
+ { name: "kb history", args: "<path>", options: ["--json"], description: "Show change history for a synced file" },
681
+ { name: "kb blame", args: "<path>", options: ["--json"], description: "Show line-by-line attribution for a synced file" },
682
+ { name: "kb content", args: "<path>", options: [], description: "Read the content of a synced file" },
683
+ { name: "board", args: "", options: ["--json"], description: "Kanban board summary" },
684
+ { name: "board list", args: "", options: ["--json", "--stage <s>", "--label <l>", "--priority <p>", "--node <n>"], description: "List kanban cards" },
685
+ { name: "board create", args: "<title>", options: ["--desc <text>", "--priority <p>", "--labels <l1,l2>", "--node <n>", "--agent <a>"], description: "Create a new kanban card" },
686
+ { name: "board get", args: "<cardId>", options: ["--json"], description: "Get kanban card details" },
687
+ { name: "board update", args: "<cardId>", options: ["--title <t>", "--desc <d>", "--priority <p>", "--labels <l1,l2>"], description: "Update kanban card properties" },
688
+ { name: "board claim", args: "<cardId>", options: ["--agent <a>"], description: "Claim a kanban card" },
689
+ { name: "board move", args: "<cardId> <stage>", options: [], description: "Move kanban card to a stage" },
690
+ { name: "board annotate", args: "<cardId> <text>", options: ["--type <t>"], description: "Add annotation to a kanban card" },
691
+ { name: "board delete", args: "<cardId>", options: [], description: "Delete a kanban card" },
692
+ // Access Control
663
693
  { name: "approve", args: "<approvalId>", options: [], description: "Approve a pending peer" },
664
694
  { name: "deny", args: "<approvalId>", options: [], description: "Deny a pending peer" },
665
695
  { name: "approval list", args: "", options: [], description: "List approved/pending peers" },
666
696
  { name: "approval revoke", args: "<nodeId>", options: [], description: "Revoke an approved peer" },
667
- { name: "kb files", args: "", options: ["--json"], description: "List all synced knowledge files" },
668
- { name: "kb history", args: "<path>", options: ["--json"], description: "Show change history for a synced file" },
697
+ // Other
669
698
  { name: "install-skill", args: "", options: [], description: "Symlink skill into ~/.claude/skills/" },
670
699
  ];
671
700
  console.log(jsonOut({ prefix: "clawmatrix", commands }));
@@ -945,7 +974,9 @@ async function cmdKb(args) {
945
974
 
946
975
  Subcommands:
947
976
  files [--json] List all synced files with metadata
948
- history <path> [--json] Show change history for a synced file`);
977
+ history <path> [--json] Show change history for a synced file
978
+ blame <path> [--json] Show line-by-line attribution for a synced file
979
+ content <path> Read the content of a synced file`);
949
980
  return;
950
981
  }
951
982
 
@@ -1016,6 +1047,57 @@ Subcommands:
1016
1047
  return;
1017
1048
  }
1018
1049
 
1050
+ if (subCmd === "content" || subCmd === "read" || subCmd === "cat") {
1051
+ const filePath = subArgs.filter((a) => !a.startsWith("--"))[0];
1052
+ if (!filePath) { console.error("Usage: clawmatrix kb content <path>"); process.exit(1); }
1053
+ const result = await callGateway("clawmatrix.kb.content", { path: filePath });
1054
+ if (result.content !== undefined) {
1055
+ console.log(result.content);
1056
+ } else {
1057
+ console.error(`Error: ${result.error || "File not found"}`);
1058
+ process.exitCode = 1;
1059
+ }
1060
+ return;
1061
+ }
1062
+
1063
+ if (subCmd === "blame") {
1064
+ const filePath = subArgs.filter((a) => !a.startsWith("--"))[0];
1065
+ if (!filePath) { console.error("Usage: clawmatrix kb blame <path>"); process.exit(1); }
1066
+ const result = await callGateway("clawmatrix.kb.blame", { path: filePath });
1067
+ if (isJson) { console.log(jsonOut(result)); return; }
1068
+
1069
+ const blame = result?.blame;
1070
+ if (!Array.isArray(blame) || blame.length === 0) {
1071
+ console.log(`No blame data for: ${filePath}`);
1072
+ return;
1073
+ }
1074
+
1075
+ if (!isTTY) {
1076
+ for (const b of blame) {
1077
+ const parts = [b.line !== undefined ? `L${b.line}` : "?"];
1078
+ if (b.nodeId) parts.push(`node=${b.nodeId}`);
1079
+ if (b.agentId) parts.push(`agent=${b.agentId}`);
1080
+ parts.push(`actor=${b.actor}`);
1081
+ parts.push(new Date(b.timestamp).toISOString());
1082
+ parts.push(b.content || "");
1083
+ console.log(parts.join(" | "));
1084
+ }
1085
+ return;
1086
+ }
1087
+
1088
+ console.log();
1089
+ console.log(` ${cyan("◆")} ${bold("File Blame")} ${dim(filePath)}`);
1090
+ console.log(` ${bar}`);
1091
+ for (const b of blame) {
1092
+ const lineNum = b.line !== undefined ? dim(`L${String(b.line).padStart(4)}`) : dim(" ?");
1093
+ const attr = b.nodeId ? yellow(b.nodeId) : dim(b.actor || "?");
1094
+ const content = b.content || "";
1095
+ console.log(` ${bar} ${lineNum} ${attr} ${content}`);
1096
+ }
1097
+ console.log();
1098
+ return;
1099
+ }
1100
+
1019
1101
  console.error(`Unknown kb subcommand: ${subCmd}`);
1020
1102
  process.exit(1);
1021
1103
  } catch (e) {
@@ -1041,6 +1123,8 @@ Subcommands:
1041
1123
  create <title> [--desc <text>] [--priority <p>] [--labels <l1,l2>]
1042
1124
  Create a new card
1043
1125
  get <cardId> Get card details
1126
+ update <cardId> [--title <t>] [--desc <d>] [--priority <p>] [--labels <l1,l2>]
1127
+ Update card properties
1044
1128
  claim <cardId> [--agent <a>] Claim a backlog card
1045
1129
  move <cardId> <stage> Move card to a stage (backlog|claimed|in_progress|review|done|archived)
1046
1130
  annotate <cardId> <text> [--type <t>]
@@ -1150,6 +1234,29 @@ Subcommands:
1150
1234
  return;
1151
1235
  }
1152
1236
 
1237
+ if (subCmd === "update" || subCmd === "edit") {
1238
+ const cardId = subArgs.filter((a) => !a.startsWith("--"))[0];
1239
+ if (!cardId) { console.error("Usage: clawmatrix board update <cardId> [--title <t>] [--desc <d>] [--priority <p>] [--labels <l1,l2>]"); process.exit(1); }
1240
+ const params = { cardId };
1241
+ const titleIdx = subArgs.indexOf("--title");
1242
+ if (titleIdx !== -1) params.title = subArgs[titleIdx + 1];
1243
+ const descIdx = subArgs.indexOf("--desc");
1244
+ if (descIdx !== -1) params.description = subArgs[descIdx + 1];
1245
+ const prioIdx = subArgs.indexOf("--priority");
1246
+ if (prioIdx !== -1) params.priority = subArgs[prioIdx + 1];
1247
+ const labelsIdx = subArgs.indexOf("--labels");
1248
+ if (labelsIdx !== -1) params.labels = subArgs[labelsIdx + 1]?.split(",");
1249
+ const nodeIdx = subArgs.indexOf("--node");
1250
+ if (nodeIdx !== -1) params.targetNode = subArgs[nodeIdx + 1];
1251
+ const agentIdx = subArgs.indexOf("--agent");
1252
+ if (agentIdx !== -1) params.targetAgent = subArgs[agentIdx + 1];
1253
+
1254
+ const card = await callGateway("clawmatrix.board.update", params);
1255
+ if (isJson) { console.log(jsonOut(card)); return; }
1256
+ console.log(`Updated: ${card.id} — ${card.title}`);
1257
+ return;
1258
+ }
1259
+
1153
1260
  if (subCmd === "claim") {
1154
1261
  const cardId = subArgs.filter((a) => !a.startsWith("--"))[0];
1155
1262
  if (!cardId) { console.error("Usage: clawmatrix board claim <cardId>"); process.exit(1); }
@@ -1226,35 +1333,397 @@ Subcommands:
1226
1333
  }
1227
1334
  }
1228
1335
 
1336
+ // ── Availability command ──────────────────────────────────────────
1337
+
1338
+ async function cmdAvailability(args) {
1339
+ const isJson = args.includes("--json");
1340
+ const range = args.find((a) => ["24h", "7d", "90d"].includes(a)) || "24h";
1341
+
1342
+ try {
1343
+ const data = await callGateway("clawmatrix.availability", { range });
1344
+ if (isJson) { console.log(jsonOut(data)); return; }
1345
+
1346
+ if (!isTTY) {
1347
+ console.log(`Range: ${data.range} | Bucket: ${data.bucketMinutes}min`);
1348
+ for (const node of (data.nodes || [])) {
1349
+ const uptimeStr = node.uptimeRatio !== undefined ? `${(node.uptimeRatio * 100).toFixed(1)}%` : "N/A";
1350
+ console.log(`${node.nodeId} | uptime: ${uptimeStr} | buckets: ${node.buckets?.length ?? 0}`);
1351
+ }
1352
+ return;
1353
+ }
1354
+
1355
+ console.log();
1356
+ console.log(` ${cyan("◆")} ${bold("Availability")} ${dim(`range: ${data.range}, bucket: ${data.bucketMinutes}min`)}`);
1357
+ console.log(` ${bar}`);
1358
+
1359
+ for (const node of (data.nodes || [])) {
1360
+ const uptimeStr = node.uptimeRatio !== undefined ? `${(node.uptimeRatio * 100).toFixed(1)}%` : "N/A";
1361
+ const uptimeColor = node.uptimeRatio >= 0.99 ? green : node.uptimeRatio >= 0.9 ? yellow : red;
1362
+ console.log(` ${bar} ${bold(node.nodeId)} ${lbl("uptime")}${uptimeColor(uptimeStr)}`);
1363
+
1364
+ if (node.buckets?.length > 0) {
1365
+ // Render a compact timeline bar
1366
+ const chars = node.buckets.map((b) => {
1367
+ if (b.status === "up") return green("▓");
1368
+ if (b.status === "degraded") return yellow("▒");
1369
+ if (b.status === "down") return red("░");
1370
+ return dim("·");
1371
+ });
1372
+ // Show at most 60 chars for readability
1373
+ const maxWidth = 60;
1374
+ const step = Math.max(1, Math.ceil(chars.length / maxWidth));
1375
+ const compressed = [];
1376
+ for (let i = 0; i < chars.length; i += step) {
1377
+ compressed.push(chars[i]);
1378
+ }
1379
+ console.log(` ${bar} ${compressed.join("")}`);
1380
+ }
1381
+ }
1382
+
1383
+ if (data.gaps?.length > 0) {
1384
+ console.log(` ${bar}`);
1385
+ console.log(` ${bar} ${bold("Gaps")}`);
1386
+ for (const gap of data.gaps.slice(0, 10)) {
1387
+ const from = new Date(gap.from).toLocaleString();
1388
+ const to = new Date(gap.to).toLocaleString();
1389
+ const dur = Math.round((gap.to - gap.from) / 60000);
1390
+ console.log(` ${bar} ${gap.nodeId} ${dim(from)} → ${dim(to)} ${red(`${dur}min`)}`);
1391
+ }
1392
+ }
1393
+ console.log();
1394
+ } catch (err) {
1395
+ console.error(`Error: ${err.message || String(err)}`);
1396
+ process.exitCode = 1;
1397
+ }
1398
+ }
1399
+
1400
+ // ── Automations command ──────────────────────────────────────────
1401
+
1402
+ async function cmdAutomations(args) {
1403
+ const isJson = args.includes("--json");
1404
+ const subCmd = args[0];
1405
+ const subArgs = args.slice(1);
1406
+
1407
+ if (!subCmd || subCmd === "--help" || subCmd === "-h") {
1408
+ console.log(`Usage: clawmatrix automations [subcommand]
1409
+
1410
+ Subcommands:
1411
+ rules [--json] List all automation rules
1412
+ history [--limit <n>] [--json]
1413
+ Show execution history
1414
+ run <ruleId> [--json] Manually trigger a rule
1415
+ replay <executionId> [--json]
1416
+ Re-run a historical execution
1417
+ save <json-file|-> Save rules from JSON file or stdin`);
1418
+ return;
1419
+ }
1420
+
1421
+ try {
1422
+ if (subCmd === "rules" || subCmd === "list") {
1423
+ const data = await callGateway("clawmatrix.automations.rules");
1424
+ if (isJson) { console.log(jsonOut(data)); return; }
1425
+ const rules = data.rules || [];
1426
+ if (rules.length === 0) { console.log("No automation rules configured."); return; }
1427
+
1428
+ if (!isTTY) {
1429
+ for (const r of rules) {
1430
+ const status = r.enabled ? "enabled" : "disabled";
1431
+ const trigger = r.trigger?.type || "unknown";
1432
+ console.log(`${r.id} | ${status} | trigger:${trigger} | ${r.name || "(unnamed)"}`);
1433
+ }
1434
+ return;
1435
+ }
1436
+
1437
+ console.log();
1438
+ console.log(` ${cyan("◆")} ${bold("Automation Rules")} ${dim(`${rules.length} rule(s)`)}`);
1439
+ console.log(` ${bar}`);
1440
+ for (const r of rules) {
1441
+ const dot = r.enabled ? green("●") : red("○");
1442
+ const trigger = r.trigger?.type || "unknown";
1443
+ console.log(` ${bar} ${dot} ${bold(r.id)} ${r.name || dim("(unnamed)")} ${dim(`trigger: ${trigger}`)}`);
1444
+ }
1445
+ console.log();
1446
+ return;
1447
+ }
1448
+
1449
+ if (subCmd === "history") {
1450
+ const lIdx = subArgs.indexOf("--limit") !== -1 ? subArgs.indexOf("--limit") : subArgs.indexOf("-l");
1451
+ const limit = lIdx !== -1 ? parseInt(subArgs[lIdx + 1], 10) : 20;
1452
+ const data = await callGateway("clawmatrix.automations.history", { limit });
1453
+ if (isJson) { console.log(jsonOut(data)); return; }
1454
+ const executions = data.executions || [];
1455
+ if (executions.length === 0) { console.log("No execution history."); return; }
1456
+
1457
+ if (!isTTY) {
1458
+ for (const ex of executions) {
1459
+ const ts = new Date(ex.startedAt || ex.ts).toISOString();
1460
+ const dur = ex.durationMs ? `${ex.durationMs}ms` : "?";
1461
+ console.log(`${ts} | ${ex.ruleId} | ${ex.status} | ${dur}`);
1462
+ }
1463
+ return;
1464
+ }
1465
+
1466
+ console.log();
1467
+ console.log(` ${cyan("◆")} ${bold("Execution History")} ${dim(`${executions.length} execution(s)`)}`);
1468
+ console.log(` ${bar}`);
1469
+ for (const ex of executions) {
1470
+ const ts = new Date(ex.startedAt || ex.ts).toLocaleString();
1471
+ const dur = ex.durationMs ? dim(`${ex.durationMs}ms`) : "";
1472
+ const statusColor = ex.status === "success" ? green : ex.status === "error" ? red : yellow;
1473
+ console.log(` ${bar} ${statusColor("●")} ${bold(ex.ruleId)} ${statusColor(ex.status)} ${dur} ${dim(ts)}`);
1474
+ if (ex.error) console.log(` ${bar} ${red(ex.error)}`);
1475
+ }
1476
+ console.log();
1477
+ return;
1478
+ }
1479
+
1480
+ if (subCmd === "run") {
1481
+ const ruleId = subArgs.filter((a) => !a.startsWith("--"))[0];
1482
+ if (!ruleId) { console.error("Usage: clawmatrix automations run <ruleId>"); process.exit(1); }
1483
+ const data = await callGateway("clawmatrix.automations.run", { ruleId }, 60000);
1484
+ if (isJson) { console.log(jsonOut(data)); return; }
1485
+ const ex = data.execution;
1486
+ const statusColor = ex?.status === "success" ? green : red;
1487
+ console.log(`${statusColor("●")} Rule ${ruleId}: ${statusColor(ex?.status || "unknown")}${ex?.durationMs ? ` (${ex.durationMs}ms)` : ""}`);
1488
+ if (ex?.error) console.log(` ${red(ex.error)}`);
1489
+ return;
1490
+ }
1491
+
1492
+ if (subCmd === "replay") {
1493
+ const executionId = subArgs.filter((a) => !a.startsWith("--"))[0];
1494
+ if (!executionId) { console.error("Usage: clawmatrix automations replay <executionId>"); process.exit(1); }
1495
+ const data = await callGateway("clawmatrix.automations.replay", { executionId }, 60000);
1496
+ if (isJson) { console.log(jsonOut(data)); return; }
1497
+ console.log(`Replayed: ${executionId} → ${data.execution?.status || "unknown"}`);
1498
+ return;
1499
+ }
1500
+
1501
+ if (subCmd === "save") {
1502
+ let jsonStr = subArgs.filter((a) => !a.startsWith("--"))[0];
1503
+ if (jsonStr === "-" || (!jsonStr && !process.stdin.isTTY)) {
1504
+ const chunks = [];
1505
+ for await (const chunk of process.stdin) chunks.push(Buffer.from(chunk));
1506
+ jsonStr = Buffer.concat(chunks).toString("utf-8").trim();
1507
+ } else if (jsonStr && !jsonStr.startsWith("[")) {
1508
+ // Treat as file path
1509
+ try { jsonStr = readFileSync(resolve(jsonStr), "utf-8"); } catch (e) {
1510
+ console.error(`Error: Could not read file: ${jsonStr}`);
1511
+ process.exit(1);
1512
+ }
1513
+ }
1514
+ if (!jsonStr) { console.error("Usage: clawmatrix automations save <json-file|->"); process.exit(1); }
1515
+ const rules = JSON.parse(jsonStr);
1516
+ const data = await callGateway("clawmatrix.automations.save", { rules });
1517
+ console.log(`Saved ${data.count} rule(s)`);
1518
+ return;
1519
+ }
1520
+
1521
+ console.error(`Unknown automations subcommand: ${subCmd}`);
1522
+ process.exit(1);
1523
+ } catch (err) {
1524
+ console.error(`Error: ${err.message || String(err)}`);
1525
+ process.exitCode = 1;
1526
+ }
1527
+ }
1528
+
1529
+ // ── Config command ───────────────────────────────────────────────
1530
+
1531
+ async function cmdConfig(args) {
1532
+ const isJson = args.includes("--json");
1533
+ const subCmd = args[0];
1534
+
1535
+ // config read <path> — read a config file under ~/.openclaw/
1536
+ if (subCmd === "read") {
1537
+ const configPath = args[1];
1538
+ if (!configPath) { console.error("Usage: clawmatrix config read <path> (path under ~/.openclaw/)"); process.exit(1); }
1539
+ try {
1540
+ const data = await callGateway("clawmatrix.config.read", { path: configPath });
1541
+ if (data.content !== undefined) {
1542
+ console.log(data.content);
1543
+ } else {
1544
+ console.error(`Error: ${data.error || "Failed to read config"}`);
1545
+ process.exitCode = 1;
1546
+ }
1547
+ } catch (err) {
1548
+ console.error(`Error: ${err.message || String(err)}`);
1549
+ process.exitCode = 1;
1550
+ }
1551
+ return;
1552
+ }
1553
+
1554
+ // config write <path> <content|-> — write a config file under ~/.openclaw/
1555
+ if (subCmd === "write") {
1556
+ const configPath = args[1];
1557
+ let content = args[2];
1558
+ if (!configPath) { console.error("Usage: clawmatrix config write <path> <content|->"); process.exit(1); }
1559
+ if (content === "-" || (!content && !process.stdin.isTTY)) {
1560
+ const chunks = [];
1561
+ for await (const chunk of process.stdin) chunks.push(Buffer.from(chunk));
1562
+ content = Buffer.concat(chunks).toString("utf-8");
1563
+ }
1564
+ if (!content) { console.error("Error: No content provided. Pass content as argument or pipe via stdin."); process.exit(1); }
1565
+ try {
1566
+ const data = await callGateway("clawmatrix.config.write", { path: configPath, content });
1567
+ if (data.success) {
1568
+ console.log(`Written: ${configPath}`);
1569
+ } else {
1570
+ console.error(`Error: ${data.error || "Failed to write config"}`);
1571
+ process.exitCode = 1;
1572
+ }
1573
+ } catch (err) {
1574
+ console.error(`Error: ${err.message || String(err)}`);
1575
+ process.exitCode = 1;
1576
+ }
1577
+ return;
1578
+ }
1579
+
1580
+ if (subCmd === "--help" || subCmd === "-h") {
1581
+ console.log(`Usage: clawmatrix config [subcommand]
1582
+
1583
+ Subcommands:
1584
+ (no args) Show local node configuration summary
1585
+ read <path> Read a config file under ~/.openclaw/
1586
+ write <path> <content|->
1587
+ Write a config file under ~/.openclaw/
1588
+ Options:
1589
+ --json Output as JSON`);
1590
+ return;
1591
+ }
1592
+
1593
+ try {
1594
+ const data = await callGateway("clawmatrix.config.get");
1595
+ if (isJson) { console.log(jsonOut(data)); return; }
1596
+
1597
+ if (!isTTY) {
1598
+ const parts = [`nodeId: ${data.nodeId}`];
1599
+ parts.push(`listen: ${data.listen !== false ? data.listen : "disabled"}`);
1600
+ parts.push(`e2ee: ${data.e2ee ?? false}`);
1601
+ parts.push(`tags: ${(data.tags || []).join(",") || "none"}`);
1602
+ parts.push(`agents: ${(data.agents || []).map((a) => a.id).join(",") || "none"}`);
1603
+ parts.push(`models: ${(data.models || []).map((m) => m.id).join(",") || "none"}`);
1604
+ parts.push(`toolProxy: ${data.toolProxy?.enabled ? "enabled" : "disabled"}`);
1605
+ parts.push(`terminal: ${data.terminal?.enabled ? "enabled" : "disabled"}`);
1606
+ parts.push(`acp: ${data.acp?.enabled ? "enabled" : "disabled"}`);
1607
+ parts.push(`knowledge: ${data.knowledge?.enabled ? "enabled" : "disabled"}`);
1608
+ parts.push(`proxyModels: ${data.proxyModels}`);
1609
+ parts.push(`peers: ${data.peers}`);
1610
+ console.log(parts.join(" | "));
1611
+ return;
1612
+ }
1613
+
1614
+ console.log();
1615
+ console.log(` ${cyan("◆")} ${bold("ClawMatrix Config")}`);
1616
+ console.log(` ${bar}`);
1617
+ console.log(` ${bar} ${lbl("Node ID")}${bold(String(data.nodeId))}`);
1618
+ console.log(` ${bar} ${lbl("Listen")}${data.listen !== false ? `:${data.listen}` : dim("disabled")}`);
1619
+ console.log(` ${bar} ${lbl("E2EE")}${data.e2ee ? green("enabled") : dim("disabled")}`);
1620
+ console.log(` ${bar} ${lbl("Tags")}${(data.tags || []).join(dim(", ")) || dim("none")}`);
1621
+ console.log(` ${bar}`);
1622
+ console.log(` ${bar} ${bold("Agents")}`);
1623
+ for (const a of (data.agents || [])) {
1624
+ console.log(` ${bar} ${green("●")} ${a.id} ${dim(a.model || "")}`);
1625
+ }
1626
+ if ((data.agents || []).length === 0) console.log(` ${bar} ${dim("none")}`);
1627
+ console.log(` ${bar}`);
1628
+ console.log(` ${bar} ${bold("Models")}`);
1629
+ for (const m of (data.models || [])) {
1630
+ console.log(` ${bar} ${green("●")} ${m.id} ${dim(m.provider || "")}`);
1631
+ }
1632
+ if ((data.models || []).length === 0) console.log(` ${bar} ${dim("none")}`);
1633
+ console.log(` ${bar}`);
1634
+ console.log(` ${bar} ${bold("Features")}`);
1635
+ const feat = (name, enabled) => ` ${bar} ${enabled ? green("●") : red("○")} ${name}`;
1636
+ console.log(feat("Tool Proxy", data.toolProxy?.enabled));
1637
+ if (data.toolProxy?.enabled) {
1638
+ const allow = data.toolProxy.allow?.join(", ") || "*";
1639
+ const deny = data.toolProxy.deny?.length > 0 ? `, deny: ${data.toolProxy.deny.join(", ")}` : "";
1640
+ console.log(` ${bar} ${dim(`allow: ${allow}${deny}`)}`);
1641
+ }
1642
+ console.log(feat("Terminal", data.terminal?.enabled));
1643
+ console.log(feat("ACP", data.acp?.enabled));
1644
+ console.log(feat("Knowledge Sync", data.knowledge?.enabled));
1645
+ console.log(` ${bar}`);
1646
+ console.log(` ${bar} ${lbl("Proxy Models")}${data.proxyModels}`);
1647
+ console.log(` ${bar} ${lbl("Peers")}${data.peers}`);
1648
+ console.log();
1649
+ } catch (err) {
1650
+ console.error(`Error: ${err.message || String(err)}`);
1651
+ process.exitCode = 1;
1652
+ }
1653
+ }
1654
+
1655
+ // ── Events ingest subcommand ─────────────────────────────────────
1656
+
1657
+ async function cmdEventsIngest(args) {
1658
+ let jsonStr = args.find((a) => a.startsWith("[") || a.startsWith("{"));
1659
+ if (!jsonStr && !process.stdin.isTTY) {
1660
+ const chunks = [];
1661
+ for await (const chunk of process.stdin) chunks.push(Buffer.from(chunk));
1662
+ jsonStr = Buffer.concat(chunks).toString("utf-8").trim();
1663
+ }
1664
+ if (!jsonStr) {
1665
+ console.error('Usage: clawmatrix events ingest \'[{"type":"x","source":"y","data":{}}]\' or pipe via stdin');
1666
+ process.exit(1);
1667
+ }
1668
+ try {
1669
+ const raw = JSON.parse(jsonStr);
1670
+ const events = Array.isArray(raw) ? raw : [raw];
1671
+ const result = await callGateway("clawmatrix.events.ingest", { events });
1672
+ console.log(jsonOut(result));
1673
+ } catch (err) {
1674
+ console.error(`Error: ${err.message || String(err)}`);
1675
+ process.exitCode = 1;
1676
+ }
1677
+ }
1678
+
1229
1679
  const HELP = `ClawMatrix - Decentralized mesh cluster CLI
1230
1680
 
1231
1681
  Usage: clawmatrix <command> [options]
1232
1682
 
1233
- Commands:
1683
+ Cluster:
1234
1684
  status Show cluster topology and peer status
1685
+ peers List known peers (JSON)
1235
1686
  check <nodeId> Check if a specific node is reachable
1687
+ config [subcmd] Node config (read|write or show summary)
1688
+ availability [range] Show node uptime (24h|7d|90d)
1689
+
1690
+ Tools & Execution:
1236
1691
  tools [node] List available tools on remote nodes
1237
1692
  call <node> <tool> [p] Invoke a tool on a remote node
1238
1693
  batch <node> [json] Invoke multiple tools in sequence
1239
1694
  models List all models available across the cluster
1240
- events Query and consume ingested events
1695
+
1696
+ Agents:
1241
1697
  send <node> <message> Send a message to a remote node
1242
1698
  handoff <task> [opts] Delegate a task to a remote agent
1243
- acp <action> [opts] Manage ACP agent sessions (list|prompt|resume|cancel|close)
1699
+ acp <action> [opts] ACP sessions (list|prompt|resume|cancel|close)
1700
+
1701
+ Events & Automations:
1702
+ events Query, consume, or ingest events
1703
+ automations [subcmd] Manage automation rules (rules|history|run|replay|save)
1704
+
1705
+ Infrastructure:
1244
1706
  diagnostic <node> Diagnose a remote node via sentinel
1245
- notify <title> [opts] Push notification to mobile devices (Dynamic Island)
1707
+ notify <title> [opts] Push notification to mobile (Dynamic Island)
1246
1708
  terminal <node> Open/manage remote terminal sessions
1247
1709
  transfer <node> <path> Transfer files to/from a remote node
1710
+
1711
+ Knowledge & Kanban:
1712
+ kb [subcommand] Knowledge sync (files|history|blame)
1713
+ board [subcommand] Kanban board (list|create|get|update|claim|move|annotate|delete)
1714
+
1715
+ Access Control:
1248
1716
  approve <approvalId> Approve a pending peer join request
1249
1717
  deny <approvalId> Deny a pending peer join request
1250
- kb [subcommand] Knowledge sync (files|history)
1251
- board [subcommand] Kanban board (list|create|get|claim|move|annotate|delete)
1252
1718
  approval list|revoke Manage approved peers
1719
+
1720
+ Other:
1253
1721
  install-skill Symlink skill into ~/.claude/skills/
1254
1722
  help-json Structured command reference (JSON)
1255
1723
 
1256
1724
  Options:
1257
1725
  --help, -h Show this help message
1726
+ --json Output as JSON (most commands)
1258
1727
 
1259
1728
  Run 'clawmatrix <command> --help' for command-specific options.`;
1260
1729
 
@@ -1273,7 +1742,10 @@ try {
1273
1742
  case "call": await cmdCall(rest[0], rest[1], rest[2], rest); break;
1274
1743
  case "batch": await cmdBatch(rest[0], rest.find((a) => a.startsWith("[") || a.startsWith("{")), rest); break;
1275
1744
  case "models": await cmdModels(rest); break;
1276
- case "events": await cmdEvents(rest); break;
1745
+ case "events":
1746
+ if (rest[0] === "ingest") await cmdEventsIngest(rest.slice(1));
1747
+ else await cmdEvents(rest);
1748
+ break;
1277
1749
  case "send": await cmdSend(rest[0], rest.slice(1).join(" ")); break;
1278
1750
  case "handoff": await cmdHandoff(rest); break;
1279
1751
  case "acp": await cmdAcp(rest[0], rest.slice(1)); break;
@@ -1283,6 +1755,9 @@ try {
1283
1755
  case "transfer": await cmdTransfer(rest[0], rest[1], rest.slice(2)); break;
1284
1756
  case "kb": case "knowledge": await cmdKb(rest); break;
1285
1757
  case "board": await cmdBoard(rest); break;
1758
+ case "availability": await cmdAvailability(rest); break;
1759
+ case "automations": case "automation": await cmdAutomations(rest); break;
1760
+ case "config": await cmdConfig(rest); break;
1286
1761
  case "approve": await cmdApprove(rest[0]); break;
1287
1762
  case "deny": await cmdDeny(rest[0]); break;
1288
1763
  case "approval":