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.
- package/cli/bin/clawmatrix.mjs +487 -12
- package/cli/skills/clawmatrix/SKILL.md +42 -86
- package/package.json +1 -1
- package/src/api.ts +410 -3
- package/src/automation.ts +90 -1
- package/src/cluster-service.ts +24 -9
- package/src/config.ts +22 -16
- package/src/connection.ts +10 -0
- package/src/device-info.ts +10 -0
- package/src/health-tracker.ts +91 -13
- package/src/index.ts +285 -0
- package/src/knowledge-sync.ts +7 -0
- package/src/peer-manager.ts +54 -23
- package/src/router.ts +21 -3
- package/src/types.ts +1 -0
package/cli/bin/clawmatrix.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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]
|
|
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
|
|
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":
|
|
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":
|