cmdr-mcp 0.1.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/.agents/plugins/marketplace.json +20 -0
- package/.claude-plugin/marketplace.json +15 -0
- package/LICENSE +21 -0
- package/README.md +149 -0
- package/docs/README.zh-CN.md +108 -0
- package/docs/agent-integration.md +70 -0
- package/docs/cmdr-design-v1.md +985 -0
- package/docs/implementation.md +71 -0
- package/docs/publishing.md +52 -0
- package/marketplace.json +12 -0
- package/package.json +68 -0
- package/plugins/cmdr/.claude-plugin/plugin.json +10 -0
- package/plugins/cmdr/.codex-plugin/plugin.json +34 -0
- package/plugins/cmdr/.mcp.json +11 -0
- package/plugins/cmdr/.zcode-plugin/plugin.json +24 -0
- package/plugins/cmdr/README.md +15 -0
- package/plugins/cmdr/THIRD_PARTY_NOTICES.txt +207 -0
- package/plugins/cmdr/bin/cmdr +4 -0
- package/plugins/cmdr/bin/cmdr-daemon +4 -0
- package/plugins/cmdr/bin/cmdr-hook +4 -0
- package/plugins/cmdr/bin/cmdr-mcp +4 -0
- package/plugins/cmdr/bin/cmdr-node +34 -0
- package/plugins/cmdr/commands/cmdr.md +7 -0
- package/plugins/cmdr/dist/cli.mjs +485 -0
- package/plugins/cmdr/dist/daemon.mjs +5427 -0
- package/plugins/cmdr/dist/hook.mjs +365 -0
- package/plugins/cmdr/dist/mcp.mjs +22002 -0
- package/plugins/cmdr/hooks/hooks.json +60 -0
- package/plugins/cmdr/skills/cmdr-commander/SKILL.md +12 -0
- package/plugins/cmdr/skills/cmdr-executor/SKILL.md +11 -0
- package/plugins/cmdr/skills/using-cmdr/SKILL.md +15 -0
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
#!/usr/bin/env -S node --experimental-sqlite --disable-warning=ExperimentalWarning
|
|
2
|
+
import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
|
|
3
|
+
|
|
4
|
+
// src/cli/main.ts
|
|
5
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
6
|
+
import { dirname as dirname2, join as join3 } from "node:path";
|
|
7
|
+
import { homedir as homedir2 } from "node:os";
|
|
8
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
9
|
+
import { execFileSync } from "node:child_process";
|
|
10
|
+
import { parseArgs } from "node:util";
|
|
11
|
+
|
|
12
|
+
// src/shared/client.ts
|
|
13
|
+
import { connect } from "node:net";
|
|
14
|
+
import { spawn } from "node:child_process";
|
|
15
|
+
import { dirname, join as join2 } from "node:path";
|
|
16
|
+
import { fileURLToPath } from "node:url";
|
|
17
|
+
import { mkdirSync as mkdirSync2, rmSync, statSync } from "node:fs";
|
|
18
|
+
|
|
19
|
+
// src/shared/paths.ts
|
|
20
|
+
import { homedir, tmpdir } from "node:os";
|
|
21
|
+
import { join, resolve } from "node:path";
|
|
22
|
+
import { mkdirSync, chmodSync } from "node:fs";
|
|
23
|
+
import { createHash } from "node:crypto";
|
|
24
|
+
|
|
25
|
+
// src/shared/ids.ts
|
|
26
|
+
var safeSid = (sid) => Buffer.from(sid).toString("base64url");
|
|
27
|
+
|
|
28
|
+
// src/shared/paths.ts
|
|
29
|
+
function paths(home = process.env.CMDR_HOME || join(homedir(), ".cmdr")) {
|
|
30
|
+
home = resolve(home);
|
|
31
|
+
let socket = join(home, "cmdr.sock");
|
|
32
|
+
if (Buffer.byteLength(socket) > 100)
|
|
33
|
+
socket = join(
|
|
34
|
+
tmpdir(),
|
|
35
|
+
`cmdr-${createHash("sha256").update(`${process.getuid?.()}:${home}`).digest("hex").slice(0, 20)}.sock`
|
|
36
|
+
);
|
|
37
|
+
return {
|
|
38
|
+
home,
|
|
39
|
+
socket,
|
|
40
|
+
db: join(home, "cmdr.db"),
|
|
41
|
+
lock: join(home, "daemon.lock"),
|
|
42
|
+
spawn: join(home, "spawn.lock"),
|
|
43
|
+
info: join(home, "daemon.json"),
|
|
44
|
+
flags: join(home, "flags"),
|
|
45
|
+
log: join(home, "logs/daemon.log"),
|
|
46
|
+
config: join(home, "config.json"),
|
|
47
|
+
flag: (sid) => join(home, "flags", safeSid(sid))
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function prepare(p2) {
|
|
51
|
+
for (const dir of [p2.home, p2.flags, join(p2.home, "logs")]) {
|
|
52
|
+
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
53
|
+
chmodSync(dir, 448);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// src/shared/rpc.ts
|
|
58
|
+
import { EventEmitter } from "node:events";
|
|
59
|
+
|
|
60
|
+
// src/shared/protocol.ts
|
|
61
|
+
var LIMITS = {
|
|
62
|
+
maxWaitSec: 300,
|
|
63
|
+
maxBody: 32768,
|
|
64
|
+
maxData: 65536,
|
|
65
|
+
maxFrame: 2 * 1024 * 1024
|
|
66
|
+
};
|
|
67
|
+
var CmdrError = class extends Error {
|
|
68
|
+
constructor(code, message = code) {
|
|
69
|
+
super(message);
|
|
70
|
+
this.code = code;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// src/shared/rpc.ts
|
|
75
|
+
var Rpc = class extends EventEmitter {
|
|
76
|
+
constructor(socket) {
|
|
77
|
+
super();
|
|
78
|
+
this.socket = socket;
|
|
79
|
+
socket.setEncoding("utf8");
|
|
80
|
+
socket.on("data", (chunk) => {
|
|
81
|
+
this.buffer += chunk;
|
|
82
|
+
if (Buffer.byteLength(this.buffer) > LIMITS.maxFrame) {
|
|
83
|
+
socket.destroy();
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
let at;
|
|
87
|
+
while ((at = this.buffer.indexOf("\n")) >= 0) {
|
|
88
|
+
const line = this.buffer.slice(0, at);
|
|
89
|
+
this.buffer = this.buffer.slice(at + 1);
|
|
90
|
+
if (line.trim()) void this.receive(line);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
socket.on("error", () => {
|
|
94
|
+
});
|
|
95
|
+
socket.on("close", () => {
|
|
96
|
+
for (const p2 of this.pending.values()) {
|
|
97
|
+
clearTimeout(p2.timer);
|
|
98
|
+
p2.cleanup();
|
|
99
|
+
p2.reject(new CmdrError("DAEMON_UNAVAILABLE"));
|
|
100
|
+
}
|
|
101
|
+
this.pending.clear();
|
|
102
|
+
for (const c of this.active.values()) c.abort();
|
|
103
|
+
this.active.clear();
|
|
104
|
+
this.emit("close");
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
buffer = "";
|
|
108
|
+
next = 1;
|
|
109
|
+
pending = /* @__PURE__ */ new Map();
|
|
110
|
+
active = /* @__PURE__ */ new Map();
|
|
111
|
+
handler;
|
|
112
|
+
async receive(line) {
|
|
113
|
+
let m;
|
|
114
|
+
try {
|
|
115
|
+
m = JSON.parse(line);
|
|
116
|
+
if (!m || m.jsonrpc !== "2.0" || Array.isArray(m)) throw new Error();
|
|
117
|
+
} catch {
|
|
118
|
+
this.send({
|
|
119
|
+
jsonrpc: "2.0",
|
|
120
|
+
id: null,
|
|
121
|
+
error: { code: -32700, message: "Invalid JSON-RPC frame" }
|
|
122
|
+
});
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (typeof m.method === "string") {
|
|
126
|
+
if (m.id === void 0) {
|
|
127
|
+
if (m.method === "rpc.cancel") this.active.get(m.params?.id)?.abort();
|
|
128
|
+
else this.emit("notification", m.method, m.params);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const controller = new AbortController();
|
|
132
|
+
this.active.set(m.id, controller);
|
|
133
|
+
try {
|
|
134
|
+
const result = await this.handler?.(m.method, m.params || {}, controller.signal);
|
|
135
|
+
this.send({ jsonrpc: "2.0", id: m.id, result: result ?? null });
|
|
136
|
+
} catch (e) {
|
|
137
|
+
this.send({
|
|
138
|
+
jsonrpc: "2.0",
|
|
139
|
+
id: m.id,
|
|
140
|
+
error: {
|
|
141
|
+
code: e instanceof CmdrError ? -32e3 : -32603,
|
|
142
|
+
message: e instanceof CmdrError ? e.message : "Internal daemon error",
|
|
143
|
+
data: { code: e instanceof CmdrError ? e.code : "INTERNAL_ERROR" }
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
} finally {
|
|
147
|
+
this.active.delete(m.id);
|
|
148
|
+
}
|
|
149
|
+
} else if (this.pending.has(m.id)) {
|
|
150
|
+
const p2 = this.pending.get(m.id);
|
|
151
|
+
this.pending.delete(m.id);
|
|
152
|
+
clearTimeout(p2.timer);
|
|
153
|
+
p2.cleanup();
|
|
154
|
+
if (m.error) p2.reject(new CmdrError(m.error.data?.code || "RPC_ERROR", m.error.message));
|
|
155
|
+
else p2.resolve(m.result);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
send(value) {
|
|
159
|
+
if (!this.socket.destroyed && this.socket.writable)
|
|
160
|
+
this.socket.write(JSON.stringify(value) + "\n");
|
|
161
|
+
}
|
|
162
|
+
request(method, params = {}, timeout = 5e3, signal) {
|
|
163
|
+
if (this.socket.destroyed) return Promise.reject(new CmdrError("DAEMON_UNAVAILABLE"));
|
|
164
|
+
if (signal?.aborted) return Promise.reject(new CmdrError("REQUEST_CANCELLED"));
|
|
165
|
+
return new Promise((resolve2, reject) => {
|
|
166
|
+
const id = this.next++;
|
|
167
|
+
const cancel = (code) => {
|
|
168
|
+
const p2 = this.pending.get(id);
|
|
169
|
+
if (!p2) return;
|
|
170
|
+
this.pending.delete(id);
|
|
171
|
+
clearTimeout(p2.timer);
|
|
172
|
+
p2.cleanup();
|
|
173
|
+
this.notify("rpc.cancel", { id });
|
|
174
|
+
reject(new CmdrError(code));
|
|
175
|
+
};
|
|
176
|
+
const abort = () => cancel("REQUEST_CANCELLED");
|
|
177
|
+
const timer = setTimeout(() => cancel("DAEMON_UNAVAILABLE"), timeout);
|
|
178
|
+
this.pending.set(id, {
|
|
179
|
+
resolve: resolve2,
|
|
180
|
+
reject,
|
|
181
|
+
timer,
|
|
182
|
+
cleanup: () => signal?.removeEventListener("abort", abort)
|
|
183
|
+
});
|
|
184
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
185
|
+
this.send({ jsonrpc: "2.0", id, method, params });
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
notify(method, params) {
|
|
189
|
+
this.send({ jsonrpc: "2.0", method, params });
|
|
190
|
+
}
|
|
191
|
+
close() {
|
|
192
|
+
this.socket.destroy();
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
// src/shared/version.ts
|
|
197
|
+
var VERSION = true ? "0.1.0" : "0.1.0";
|
|
198
|
+
var PROTOCOL = 1;
|
|
199
|
+
function newer(a, b) {
|
|
200
|
+
const x = a.split(".").map(Number), y = b.split(".").map(Number);
|
|
201
|
+
for (let i = 0; i < 3; i++) {
|
|
202
|
+
if (x[i] !== y[i]) return x[i] > y[i];
|
|
203
|
+
}
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// src/shared/client.ts
|
|
208
|
+
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
209
|
+
async function dial(p2, timeout = 1e3) {
|
|
210
|
+
return new Promise((resolve2, reject) => {
|
|
211
|
+
const socket = connect(p2.socket);
|
|
212
|
+
const timer = setTimeout(() => socket.destroy(new Error("connect timeout")), timeout);
|
|
213
|
+
socket.once("error", (e) => {
|
|
214
|
+
clearTimeout(timer);
|
|
215
|
+
reject(e);
|
|
216
|
+
});
|
|
217
|
+
socket.once("connect", () => {
|
|
218
|
+
clearTimeout(timer);
|
|
219
|
+
resolve2(new Rpc(socket));
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
async function daemonConnection(options = {}) {
|
|
224
|
+
const p2 = paths(options.home), timeout = options.timeout || 1e3;
|
|
225
|
+
if (options.start) prepare(p2);
|
|
226
|
+
let owner = false, spawned = false;
|
|
227
|
+
try {
|
|
228
|
+
for (let attempt = 0; attempt < (options.start ? 65 : 1); attempt++) {
|
|
229
|
+
let rpc;
|
|
230
|
+
try {
|
|
231
|
+
rpc = await dial(p2, timeout);
|
|
232
|
+
const hello = await rpc.request(
|
|
233
|
+
"hello",
|
|
234
|
+
{ client: "cmdr", version: VERSION, protocol: PROTOCOL },
|
|
235
|
+
timeout
|
|
236
|
+
);
|
|
237
|
+
if (options.upgrade && newer(VERSION, hello.version)) {
|
|
238
|
+
await rpc.request("admin.shutdown", { reason: "upgrade" }, timeout);
|
|
239
|
+
rpc.close();
|
|
240
|
+
await sleep(100);
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
return rpc;
|
|
244
|
+
} catch (e) {
|
|
245
|
+
rpc?.close();
|
|
246
|
+
if (e.code === "PROTOCOL_MISMATCH" || !options.start) throw e;
|
|
247
|
+
}
|
|
248
|
+
if (!owner) {
|
|
249
|
+
try {
|
|
250
|
+
mkdirSync2(p2.spawn, { mode: 448 });
|
|
251
|
+
owner = true;
|
|
252
|
+
} catch {
|
|
253
|
+
try {
|
|
254
|
+
if (Date.now() - statSync(p2.spawn).mtimeMs > 1e4)
|
|
255
|
+
rmSync(p2.spawn, { recursive: true, force: true });
|
|
256
|
+
} catch {
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (owner) {
|
|
261
|
+
if (attempt === 0 || !spawned) {
|
|
262
|
+
const child = spawn(
|
|
263
|
+
process.execPath,
|
|
264
|
+
["--experimental-sqlite", join2(dirname(fileURLToPath(import.meta.url)), "daemon.mjs")],
|
|
265
|
+
{ detached: true, stdio: "ignore", env: { ...process.env, CMDR_HOME: p2.home } }
|
|
266
|
+
);
|
|
267
|
+
child.on("error", () => {
|
|
268
|
+
});
|
|
269
|
+
child.unref();
|
|
270
|
+
spawned = true;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
await sleep(50);
|
|
274
|
+
}
|
|
275
|
+
} finally {
|
|
276
|
+
if (owner) rmSync(p2.spawn, { recursive: true, force: true });
|
|
277
|
+
spawned = false;
|
|
278
|
+
}
|
|
279
|
+
throw new CmdrError("DAEMON_UNAVAILABLE", "Cannot start cmdr daemon. Run cmdr doctor.");
|
|
280
|
+
}
|
|
281
|
+
async function quickCall(method, params = {}, options = {}) {
|
|
282
|
+
const rpc = await daemonConnection({ ...options, upgrade: !!options.start });
|
|
283
|
+
try {
|
|
284
|
+
if (options.kind !== "hook")
|
|
285
|
+
await rpc.request("session.register", { kind: "cli" }, options.timeout || 5e3);
|
|
286
|
+
return await rpc.request(method, params, options.timeout || 5e3);
|
|
287
|
+
} finally {
|
|
288
|
+
rpc.close();
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// src/cli/main.ts
|
|
293
|
+
var { values: v, positionals: args } = parseArgs({
|
|
294
|
+
allowPositionals: true,
|
|
295
|
+
options: {
|
|
296
|
+
agent: { type: "string" },
|
|
297
|
+
all: { type: "boolean" },
|
|
298
|
+
squad: { type: "string" },
|
|
299
|
+
to: { type: "string" },
|
|
300
|
+
type: { type: "string" },
|
|
301
|
+
"reply-to": { type: "string" },
|
|
302
|
+
session: { type: "string" },
|
|
303
|
+
peek: { type: "boolean" },
|
|
304
|
+
follow: { type: "boolean" },
|
|
305
|
+
full: { type: "boolean" },
|
|
306
|
+
json: { type: "boolean" },
|
|
307
|
+
help: { type: "boolean" }
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
var p = paths();
|
|
311
|
+
var cmd = args[0] || "status";
|
|
312
|
+
var print = (value) => process.stdout.write(JSON.stringify(value, null, 2) + "\n");
|
|
313
|
+
var call = (method, params = {}, start = false) => quickCall(method, params, { start });
|
|
314
|
+
var pause = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
315
|
+
async function doctor() {
|
|
316
|
+
const checks = {
|
|
317
|
+
node: process.version,
|
|
318
|
+
node_path: process.execPath,
|
|
319
|
+
cmdr: VERSION,
|
|
320
|
+
home: p.home
|
|
321
|
+
};
|
|
322
|
+
const root = dirname2(dirname2(fileURLToPath2(import.meta.url)));
|
|
323
|
+
checks.bundles = Object.fromEntries(
|
|
324
|
+
["daemon", "mcp", "hook", "cli"].map((n) => [n, existsSync(join3(root, "dist", `${n}.mjs`))])
|
|
325
|
+
);
|
|
326
|
+
for (const host of ["claude", "codex"]) {
|
|
327
|
+
try {
|
|
328
|
+
checks[host] = execFileSync(host, ["--version"], { encoding: "utf8", timeout: 3e3 }).trim();
|
|
329
|
+
} catch {
|
|
330
|
+
checks[host] = "CLI not available in PATH";
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
try {
|
|
334
|
+
checks.daemon = await call("admin.status");
|
|
335
|
+
} catch {
|
|
336
|
+
checks.daemon = "not running";
|
|
337
|
+
}
|
|
338
|
+
try {
|
|
339
|
+
const conf = readFileSync(
|
|
340
|
+
join3(process.env.CODEX_HOME || join3(homedir2(), ".codex"), "config.toml"),
|
|
341
|
+
"utf8"
|
|
342
|
+
);
|
|
343
|
+
checks.codex_hooks = /(?:^|\n)\s*hooks\s*=\s*false/.test(conf) ? "disabled" : "not explicitly disabled; verify features.hooks in Codex";
|
|
344
|
+
checks.codex_hook_trust = "Verify all five cmdr hooks in the Codex trust UI (storage format is host-specific).";
|
|
345
|
+
if (/cmdr/.test(conf)) {
|
|
346
|
+
const listing = execFileSync("codex", ["mcp", "list"], {
|
|
347
|
+
encoding: "utf8",
|
|
348
|
+
timeout: 5e3,
|
|
349
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
350
|
+
});
|
|
351
|
+
checks.codex_mcp_registered = /\bcmdr\b/.test(listing);
|
|
352
|
+
} else
|
|
353
|
+
checks.codex_mcp_registered = "No cmdr entry found in config; run codex mcp list after installing.";
|
|
354
|
+
} catch {
|
|
355
|
+
checks.codex_hooks = "Codex configuration unavailable";
|
|
356
|
+
}
|
|
357
|
+
const zcodeRoot = "/Applications/ZCode.app/Contents";
|
|
358
|
+
if (existsSync(join3(zcodeRoot, "Info.plist"))) {
|
|
359
|
+
try {
|
|
360
|
+
checks.zcode = {
|
|
361
|
+
app_version: execFileSync(
|
|
362
|
+
"/usr/libexec/PlistBuddy",
|
|
363
|
+
["-c", "Print :CFBundleShortVersionString", join3(zcodeRoot, "Info.plist")],
|
|
364
|
+
{ encoding: "utf8", timeout: 1e3 }
|
|
365
|
+
).trim(),
|
|
366
|
+
runtime: join3(zcodeRoot, "Resources/glm/zcode.cjs")
|
|
367
|
+
};
|
|
368
|
+
} catch {
|
|
369
|
+
checks.zcode = "Installed";
|
|
370
|
+
}
|
|
371
|
+
} else
|
|
372
|
+
checks.zcode = "Desktop app not found in /Applications; generic MCP configuration is available.";
|
|
373
|
+
try {
|
|
374
|
+
const zconfig = JSON.parse(readFileSync(join3(homedir2(), ".zcode/cli/config.json"), "utf8"));
|
|
375
|
+
checks.zcode_user_hooks = zconfig.hooks?.enabled === true;
|
|
376
|
+
} catch {
|
|
377
|
+
checks.zcode_user_hooks = "No user hook config; installed plugin hooks follow plugin enablement.";
|
|
378
|
+
}
|
|
379
|
+
print(checks);
|
|
380
|
+
}
|
|
381
|
+
try {
|
|
382
|
+
if (v.help)
|
|
383
|
+
process.stdout.write(
|
|
384
|
+
"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 | purge [--all]\n"
|
|
385
|
+
);
|
|
386
|
+
else if (cmd === "config") {
|
|
387
|
+
const agent = v.agent || "generic";
|
|
388
|
+
if (!/^[a-z][a-z0-9_-]{0,63}$/.test(agent))
|
|
389
|
+
throw new Error("Invalid --agent: use lowercase letters, digits, underscores or hyphens.");
|
|
390
|
+
const root = dirname2(dirname2(fileURLToPath2(import.meta.url)));
|
|
391
|
+
const server = {
|
|
392
|
+
command: join3(root, "bin/cmdr-mcp"),
|
|
393
|
+
env: {
|
|
394
|
+
CMDR_AGENT: agent,
|
|
395
|
+
...v.session ? { CMDR_SESSION_ID: v.session } : {},
|
|
396
|
+
...agent === "zcode" ? { CMDR_TOOL_TIMEOUT_SEC: "600" } : {}
|
|
397
|
+
},
|
|
398
|
+
...agent === "zcode" ? { timeoutMs: 6e5 } : {}
|
|
399
|
+
};
|
|
400
|
+
print(
|
|
401
|
+
agent === "zcode" ? { mcp: { servers: { cmdr: server } } } : { mcpServers: { cmdr: server } }
|
|
402
|
+
);
|
|
403
|
+
} else if (cmd === "doctor") await doctor();
|
|
404
|
+
else if (cmd === "status") print(await call("admin.status"));
|
|
405
|
+
else if (cmd === "list") {
|
|
406
|
+
const result = await call("session.list", { scope: v.all ? "all" : void 0, squad: v.squad });
|
|
407
|
+
if (v.json) print(result);
|
|
408
|
+
else {
|
|
409
|
+
console.table(
|
|
410
|
+
result.sessions.map((s) => ({
|
|
411
|
+
sid: s.short,
|
|
412
|
+
agent: s.agent,
|
|
413
|
+
name: s.name,
|
|
414
|
+
title: s.title,
|
|
415
|
+
role: s.role,
|
|
416
|
+
squad: s.squad,
|
|
417
|
+
presence: s.presence,
|
|
418
|
+
activity: s.activity,
|
|
419
|
+
pending: s.pending,
|
|
420
|
+
cwd: s.cwd,
|
|
421
|
+
terminal: s.terminal?.program
|
|
422
|
+
}))
|
|
423
|
+
);
|
|
424
|
+
console.table(
|
|
425
|
+
result.squads.map((s) => ({
|
|
426
|
+
id: s.id,
|
|
427
|
+
name: s.name,
|
|
428
|
+
status: s.status,
|
|
429
|
+
commander: s.commander_sid
|
|
430
|
+
}))
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
} else if (cmd === "send")
|
|
434
|
+
print(
|
|
435
|
+
await call(
|
|
436
|
+
"msg.send",
|
|
437
|
+
{
|
|
438
|
+
squad: v.squad,
|
|
439
|
+
to: v.to || "all",
|
|
440
|
+
type: v.type || "command",
|
|
441
|
+
reply_to: v["reply-to"],
|
|
442
|
+
message: args.slice(1).join(" ")
|
|
443
|
+
},
|
|
444
|
+
true
|
|
445
|
+
)
|
|
446
|
+
);
|
|
447
|
+
else if (cmd === "read") {
|
|
448
|
+
if (!v.session) throw new Error("--session SID is required");
|
|
449
|
+
print(await call("admin.read", { sid: v.session, options: { peek: !!v.peek } }));
|
|
450
|
+
} else if (cmd === "purge") print(await call("admin.purge", { all: !!v.all }));
|
|
451
|
+
else if (cmd === "tail") {
|
|
452
|
+
const rpc = await daemonConnection();
|
|
453
|
+
await rpc.request("session.register", { kind: "cli" });
|
|
454
|
+
if (v.follow) {
|
|
455
|
+
rpc.on("notification", (method, value) => {
|
|
456
|
+
if (method === "msg.event") print(value);
|
|
457
|
+
});
|
|
458
|
+
await rpc.request("admin.tail", { squad: v.squad, full: !!v.full });
|
|
459
|
+
}
|
|
460
|
+
print(await rpc.request("admin.recent", { squad: v.squad, full: !!v.full }));
|
|
461
|
+
if (!v.follow) rpc.close();
|
|
462
|
+
else process.on("SIGINT", () => rpc.close());
|
|
463
|
+
} else if (cmd === "daemon") {
|
|
464
|
+
const action = args[1] || "status";
|
|
465
|
+
if (action === "logs")
|
|
466
|
+
process.stdout.write(existsSync(p.log) ? readFileSync(p.log, "utf8") : "No daemon logs.\n");
|
|
467
|
+
else if (action === "status") print(await call("admin.status"));
|
|
468
|
+
else if (action === "stop" || action === "restart") {
|
|
469
|
+
try {
|
|
470
|
+
print(await call("admin.shutdown", { reason: action }));
|
|
471
|
+
} catch (e) {
|
|
472
|
+
if (action === "stop") throw e;
|
|
473
|
+
}
|
|
474
|
+
if (action === "restart") {
|
|
475
|
+
for (let n = 0; n < 60 && existsSync(p.socket); n++) await pause(50);
|
|
476
|
+
print(await call("admin.status", {}, true));
|
|
477
|
+
}
|
|
478
|
+
} else if (action === "start") print(await call("admin.status", {}, true));
|
|
479
|
+
else throw new Error(`Unknown daemon action: ${action}`);
|
|
480
|
+
} else throw new Error(`Unknown command: ${cmd}`);
|
|
481
|
+
} catch (e) {
|
|
482
|
+
process.stderr.write(`cmdr: ${e.code || "ERROR"}: ${e.message}
|
|
483
|
+
`);
|
|
484
|
+
process.exitCode = 1;
|
|
485
|
+
}
|