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.
@@ -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`. The first becomes commander; others join as executors. Without slash commands, ask the Agent to call `join(squad_name="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`. Use each result's `me.recommended_wait` for long polling; the skills limit standby to 40 rounds.
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
- No active wakeups, Agent launching, remote transport or execution of message contents. Read marks delivery; use history if a host loses a previously delivered result. Idle recipients may need the user to say “continue”.
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 named local agent squad.
2
+ description: Create or join a persistent local agent channel.
3
3
  argument-hint: <name>
4
4
  ---
5
- Call the cmdr join tool with squad_name="$ARGUMENTS". This is an atomic find-or-create operation; do not list then create. If no name was provided, ask for one. Do not invent a name.
6
- If the squad is orphaned, ask whether to take over as commander or join as executor and then use explicit role and squad ID.
7
- Follow the returned protocol_hint. An executor reports ready with cwd, capabilities and context before replying. Translate user_reply into the user's language, keep the join line unchanged, and reply with only that text. Then use read(wait=me.recommended_wait) to participate.
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.1.1" : "0.1.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
- await rpc.request("admin.shutdown", { reason: "upgrade", version: VERSION }, timeout);
310
- rpc.close();
311
- await sleep(100);
312
- continue;
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) throw e;
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
- if (owner) {
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; commands do not wake agents."
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 readFileSync2, existsSync } from "node:fs";
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 = readFileSync2(
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(readFileSync2(join5(homedir2(), ".zcode/cli/config.json"), "utf8"));
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
- presence: s.presence,
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
- age_ms: Date.now() - s.created_at,
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(await call("admin.read", { sid: v.session, options: { peek: !!v.peek } }));
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 === "tail") {
4985
- const rpc = await daemonConnection();
4986
- await rpc.request("session.register", { kind: "cli" });
4987
- if (v.follow) {
4988
- rpc.on("notification", (method, value) => {
4989
- if (method === "msg.event") print(value);
4990
- });
4991
- await rpc.request("admin.tail", { squad: v.squad, full: !!v.full });
4992
- }
4993
- print(await rpc.request("admin.recent", { squad: v.squad, full: !!v.full }));
4994
- if (!v.follow) rpc.close();
4995
- else process.on("SIGINT", () => rpc.close());
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) ? readFileSync2(p.log, "utf8") : "No daemon logs.\n");
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) {