cmdr-mcp 0.1.1 → 0.2.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.
- package/.claude-plugin/marketplace.json +1 -1
- package/README.md +18 -15
- package/docs/README.zh-CN.md +16 -11
- package/docs/agent-integration.md +9 -3
- package/docs/implementation.md +21 -6
- package/docs/long-running-collaboration.md +99 -0
- package/docs/publishing.md +22 -7
- package/docs/troubleshooting.md +28 -3
- package/marketplace.json +1 -1
- package/package.json +2 -1
- package/plugins/cmdr/.claude-plugin/plugin.json +1 -1
- package/plugins/cmdr/.codex-plugin/plugin.json +1 -1
- package/plugins/cmdr/.zcode-plugin/plugin.json +1 -1
- package/plugins/cmdr/README.md +4 -4
- package/plugins/cmdr/bin/cmdr-node +2 -2
- package/plugins/cmdr/commands/cmdr.md +4 -6
- package/plugins/cmdr/dist/cli.mjs +217 -37
- package/plugins/cmdr/dist/daemon.mjs +1136 -123
- package/plugins/cmdr/dist/hook.mjs +25 -8
- package/plugins/cmdr/dist/integrity.json +13 -13
- package/plugins/cmdr/dist/mcp.mjs +44 -17
- package/plugins/cmdr/skills/cmdr-commander/SKILL.md +10 -8
- package/plugins/cmdr/skills/cmdr-executor/SKILL.md +9 -7
- package/plugins/cmdr/skills/using-cmdr/SKILL.md +7 -9
package/plugins/cmdr/README.md
CHANGED
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
Connect local Claude Code, Codex, ZCode and other MCP-capable sessions as a squad. Requires macOS/Linux and Node.js ≥22.5 (24 recommended); npm distribution packages include all runtime dependencies. A source checkout requires `npm ci && npm run build` at the repository root before installation; generated `dist` files are not tracked by Git.
|
|
4
4
|
|
|
5
|
-
In each session enter `/cmdr my-project`.
|
|
5
|
+
In each session enter `/cmdr my-project`. Joining defaults to executor, even for a new channel. Explicitly use role="commander" to claim command; no commander is required to retain a channel. Without slash commands, ask the Agent to call `join(squad_name="my-project")`.
|
|
6
6
|
|
|
7
|
-
Tools: `join`, `list`, `send`, `report`, `ask`, `read`, `leave`. Executors report ready with capabilities/cwd, then read tasks, report results and ask when blocked. Commanders dispatch verifiable tasks and answer questions with `reply_to`.
|
|
7
|
+
Tools: `join`, `list`, `send`, `report`, `ask`, `read`, `leave`. Executors report ready with capabilities/cwd, then read tasks, report results and ask when blocked. Commanders dispatch verifiable tasks and answer questions with `reply_to`. Acknowledge each command with working + reply_to before executing it. Use read(recover=true) to find unfinished commands after interruptions. Offline never means stopped. Use join(standby="auto") and check listener health; with a healthy listener, end the idle turn. Manual fallback is limited to two recommended waits.
|
|
8
8
|
|
|
9
|
-
Install a built checkout or the unpacked/installed npm package root as a marketplace in Claude Code/Codex, or use ZCode Settings → Plugins → Create → Add plugin marketplace. Codex needs hooks enabled and five hook approvals; start a new session after installation. ZCode uses its native manifest and four supported hooks; MCP EOF handles offline state. Generic hosts can configure the absolute `bin/cmdr-mcp` executable and `CMDR_AGENT=<host>` with optional `CMDR_SESSION_ID=<unique native session>`.
|
|
9
|
+
Install a built checkout or the unpacked/installed npm package root as a marketplace in Claude Code/Codex, or use ZCode Settings → Plugins → Create → Add plugin marketplace. Codex needs hooks enabled and five hook approvals; start a new session after installation. For ZCode, use the published `njugray/cmdr#marketplace` source to install without a local build or global npm package. ZCode uses its native manifest and four supported hooks; MCP EOF handles offline state. Generic hosts can configure the absolute `bin/cmdr-mcp` executable and `CMDR_AGENT=<host>` with optional `CMDR_SESSION_ID=<unique native session>`.
|
|
10
10
|
|
|
11
11
|
`bin/cmdr config --agent zcode` prints native ZCode configuration; `bin/cmdr config --agent my-agent` prints generic MCP configuration. `bin/cmdr doctor` checks installation health. `bin/cmdr daemon restart` reloads same-version code changes. State lives in `~/.cmdr/` (override `CMDR_HOME`), shared by all sessions.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
The daemon can wake existing Codex sessions through the shared public app-server transport; other hosts explicitly remain manual. It never creates agents or provides remote transport. Read marks delivery; report(working/done/failed/cancelled, reply_to) tracks work separately. cmdr standby manages listeners, and tail --after/--for supports non-consuming event replay. See [long-running collaboration](https://github.com/njugray/cmdr/blob/main/docs/long-running-collaboration.md).
|
|
14
14
|
|
|
15
15
|
[Installation and usage](https://github.com/njugray/cmdr#readme) · [Host integration](https://github.com/njugray/cmdr/blob/main/docs/agent-integration.md) · [Verification scope](https://github.com/njugray/cmdr/blob/main/docs/implementation.md)
|
|
16
16
|
|
|
@@ -37,13 +37,13 @@ if [ -z "$CMDR_NODE" ]; then
|
|
|
37
37
|
fi
|
|
38
38
|
if [ -z "$CMDR_NODE" ]; then
|
|
39
39
|
cmdr_failure node
|
|
40
|
-
[ "$CMDR_ENTRY" = hook ] && exit 0
|
|
40
|
+
[ "$CMDR_ENTRY" = hook ] && { echo "cmdr hook unavailable: Node >=22.5 required; run cmdr doctor." >&2; exit 0; }
|
|
41
41
|
echo 'cmdr requires Node.js >=22.5 (24 recommended). Install Node and retry.' >&2
|
|
42
42
|
exit 1
|
|
43
43
|
fi
|
|
44
44
|
if [ ! -f "$CMDR_BIN_DIR/../dist/$CMDR_ENTRY.mjs" ]; then
|
|
45
45
|
cmdr_failure runtime
|
|
46
|
-
[ "$CMDR_ENTRY" = hook ] && exit 0
|
|
46
|
+
[ "$CMDR_ENTRY" = hook ] && { echo "cmdr hook unavailable: missing dist runtime; build or reinstall the plugin and run cmdr doctor --plugin-root PATH." >&2; exit 0; }
|
|
47
47
|
if [ "$CMDR_ENTRY" = cli ] && [ "$1" = doctor ] && [ -f "$CMDR_BIN_DIR/cmdr-check.mjs" ]; then
|
|
48
48
|
exec "$CMDR_NODE" "$CMDR_BIN_DIR/cmdr-check.mjs" "$CMDR_BIN_DIR/.."
|
|
49
49
|
fi
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Create or join a
|
|
2
|
+
description: Create or join a persistent local agent channel.
|
|
3
3
|
argument-hint: <name>
|
|
4
4
|
---
|
|
5
|
-
Call
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
If cmdr tools are unavailable, follow the using-cmdr installation diagnostics and member CLI fallback. Do not invent native session IDs or access raw sockets.
|
|
5
|
+
Call join(squad_name="$ARGUMENTS", standby="auto") for atomic find-or-create. If no name was provided, ask for one; do not invent it. Joining defaults to executor, including a channel without a commander. Add role="commander" if the user explicitly requested command; use takeover=true only for requested handover.
|
|
6
|
+
Follow protocol_hint and the role skill. Executors report ready with cwd and capabilities. Give a brief translated user_reply preserving the join line. Check listener health: end the idle turn when can_auto_respond=true; otherwise use at most two recommended waits and explain manual continuation. Do not create a private wake script.
|
|
7
|
+
If tools are unavailable, follow using-cmdr diagnostics and member CLI fallback. Use the real host session ID, never an invented identity or raw socket workaround.
|
|
@@ -6,9 +6,6 @@ var __export = (target, all) => {
|
|
|
6
6
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
7
7
|
};
|
|
8
8
|
|
|
9
|
-
// src/cli/session.ts
|
|
10
|
-
import { parseArgs } from "node:util";
|
|
11
|
-
|
|
12
9
|
// src/shared/diagnostics.ts
|
|
13
10
|
import { mkdirSync as mkdirSync2, readFileSync, writeFileSync, renameSync, rmSync } from "node:fs";
|
|
14
11
|
import { join as join2 } from "node:path";
|
|
@@ -115,7 +112,18 @@ import { connect } from "node:net";
|
|
|
115
112
|
import { spawn } from "node:child_process";
|
|
116
113
|
import { dirname, join as join3 } from "node:path";
|
|
117
114
|
import { fileURLToPath } from "node:url";
|
|
118
|
-
import { mkdirSync as mkdirSync3, rmSync as rmSync2, statSync } from "node:fs";
|
|
115
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync2, rmSync as rmSync2, statSync } from "node:fs";
|
|
116
|
+
|
|
117
|
+
// src/daemon/lock.ts
|
|
118
|
+
function alive(pid) {
|
|
119
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
120
|
+
try {
|
|
121
|
+
process.kill(pid, 0);
|
|
122
|
+
return true;
|
|
123
|
+
} catch (e) {
|
|
124
|
+
return e.code === "EPERM";
|
|
125
|
+
}
|
|
126
|
+
}
|
|
119
127
|
|
|
120
128
|
// src/shared/rpc.ts
|
|
121
129
|
import { EventEmitter } from "node:events";
|
|
@@ -260,7 +268,7 @@ var Rpc = class extends EventEmitter {
|
|
|
260
268
|
};
|
|
261
269
|
|
|
262
270
|
// src/shared/version.ts
|
|
263
|
-
var VERSION = true ? "0.
|
|
271
|
+
var VERSION = true ? "0.2.0" : MIN_CLIENT_VERSION;
|
|
264
272
|
var PROTOCOL = 1;
|
|
265
273
|
function newer(a, b) {
|
|
266
274
|
const x = a.split(".").map(Number), y = b.split(".").map(Number);
|
|
@@ -306,15 +314,16 @@ async function daemonConnection(options = {}) {
|
|
|
306
314
|
{ from: hello.version, to: VERSION, protocol: hello.protocol },
|
|
307
315
|
options.home
|
|
308
316
|
);
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
317
|
+
throw new CmdrError(
|
|
318
|
+
"UPGRADE_REQUIRED",
|
|
319
|
+
`Daemon ${hello.version} is older than client ${VERSION}. Run cmdr daemon restart from this installation; automatic replacement is disabled to protect live sessions.`
|
|
320
|
+
);
|
|
313
321
|
}
|
|
314
322
|
return rpc;
|
|
315
323
|
} catch (e) {
|
|
316
324
|
rpc?.close();
|
|
317
|
-
if (e.code === "PROTOCOL_MISMATCH" || !options.start)
|
|
325
|
+
if (e.code === "PROTOCOL_MISMATCH" || e.code === "UPGRADE_REQUIRED" || !options.start)
|
|
326
|
+
throw e;
|
|
318
327
|
}
|
|
319
328
|
if (!owner) {
|
|
320
329
|
try {
|
|
@@ -328,7 +337,12 @@ async function daemonConnection(options = {}) {
|
|
|
328
337
|
}
|
|
329
338
|
}
|
|
330
339
|
}
|
|
331
|
-
|
|
340
|
+
let daemonOwnsLock = false;
|
|
341
|
+
try {
|
|
342
|
+
daemonOwnsLock = alive(Number(readFileSync2(p.lock, "utf8")));
|
|
343
|
+
} catch {
|
|
344
|
+
}
|
|
345
|
+
if (owner && !daemonOwnsLock) {
|
|
332
346
|
if (attempt === 0 || !spawned) {
|
|
333
347
|
const child = spawn(
|
|
334
348
|
process.execPath,
|
|
@@ -439,6 +453,81 @@ var DaemonClient = class {
|
|
|
439
453
|
}
|
|
440
454
|
};
|
|
441
455
|
|
|
456
|
+
// src/cli/tail.ts
|
|
457
|
+
async function tail(options) {
|
|
458
|
+
let after = options.after, stopped = false;
|
|
459
|
+
let connection;
|
|
460
|
+
const stop = () => {
|
|
461
|
+
stopped = true;
|
|
462
|
+
connection?.close();
|
|
463
|
+
};
|
|
464
|
+
process.on("SIGINT", stop);
|
|
465
|
+
process.on("SIGTERM", stop);
|
|
466
|
+
const show = (event) => {
|
|
467
|
+
if (after !== void 0 && event.event_seq <= after) return;
|
|
468
|
+
process.stdout.write(
|
|
469
|
+
options.json ? JSON.stringify(event) + "\n" : `${event.event_seq} ${new Date(event.at).toISOString()} ${event.channel || "-"} ${event.kind} ${event.from_sid || "-"} \u2192 ${event.to_sid || "-"} ${event.message_id || ""}${event.reply_to ? ` reply_to=${event.reply_to}` : ""}${event.reason ? ` ${event.reason}` : ""}${event.message ? ` ${event.message.body.replace(/\s+/g, " ")}` : ""}
|
|
470
|
+
`
|
|
471
|
+
);
|
|
472
|
+
after = event.event_seq;
|
|
473
|
+
};
|
|
474
|
+
try {
|
|
475
|
+
do {
|
|
476
|
+
try {
|
|
477
|
+
const rpc = connection = await daemonConnection();
|
|
478
|
+
const closed = new Promise((resolve3) => rpc.once("close", resolve3));
|
|
479
|
+
await rpc.request("session.register", { kind: "cli" });
|
|
480
|
+
let replaying = true;
|
|
481
|
+
const buffer = [];
|
|
482
|
+
rpc.on("notification", (method, value) => {
|
|
483
|
+
if (method === "lifecycle.event") replaying ? buffer.push(value) : show(value);
|
|
484
|
+
});
|
|
485
|
+
let result = await rpc.request(options.follow ? "admin.tail" : "admin.events", {
|
|
486
|
+
...options,
|
|
487
|
+
after
|
|
488
|
+
});
|
|
489
|
+
if (result.gap) {
|
|
490
|
+
const gap = { kind: "retention.gap", after, retained_after: result.retained_after };
|
|
491
|
+
if (options.json) process.stdout.write(JSON.stringify(gap) + "\n");
|
|
492
|
+
else
|
|
493
|
+
process.stderr.write(
|
|
494
|
+
`cmdr: event retention gap; events through ${result.retained_after} expired
|
|
495
|
+
`
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
for (; ; ) {
|
|
499
|
+
for (const event of result.events) show(event);
|
|
500
|
+
after = Math.max(after || 0, result.next);
|
|
501
|
+
if (result.next >= result.high) break;
|
|
502
|
+
result = await rpc.request("admin.events", { ...options, after });
|
|
503
|
+
}
|
|
504
|
+
replaying = false;
|
|
505
|
+
for (const event of buffer.sort((a, b) => a.event_seq - b.event_seq)) show(event);
|
|
506
|
+
if (!options.follow) rpc.close();
|
|
507
|
+
else await closed;
|
|
508
|
+
} catch (e) {
|
|
509
|
+
if (!options.follow || stopped) {
|
|
510
|
+
if (!stopped) throw e;
|
|
511
|
+
} else
|
|
512
|
+
process.stderr.write(
|
|
513
|
+
`cmdr tail: ${String(e)}; reconnecting after ${after ?? "latest"}
|
|
514
|
+
`
|
|
515
|
+
);
|
|
516
|
+
} finally {
|
|
517
|
+
connection?.close();
|
|
518
|
+
connection = void 0;
|
|
519
|
+
}
|
|
520
|
+
if (options.follow && !stopped) await new Promise((r) => setTimeout(r, 1e3));
|
|
521
|
+
} while (options.follow && !stopped);
|
|
522
|
+
} finally {
|
|
523
|
+
process.off("SIGINT", stop);
|
|
524
|
+
process.off("SIGTERM", stop);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// src/cli/session.ts
|
|
529
|
+
import { parseArgs } from "node:util";
|
|
530
|
+
|
|
442
531
|
// node_modules/zod/v3/external.js
|
|
443
532
|
var external_exports = {};
|
|
444
533
|
__export(external_exports, {
|
|
@@ -4492,16 +4581,20 @@ var schemas = {
|
|
|
4492
4581
|
squad: external_exports.string().optional(),
|
|
4493
4582
|
name: external_exports.string().trim().min(1).max(64).optional(),
|
|
4494
4583
|
note: text.optional(),
|
|
4495
|
-
squad_name: external_exports.string().trim().min(1).max(64).optional()
|
|
4584
|
+
squad_name: external_exports.string().trim().min(1).max(64).optional(),
|
|
4585
|
+
takeover: external_exports.boolean().default(false),
|
|
4586
|
+
standby: external_exports.enum(["auto", "manual"]).optional(),
|
|
4587
|
+
rebind: external_exports.string().optional()
|
|
4496
4588
|
}).strict(),
|
|
4497
4589
|
list: external_exports.object({
|
|
4498
4590
|
...identity,
|
|
4591
|
+
full: external_exports.boolean().default(false),
|
|
4499
4592
|
scope: external_exports.enum(["squad", "all"]).optional(),
|
|
4500
4593
|
squad: external_exports.string().optional()
|
|
4501
4594
|
}).strict(),
|
|
4502
4595
|
report: external_exports.object({
|
|
4503
4596
|
...identity,
|
|
4504
|
-
status: external_exports.enum(["ready", "working", "blocked", "done", "failed"]),
|
|
4597
|
+
status: external_exports.enum(["ready", "working", "blocked", "done", "failed", "cancelled"]),
|
|
4505
4598
|
message: text,
|
|
4506
4599
|
reply_to: external_exports.string().optional(),
|
|
4507
4600
|
data
|
|
@@ -4511,7 +4604,10 @@ var schemas = {
|
|
|
4511
4604
|
...identity,
|
|
4512
4605
|
to: external_exports.union([external_exports.string().min(1), external_exports.array(external_exports.string().min(1)).min(1).max(1e3)]),
|
|
4513
4606
|
message: text,
|
|
4514
|
-
type: external_exports.enum(["command", "answer", "info"]).default("command"),
|
|
4607
|
+
type: external_exports.enum(["command", "cancel", "answer", "info"]).default("command"),
|
|
4608
|
+
task_key: external_exports.string().min(1).max(128).optional(),
|
|
4609
|
+
reassign: external_exports.string().optional(),
|
|
4610
|
+
attention: external_exports.boolean().optional(),
|
|
4515
4611
|
priority: external_exports.enum(["high", "normal", "low"]).optional(),
|
|
4516
4612
|
reply_to: external_exports.string().optional(),
|
|
4517
4613
|
data
|
|
@@ -4522,7 +4618,10 @@ var schemas = {
|
|
|
4522
4618
|
limit: external_exports.number().int().min(1).max(100).default(20),
|
|
4523
4619
|
peek: external_exports.boolean().default(false),
|
|
4524
4620
|
history: external_exports.boolean().default(false),
|
|
4525
|
-
since: external_exports.string().optional()
|
|
4621
|
+
since: external_exports.string().optional(),
|
|
4622
|
+
id: external_exports.string().optional(),
|
|
4623
|
+
recover: external_exports.boolean().default(false),
|
|
4624
|
+
full: external_exports.boolean().default(false)
|
|
4526
4625
|
}).strict(),
|
|
4527
4626
|
leave: external_exports.object({ ...identity, dissolve: external_exports.boolean().default(false), message: text.optional() }).strict()
|
|
4528
4627
|
};
|
|
@@ -4591,6 +4690,16 @@ async function runSession(argv) {
|
|
|
4591
4690
|
to: { type: "string" },
|
|
4592
4691
|
type: { type: "string" },
|
|
4593
4692
|
"reply-to": { type: "string" },
|
|
4693
|
+
priority: { type: "string" },
|
|
4694
|
+
"task-key": { type: "string" },
|
|
4695
|
+
reassign: { type: "string" },
|
|
4696
|
+
rebind: { type: "string" },
|
|
4697
|
+
takeover: { type: "boolean" },
|
|
4698
|
+
standby: { type: "string" },
|
|
4699
|
+
full: { type: "boolean" },
|
|
4700
|
+
recover: { type: "boolean" },
|
|
4701
|
+
id: { type: "string" },
|
|
4702
|
+
limit: { type: "string" },
|
|
4594
4703
|
peek: { type: "boolean" },
|
|
4595
4704
|
history: { type: "boolean" },
|
|
4596
4705
|
dissolve: { type: "boolean" },
|
|
@@ -4600,7 +4709,7 @@ async function runSession(argv) {
|
|
|
4600
4709
|
});
|
|
4601
4710
|
if (v.help) {
|
|
4602
4711
|
console.log(
|
|
4603
|
-
"cmdr session join|list|send|report|ask|read|leave --agent HOST --native-id ID [--input JSON] [--timeout SECONDS]\nUse --squad-name for join; --status and text for report; --to and text for send; text for ask. read supports --wait/--peek/--history. IDs must match the host session;
|
|
4712
|
+
"cmdr session join|list|send|report|ask|read|leave --agent HOST --native-id ID [--input JSON] [--timeout SECONDS]\nUse --squad-name for join; --status and text for report; --to and text for send; text for ask. read supports --wait/--peek/--history. IDs must match the host session; read also supports --recover/--id/--full/--limit. Configure automatic wake with cmdr standby start --session SID; unsupported hosts remain manual."
|
|
4604
4713
|
);
|
|
4605
4714
|
return;
|
|
4606
4715
|
}
|
|
@@ -4631,12 +4740,22 @@ async function runSession(argv) {
|
|
|
4631
4740
|
to: "to",
|
|
4632
4741
|
type: "type",
|
|
4633
4742
|
"reply-to": "reply_to",
|
|
4743
|
+
priority: "priority",
|
|
4744
|
+
"task-key": "task_key",
|
|
4745
|
+
reassign: "reassign",
|
|
4746
|
+
rebind: "rebind",
|
|
4747
|
+
takeover: "takeover",
|
|
4748
|
+
standby: "standby",
|
|
4749
|
+
full: "full",
|
|
4750
|
+
recover: "recover",
|
|
4751
|
+
id: "id",
|
|
4634
4752
|
peek: "peek",
|
|
4635
4753
|
history: "history",
|
|
4636
4754
|
dissolve: "dissolve"
|
|
4637
4755
|
};
|
|
4638
4756
|
for (const [flag, field] of Object.entries(mapping))
|
|
4639
4757
|
if (v[flag] !== void 0) input[field] = v[flag];
|
|
4758
|
+
if (v.limit !== void 0) input.limit = Number(v.limit);
|
|
4640
4759
|
if (v.all) input.scope = "all";
|
|
4641
4760
|
if (v.wait !== void 0) input.wait = Number(v.wait);
|
|
4642
4761
|
if (args.length > 1) input[action === "ask" ? "question" : "message"] = args.slice(1).join(" ");
|
|
@@ -4646,6 +4765,7 @@ async function runSession(argv) {
|
|
|
4646
4765
|
throw new Error("--timeout must be >0 and <=3600 seconds");
|
|
4647
4766
|
client = new DaemonClient({
|
|
4648
4767
|
kind: "mcp",
|
|
4768
|
+
transport: "cli",
|
|
4649
4769
|
agent,
|
|
4650
4770
|
native_id: native,
|
|
4651
4771
|
cwd: process.cwd(),
|
|
@@ -4794,7 +4914,7 @@ async function probeMcp(root) {
|
|
|
4794
4914
|
}
|
|
4795
4915
|
|
|
4796
4916
|
// src/cli/main.ts
|
|
4797
|
-
import { readFileSync as
|
|
4917
|
+
import { readFileSync as readFileSync3, existsSync } from "node:fs";
|
|
4798
4918
|
import { dirname as dirname2, join as join5, resolve as resolve2 } from "node:path";
|
|
4799
4919
|
import { homedir as homedir2 } from "node:os";
|
|
4800
4920
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
@@ -4814,6 +4934,19 @@ if (process.argv[2] === "session") {
|
|
|
4814
4934
|
to: { type: "string" },
|
|
4815
4935
|
type: { type: "string" },
|
|
4816
4936
|
"reply-to": { type: "string" },
|
|
4937
|
+
priority: { type: "string" },
|
|
4938
|
+
"task-key": { type: "string" },
|
|
4939
|
+
reassign: { type: "string" },
|
|
4940
|
+
after: { type: "string" },
|
|
4941
|
+
for: { type: "string" },
|
|
4942
|
+
adapter: { type: "string" },
|
|
4943
|
+
executable: { type: "string" },
|
|
4944
|
+
socket: { type: "string" },
|
|
4945
|
+
resolve: { type: "string" },
|
|
4946
|
+
recover: { type: "boolean" },
|
|
4947
|
+
history: { type: "boolean" },
|
|
4948
|
+
id: { type: "string" },
|
|
4949
|
+
limit: { type: "string" },
|
|
4817
4950
|
session: { type: "string" },
|
|
4818
4951
|
peek: { type: "boolean" },
|
|
4819
4952
|
follow: { type: "boolean" },
|
|
@@ -4864,7 +4997,7 @@ if (process.argv[2] === "session") {
|
|
|
4864
4997
|
checks.daemon = "not running";
|
|
4865
4998
|
}
|
|
4866
4999
|
try {
|
|
4867
|
-
const conf =
|
|
5000
|
+
const conf = readFileSync3(
|
|
4868
5001
|
join5(process.env.CODEX_HOME || join5(homedir2(), ".codex"), "config.toml"),
|
|
4869
5002
|
"utf8"
|
|
4870
5003
|
);
|
|
@@ -4899,7 +5032,7 @@ if (process.argv[2] === "session") {
|
|
|
4899
5032
|
} else
|
|
4900
5033
|
checks.zcode = "Desktop app not found in /Applications; generic MCP configuration is available.";
|
|
4901
5034
|
try {
|
|
4902
|
-
const zconfig = JSON.parse(
|
|
5035
|
+
const zconfig = JSON.parse(readFileSync3(join5(homedir2(), ".zcode/cli/config.json"), "utf8"));
|
|
4903
5036
|
checks.zcode_user_hooks = zconfig.hooks?.enabled === true;
|
|
4904
5037
|
} catch {
|
|
4905
5038
|
checks.zcode_user_hooks = "No user hook config; installed plugin hooks follow plugin enablement.";
|
|
@@ -4909,7 +5042,7 @@ if (process.argv[2] === "session") {
|
|
|
4909
5042
|
try {
|
|
4910
5043
|
if (v.help)
|
|
4911
5044
|
process.stdout.write(
|
|
4912
|
-
"cmdr status | list [--all] [--squad ID] | tail [--follow] [--full] | send --squad ID [--to MEMBER] [--type command|info|answer] TEXT | read --session SID [--peek] | daemon start|stop|restart|status|logs | config [--agent HOST] [--session ID] | doctor [--plugin-root PATH] [--deep] | session --help | purge [--all]\n"
|
|
5045
|
+
"cmdr status | list [--all] [--squad ID] | tail [--follow] [--full] [--json] [--after EVENT_SEQ] [--for SID] | standby start|status|stop|resume --session SID [--adapter codex|manual] | send --squad ID [--to MEMBER] [--type command|cancel|info|answer] TEXT | read --session SID [--peek] | daemon start|stop|restart|status|logs | config [--agent HOST] [--session ID] | doctor [--plugin-root PATH] [--deep] | session --help | purge [--all]\n"
|
|
4913
5046
|
);
|
|
4914
5047
|
else if (cmd === "config") {
|
|
4915
5048
|
const agent = v.agent || "generic";
|
|
@@ -4933,7 +5066,8 @@ if (process.argv[2] === "session") {
|
|
|
4933
5066
|
else if (cmd === "list") {
|
|
4934
5067
|
const result = await call("session.list", {
|
|
4935
5068
|
scope: v.all ? "all" : void 0,
|
|
4936
|
-
squad: v.squad
|
|
5069
|
+
squad: v.squad,
|
|
5070
|
+
full: !!v.full
|
|
4937
5071
|
});
|
|
4938
5072
|
if (v.json) print(result);
|
|
4939
5073
|
else {
|
|
@@ -4945,11 +5079,16 @@ if (process.argv[2] === "session") {
|
|
|
4945
5079
|
title: s.title,
|
|
4946
5080
|
role: s.role,
|
|
4947
5081
|
squad: s.squad,
|
|
4948
|
-
|
|
5082
|
+
work: s.commands.map((m) => `${m.id}:${m.state}${m.cancel_requested_at ? ":cancelling" : ""}`).join(", "),
|
|
5083
|
+
unacked: s.unacked,
|
|
5084
|
+
in_progress: s.in_progress,
|
|
4949
5085
|
activity: s.activity,
|
|
5086
|
+
progress_seconds_ago: s.last_progress_at ? Math.floor((Date.now() - s.last_progress_at) / 1e3) : "unknown",
|
|
5087
|
+
connection: s.presence,
|
|
4950
5088
|
pending: s.pending,
|
|
5089
|
+
wake: `${s.listener.wake_mode}/${s.listener.health}`,
|
|
4951
5090
|
identity: s.native_id ? "confirmed" : "provisional",
|
|
4952
|
-
|
|
5091
|
+
seen_seconds_ago: Math.floor((Date.now() - s.last_seen) / 1e3),
|
|
4953
5092
|
cwd: s.cwd,
|
|
4954
5093
|
terminal: s.terminal?.program
|
|
4955
5094
|
}))
|
|
@@ -4972,6 +5111,9 @@ if (process.argv[2] === "session") {
|
|
|
4972
5111
|
to: v.to || "all",
|
|
4973
5112
|
type: v.type || "command",
|
|
4974
5113
|
reply_to: v["reply-to"],
|
|
5114
|
+
priority: v.priority,
|
|
5115
|
+
reassign: v.reassign,
|
|
5116
|
+
task_key: v["task-key"],
|
|
4975
5117
|
message: args.slice(1).join(" ")
|
|
4976
5118
|
},
|
|
4977
5119
|
true
|
|
@@ -4979,26 +5121,64 @@ if (process.argv[2] === "session") {
|
|
|
4979
5121
|
);
|
|
4980
5122
|
else if (cmd === "read") {
|
|
4981
5123
|
if (!v.session) throw new Error("--session SID is required");
|
|
4982
|
-
print(
|
|
5124
|
+
print(
|
|
5125
|
+
await call("admin.read", {
|
|
5126
|
+
sid: v.session,
|
|
5127
|
+
options: {
|
|
5128
|
+
peek: !!v.peek,
|
|
5129
|
+
history: !!v.history,
|
|
5130
|
+
full: !!v.full,
|
|
5131
|
+
recover: !!v.recover,
|
|
5132
|
+
id: v.id,
|
|
5133
|
+
limit: v.limit ? Number(v.limit) : void 0
|
|
5134
|
+
}
|
|
5135
|
+
})
|
|
5136
|
+
);
|
|
4983
5137
|
} else if (cmd === "purge") print(await call("admin.purge", { all: !!v.all }));
|
|
4984
|
-
else if (cmd === "
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
5138
|
+
else if (cmd === "standby") {
|
|
5139
|
+
if (!v.session) throw new Error("--session SID is required");
|
|
5140
|
+
print(
|
|
5141
|
+
await call(
|
|
5142
|
+
"admin.standby",
|
|
5143
|
+
{
|
|
5144
|
+
sid: v.session,
|
|
5145
|
+
action: args[1] || "status",
|
|
5146
|
+
adapter: v.adapter,
|
|
5147
|
+
executable: v.executable,
|
|
5148
|
+
socket: v.socket,
|
|
5149
|
+
resolve: v.resolve
|
|
5150
|
+
},
|
|
5151
|
+
true
|
|
5152
|
+
)
|
|
5153
|
+
);
|
|
5154
|
+
} else if (cmd === "tail") {
|
|
5155
|
+
const after = v.after === void 0 ? void 0 : Number(v.after);
|
|
5156
|
+
if (after !== void 0 && (!Number.isSafeInteger(after) || after < 0))
|
|
5157
|
+
throw new Error("--after requires a nonnegative event_seq");
|
|
5158
|
+
await tail({
|
|
5159
|
+
squad: v.squad,
|
|
5160
|
+
for: v.for,
|
|
5161
|
+
after,
|
|
5162
|
+
full: !!v.full,
|
|
5163
|
+
json: !!v.json,
|
|
5164
|
+
follow: !!v.follow
|
|
5165
|
+
});
|
|
4996
5166
|
} else if (cmd === "daemon") {
|
|
4997
5167
|
const action = args[1] || "status";
|
|
4998
5168
|
if (action === "logs")
|
|
4999
|
-
process.stdout.write(existsSync(p.log) ?
|
|
5169
|
+
process.stdout.write(existsSync(p.log) ? readFileSync3(p.log, "utf8") : "No daemon logs.\n");
|
|
5000
5170
|
else if (action === "status") print(await call("admin.status"));
|
|
5001
5171
|
else if (action === "stop" || action === "restart") {
|
|
5172
|
+
if (action === "restart")
|
|
5173
|
+
execFileSync(
|
|
5174
|
+
process.execPath,
|
|
5175
|
+
[
|
|
5176
|
+
"--experimental-sqlite",
|
|
5177
|
+
join5(dirname2(fileURLToPath3(import.meta.url)), "daemon.mjs"),
|
|
5178
|
+
"--preflight"
|
|
5179
|
+
],
|
|
5180
|
+
{ timeout: 15e3, stdio: ["ignore", "pipe", "pipe"] }
|
|
5181
|
+
);
|
|
5002
5182
|
try {
|
|
5003
5183
|
print(await call("admin.shutdown", { reason: action }));
|
|
5004
5184
|
} catch (e) {
|