cmdr-mcp 0.4.0 → 0.6.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.
Files changed (38) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/README.md +44 -17
  3. package/docs/README.zh-CN.md +33 -8
  4. package/docs/agent-integration.md +74 -12
  5. package/docs/dashboard-plan.md +327 -0
  6. package/docs/dashboard.md +101 -0
  7. package/docs/implementation.md +25 -4
  8. package/docs/long-running-collaboration.md +4 -2
  9. package/docs/publishing.md +11 -9
  10. package/docs/setup.md +4 -3
  11. package/docs/troubleshooting.md +3 -3
  12. package/marketplace.json +2 -2
  13. package/package.json +10 -2
  14. package/plugins/cmdr/.claude-plugin/plugin.json +1 -1
  15. package/plugins/cmdr/.codex-plugin/plugin.json +1 -1
  16. package/plugins/cmdr/.kimi-plugin/plugin.json +65 -0
  17. package/plugins/cmdr/.zcode-plugin/plugin.json +1 -1
  18. package/plugins/cmdr/README.md +7 -5
  19. package/plugins/cmdr/THIRD_PARTY_NOTICES.txt +130 -0
  20. package/plugins/cmdr/bin/cmdr-check.mjs +6 -1
  21. package/plugins/cmdr/bin/cmdr-node +8 -1
  22. package/plugins/cmdr/commands/cmdr.md +3 -1
  23. package/plugins/cmdr/dist/cli.mjs +226 -43
  24. package/plugins/cmdr/dist/daemon.mjs +4792 -161
  25. package/plugins/cmdr/dist/dashboard/app.css +1 -0
  26. package/plugins/cmdr/dist/dashboard/app.js +10 -0
  27. package/plugins/cmdr/dist/dashboard/index.html +13 -0
  28. package/plugins/cmdr/dist/hook.mjs +22 -4
  29. package/plugins/cmdr/dist/integrity.json +24 -19
  30. package/plugins/cmdr/dist/mcp.mjs +148 -66
  31. package/plugins/cmdr/kimi-identity/SKILL.md +12 -0
  32. package/plugins/cmdr/skills/cmdr/SKILL.md +4 -2
  33. package/plugins/cmdr/skills/cmdr/references/commander.md +3 -1
  34. package/plugins/cmdr/skills/cmdr/references/executor.md +3 -1
  35. package/plugins/cmdr/skills/cmdr/references/setup.md +1 -1
  36. package/plugins/cmdr/skills/cmdr-commander/SKILL.md +3 -1
  37. package/plugins/cmdr/skills/cmdr-executor/SKILL.md +3 -1
  38. package/plugins/cmdr/skills/using-cmdr/SKILL.md +3 -3
@@ -240,26 +240,26 @@ var Rpc = class extends EventEmitter {
240
240
  if (this.socket.destroyed) return Promise.reject(new CmdrError("DAEMON_UNAVAILABLE"));
241
241
  if (signal?.aborted) return Promise.reject(new CmdrError("REQUEST_CANCELLED"));
242
242
  return new Promise((resolve4, reject) => {
243
- const id = this.next++;
243
+ const id2 = this.next++;
244
244
  const cancel = (code) => {
245
- const p = this.pending.get(id);
245
+ const p = this.pending.get(id2);
246
246
  if (!p) return;
247
- this.pending.delete(id);
247
+ this.pending.delete(id2);
248
248
  clearTimeout(p.timer);
249
249
  p.cleanup();
250
- this.notify("rpc.cancel", { id });
250
+ this.notify("rpc.cancel", { id: id2 });
251
251
  reject(new CmdrError(code));
252
252
  };
253
253
  const abort = () => cancel("REQUEST_CANCELLED");
254
254
  const timer = setTimeout(() => cancel("DAEMON_UNAVAILABLE"), timeout);
255
- this.pending.set(id, {
255
+ this.pending.set(id2, {
256
256
  resolve: resolve4,
257
257
  reject,
258
258
  timer,
259
259
  cleanup: () => signal?.removeEventListener("abort", abort)
260
260
  });
261
261
  signal?.addEventListener("abort", abort, { once: true });
262
- this.send({ jsonrpc: "2.0", id, method, params });
262
+ this.send({ jsonrpc: "2.0", id: id2, method, params });
263
263
  });
264
264
  }
265
265
  notify(method, params) {
@@ -271,7 +271,7 @@ var Rpc = class extends EventEmitter {
271
271
  };
272
272
 
273
273
  // src/shared/version.ts
274
- var VERSION = true ? "0.4.0" : MIN_CLIENT_VERSION;
274
+ var VERSION = true ? "0.6.0" : MIN_CLIENT_VERSION;
275
275
  var PROTOCOL = 1;
276
276
  function newer(a, b) {
277
277
  const x = a.split(".").map(Number), y = b.split(".").map(Number);
@@ -497,7 +497,7 @@ async function watch(sid, once = false) {
497
497
  instruction: "Call read and read(recover=true); handle cancellation first and report with reply_to."
498
498
  }) + "\n"
499
499
  );
500
- if (once || result.wake_mode === "zcode") stop();
500
+ if (once || result.wake_mode === "zcode" || result.wake_mode === "kimi") stop();
501
501
  };
502
502
  const check = async () => {
503
503
  if (stopped) return;
@@ -4664,8 +4664,70 @@ var coerce = {
4664
4664
  };
4665
4665
  var NEVER = INVALID;
4666
4666
 
4667
+ // src/shared/dashboard.ts
4668
+ var DASHBOARD_LIMITS = {
4669
+ recordsPerKind: 200,
4670
+ runsPerTask: 100,
4671
+ htmlBytes: 256 * 1024,
4672
+ htmlBytesPerSquad: 8 * 1024 * 1024
4673
+ };
4674
+
4675
+ // src/shared/dashboard-schemas.ts
4676
+ var id = external_exports.string().min(1).max(128);
4677
+ var text = external_exports.string().max(8e3);
4678
+ var artifacts = external_exports.array(id).max(8);
4679
+ var taskFields = {
4680
+ action: external_exports.enum(["create", "update", "get", "list", "archive", "restore"]).default("list"),
4681
+ id: id.optional(),
4682
+ title: external_exports.string().trim().min(1).max(200).optional(),
4683
+ description: text.optional(),
4684
+ acceptance: text.optional(),
4685
+ position: external_exports.number().finite().optional(),
4686
+ artifact_ids: artifacts.optional(),
4687
+ archived: external_exports.boolean().optional(),
4688
+ offset: external_exports.number().int().min(0).default(0),
4689
+ limit: external_exports.number().int().min(1).max(50).default(20)
4690
+ };
4691
+ var questionFields = {
4692
+ target: external_exports.enum(["commander", "user"]).default("commander"),
4693
+ action: external_exports.enum(["create", "update", "get", "list", "withdraw", "handle"]).default("create"),
4694
+ id: id.optional(),
4695
+ task_id: id.optional(),
4696
+ version: external_exports.number().int().positive().optional(),
4697
+ description: text.optional(),
4698
+ kind: external_exports.enum(["single", "multiple", "text", "confirm"]).optional(),
4699
+ options: external_exports.array(external_exports.object({ id, label: external_exports.string().trim().min(1).max(300) }).strict()).max(20).optional(),
4700
+ artifact_ids: artifacts.optional(),
4701
+ result: text.optional(),
4702
+ status: external_exports.enum(["pending", "answered", "handled", "withdrawn"]).optional(),
4703
+ offset: external_exports.number().int().min(0).default(0),
4704
+ limit: external_exports.number().int().min(1).max(50).default(20)
4705
+ };
4706
+ var artifactFields = {
4707
+ action: external_exports.enum(["publish", "get", "list"]).default("publish"),
4708
+ id: id.optional(),
4709
+ version: external_exports.number().int().positive().optional(),
4710
+ title: external_exports.string().trim().min(1).max(200).optional(),
4711
+ html: external_exports.string().min(1).refine((s) => Buffer.byteLength(s) <= DASHBOARD_LIMITS.htmlBytes, "MESSAGE_TOO_LARGE").optional(),
4712
+ offset: external_exports.number().int().min(0).default(0),
4713
+ limit: external_exports.number().int().min(1).max(50).default(20)
4714
+ };
4715
+ var answerSchema = external_exports.object({
4716
+ question_id: id,
4717
+ version: external_exports.number().int().positive(),
4718
+ submission_id: external_exports.string().uuid(),
4719
+ selected: external_exports.array(id).max(20).default([]),
4720
+ text: text.default(""),
4721
+ confirmed: external_exports.boolean().optional()
4722
+ }).strict();
4723
+ var userMessageSchema = external_exports.object({
4724
+ squad_id: id,
4725
+ submission_id: external_exports.string().uuid(),
4726
+ text: external_exports.string().trim().min(1).max(8e3)
4727
+ }).strict();
4728
+
4667
4729
  // src/shared/schemas.ts
4668
- var text = external_exports.string().min(1).refine((v) => Buffer.byteLength(v) <= LIMITS.maxBody, "MESSAGE_TOO_LARGE");
4730
+ var text2 = external_exports.string().min(1).refine((v) => Buffer.byteLength(v) <= LIMITS.maxBody, "MESSAGE_TOO_LARGE");
4669
4731
  var data = external_exports.record(external_exports.unknown()).refine((v) => Buffer.byteLength(JSON.stringify(v)) <= LIMITS.maxData, "MESSAGE_TOO_LARGE").optional();
4670
4732
  var wait = external_exports.number().min(0).max(300).default(0);
4671
4733
  var identity = { _cmdr_session: external_exports.string().min(1).max(256).optional() };
@@ -4675,7 +4737,7 @@ var schemas = {
4675
4737
  role: external_exports.enum(["commander", "executor"]).optional(),
4676
4738
  squad: external_exports.string().optional(),
4677
4739
  name: external_exports.string().trim().min(1).max(64).optional(),
4678
- note: text.optional(),
4740
+ note: text2.optional(),
4679
4741
  squad_name: external_exports.string().trim().min(1).max(64).optional(),
4680
4742
  takeover: external_exports.boolean().default(false),
4681
4743
  standby: external_exports.enum(["auto", "manual"]).optional(),
@@ -4690,17 +4752,27 @@ var schemas = {
4690
4752
  report: external_exports.object({
4691
4753
  ...identity,
4692
4754
  status: external_exports.enum(["ready", "working", "blocked", "done", "failed", "cancelled"]),
4693
- message: text,
4755
+ message: text2,
4694
4756
  reply_to: external_exports.string().optional(),
4695
4757
  data
4696
4758
  }).strict(),
4697
- ask: external_exports.object({ ...identity, question: text, wait, reply_to: external_exports.string().optional(), data }).strict(),
4759
+ ask: external_exports.object({
4760
+ ...identity,
4761
+ ...questionFields,
4762
+ question: text2.optional(),
4763
+ wait,
4764
+ reply_to: external_exports.string().optional(),
4765
+ data
4766
+ }).strict(),
4767
+ task: external_exports.object({ ...identity, ...taskFields }).strict(),
4768
+ artifact: external_exports.object({ ...identity, ...artifactFields }).strict(),
4698
4769
  send: external_exports.object({
4699
4770
  ...identity,
4700
4771
  to: external_exports.union([external_exports.string().min(1), external_exports.array(external_exports.string().min(1)).min(1).max(1e3)]),
4701
- message: text,
4772
+ message: text2,
4702
4773
  type: external_exports.enum(["command", "cancel", "answer", "info"]).default("command"),
4703
4774
  task_key: external_exports.string().min(1).max(128).optional(),
4775
+ task_id: external_exports.string().min(1).max(128).optional(),
4704
4776
  reassign: external_exports.string().optional(),
4705
4777
  attention: external_exports.boolean().optional(),
4706
4778
  priority: external_exports.enum(["high", "normal", "low"]).optional(),
@@ -4718,7 +4790,7 @@ var schemas = {
4718
4790
  recover: external_exports.boolean().default(false),
4719
4791
  full: external_exports.boolean().default(false)
4720
4792
  }).strict(),
4721
- leave: external_exports.object({ ...identity, dissolve: external_exports.boolean().default(false), message: text.optional() }).strict()
4793
+ leave: external_exports.object({ ...identity, dissolve: external_exports.boolean().default(false), message: text2.optional() }).strict()
4722
4794
  };
4723
4795
  function parse(tool, args) {
4724
4796
  const r = schemas[tool].safeParse(args);
@@ -4738,10 +4810,13 @@ var methods = {
4738
4810
  report: "msg.report",
4739
4811
  ask: "msg.ask",
4740
4812
  read: "msg.read",
4741
- leave: "session.leave"
4813
+ leave: "session.leave",
4814
+ task: "dashboard.task",
4815
+ artifact: "dashboard.artifact"
4742
4816
  };
4743
4817
 
4744
4818
  // src/shared/env.ts
4819
+ var cmdrTool = /(?:^|[_:])cmdr(?:__|:)(list|join|report|leave|ask|send|read|task|artifact)$/;
4745
4820
  function waitRecommendation(agent, env = process.env) {
4746
4821
  const timeout = Number(env.CMDR_TOOL_TIMEOUT_SEC);
4747
4822
  if (Number.isFinite(timeout) && timeout > 0) {
@@ -4787,6 +4862,7 @@ async function runSession(argv) {
4787
4862
  "reply-to": { type: "string" },
4788
4863
  priority: { type: "string" },
4789
4864
  "task-key": { type: "string" },
4865
+ "task-id": { type: "string" },
4790
4866
  reassign: { type: "string" },
4791
4867
  rebind: { type: "string" },
4792
4868
  takeover: { type: "boolean" },
@@ -4804,13 +4880,13 @@ async function runSession(argv) {
4804
4880
  });
4805
4881
  if (v.help) {
4806
4882
  console.log(
4807
- "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."
4883
+ "cmdr session join|list|send|report|ask|read|leave|task|artifact --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."
4808
4884
  );
4809
4885
  return;
4810
4886
  }
4811
4887
  const action = args[0];
4812
4888
  if (!Object.hasOwn(schemas, action))
4813
- throw new Error("Expected session join|list|send|report|ask|read|leave");
4889
+ throw new Error("Expected session join|list|send|report|ask|read|leave|task|artifact");
4814
4890
  const agent = v.agent || process.env.CMDR_AGENT;
4815
4891
  const native = v["native-id"] || process.env.CMDR_SESSION_ID;
4816
4892
  if (!agent || !/^[a-z][a-z0-9_-]{0,63}$/.test(agent))
@@ -4837,6 +4913,7 @@ async function runSession(argv) {
4837
4913
  "reply-to": "reply_to",
4838
4914
  priority: "priority",
4839
4915
  "task-key": "task_key",
4916
+ "task-id": "task_id",
4840
4917
  reassign: "reassign",
4841
4918
  rebind: "rebind",
4842
4919
  takeover: "takeover",
@@ -4952,9 +5029,9 @@ async function probeMcp(root) {
4952
5029
  reject(new Error("MCP exited"));
4953
5030
  return;
4954
5031
  }
4955
- const id = ++requestId;
4956
- pending.set(id, { resolve: resolve4, reject });
4957
- child.stdin.write(JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n");
5032
+ const id2 = ++requestId;
5033
+ pending.set(id2, { resolve: resolve4, reject });
5034
+ child.stdin.write(JSON.stringify({ jsonrpc: "2.0", id: id2, method, params }) + "\n");
4958
5035
  });
4959
5036
  let timer;
4960
5037
  try {
@@ -4970,8 +5047,8 @@ async function probeMcp(root) {
4970
5047
  );
4971
5048
  const result = await request("tools/list", {});
4972
5049
  const names = result.tools?.map((t) => t.name).sort();
4973
- if (JSON.stringify(names) !== JSON.stringify(["ask", "join", "leave", "list", "read", "report", "send"]))
4974
- throw new Error("Expected seven cmdr tools");
5050
+ if (JSON.stringify(names) !== JSON.stringify(Object.keys(schemas).sort()))
5051
+ throw new Error("Unexpected cmdr tool set");
4975
5052
  const call = await request("tools/call", { name: "list", arguments: {} });
4976
5053
  if (call.isError) throw new Error("MCP tool cannot reach daemon");
4977
5054
  return { ok: true, tools: names, isolated: true };
@@ -5837,9 +5914,9 @@ function object(value, label) {
5837
5914
  throw new Error(`${label} must be an object; no configuration was changed.`);
5838
5915
  return value;
5839
5916
  }
5840
- function jsonConfig(text2, label) {
5917
+ function jsonConfig(text3, label) {
5841
5918
  try {
5842
- return object(JSON.parse(text2 || "{}"), label);
5919
+ return object(JSON.parse(text3 || "{}"), label);
5843
5920
  } catch {
5844
5921
  throw new Error(`Cannot parse ${label} as a JSON object; no configuration was changed.`);
5845
5922
  }
@@ -5867,7 +5944,8 @@ function serverConfig(previous, command, host) {
5867
5944
  command,
5868
5945
  args: [],
5869
5946
  ...host === "codex" ? { tool_timeout_sec: 600 } : { type: "stdio" },
5870
- ...host === "zcode" ? { timeoutMs: 6e5 } : {}
5947
+ ...host === "zcode" ? { timeoutMs: 6e5 } : {},
5948
+ ...host === "kimi" ? { toolTimeoutMs: 6e5 } : {}
5871
5949
  };
5872
5950
  }
5873
5951
  function mergeJsonServer(config, command, host) {
@@ -5876,10 +5954,10 @@ function mergeJsonServer(config, command, host) {
5876
5954
  const servers = parent[key] = object(parent[key] ?? {}, key);
5877
5955
  servers.cmdr = serverConfig(servers.cmdr, command, host);
5878
5956
  }
5879
- function codexConfig(text2, command) {
5957
+ function codexConfig(text3, command) {
5880
5958
  let config;
5881
5959
  try {
5882
- config = parse2(text2, { integersAsBigInt: "asNeeded" });
5960
+ config = parse2(text3, { integersAsBigInt: "asNeeded" });
5883
5961
  } catch {
5884
5962
  throw new Error("Cannot parse Codex config.toml; no configuration was changed.");
5885
5963
  }
@@ -5888,20 +5966,20 @@ function codexConfig(text2, command) {
5888
5966
  const server = serverConfig(servers.cmdr, command, "codex");
5889
5967
  const block = `${begin}
5890
5968
  ${stringify({ mcp_servers: { cmdr: server } })}${end}`;
5891
- const start = text2.indexOf(begin), stop = text2.indexOf(end);
5969
+ const start = text3.indexOf(begin), stop = text3.indexOf(end);
5892
5970
  let output;
5893
5971
  if (start >= 0 || stop >= 0) {
5894
- if (start < 0 || stop < start || text2.indexOf(begin, start + begin.length) >= 0 || text2.indexOf(end, stop + end.length) >= 0)
5972
+ if (start < 0 || stop < start || text3.indexOf(begin, start + begin.length) >= 0 || text3.indexOf(end, stop + end.length) >= 0)
5895
5973
  throw new Error(
5896
5974
  "Invalid cmdr setup markers in config.toml. Restore the managed block before retrying."
5897
5975
  );
5898
- output = text2.slice(0, start) + block + text2.slice(stop + end.length);
5976
+ output = text3.slice(0, start) + block + text3.slice(stop + end.length);
5899
5977
  } else {
5900
5978
  if (servers.cmdr !== void 0)
5901
5979
  throw new Error(
5902
5980
  "The cmdr MCP entry is not managed by setup. Remove that entry before retrying."
5903
5981
  );
5904
- output = `${text2}${text2 && !text2.endsWith("\n") ? "\n" : ""}
5982
+ output = `${text3}${text3 && !text3.endsWith("\n") ? "\n" : ""}
5905
5983
  ${block}
5906
5984
  `;
5907
5985
  }
@@ -5920,6 +5998,58 @@ ${block}
5920
5998
  serverDisabled: server.enabled === false
5921
5999
  };
5922
6000
  }
6001
+ function kimiHooksConfig(text3, executable) {
6002
+ const blockHooks = [
6003
+ { event: "SessionStart" },
6004
+ { event: "UserPromptSubmit" },
6005
+ { event: "PreToolUse", matcher: cmdrTool.source },
6006
+ { event: "Stop" },
6007
+ { event: "SessionEnd", timeout: 1 }
6008
+ ].map(({ event, matcher, timeout }) => ({
6009
+ event,
6010
+ ...matcher ? { matcher } : {},
6011
+ command: `${shellQuote(executable)} ${event}`,
6012
+ timeout: timeout ?? 5
6013
+ }));
6014
+ const block = `${begin}
6015
+ ${stringify({ hooks: blockHooks })}${end}`;
6016
+ const start = text3.indexOf(begin), stop = text3.indexOf(end);
6017
+ if ((start >= 0 || stop >= 0) && (start < 0 || stop < start))
6018
+ throw new Error(
6019
+ "Invalid cmdr setup markers in config.toml. Restore the managed block before retrying."
6020
+ );
6021
+ const output = start >= 0 ? text3.slice(0, start) + block + text3.slice(stop + end.length) : `${text3}${text3 && !text3.endsWith("\n") ? "\n" : ""}
6022
+ ${block}
6023
+ `;
6024
+ let full;
6025
+ try {
6026
+ full = parse2(text3, { integersAsBigInt: "asNeeded" });
6027
+ } catch {
6028
+ throw new Error("Cannot parse Kimi Code config.toml; no configuration was changed.");
6029
+ }
6030
+ const allHooks = full.hooks ?? [];
6031
+ if (!Array.isArray(allHooks)) throw new Error("hooks must be an array of [[hooks]] tables.");
6032
+ let previous = [];
6033
+ if (start >= 0)
6034
+ try {
6035
+ previous = parse2(text3.slice(start, stop + end.length), {
6036
+ integersAsBigInt: "asNeeded"
6037
+ }).hooks ?? [];
6038
+ } catch {
6039
+ previous = [];
6040
+ }
6041
+ const kept = allHooks.filter((h) => !previous.some((o) => isDeepStrictEqual(o, h)));
6042
+ const expected = { ...full, hooks: [...kept, ...blockHooks] };
6043
+ try {
6044
+ if (!isDeepStrictEqual(parse2(output, { integersAsBigInt: "asNeeded" }), expected))
6045
+ throw new Error();
6046
+ } catch {
6047
+ throw new Error(
6048
+ "Cannot safely merge the cmdr hooks into config.toml. Move conflicting [[hooks]] entries and retry."
6049
+ );
6050
+ }
6051
+ return output;
6052
+ }
5923
6053
  function mergeHooks(config, executable, host) {
5924
6054
  const container = config.hooks = object(config.hooks ?? {}, "hooks");
5925
6055
  const hooks = host === "zcode" ? container.events = object(container.events ?? {}, "hooks.events") : container;
@@ -6106,7 +6236,7 @@ function launcher(runtime, entry, state, agent, codexHome) {
6106
6236
  # Managed by cmdr setup (https://github.com/njugray/cmdr).
6107
6237
  export CMDR_HOME=${shellQuote(state)}
6108
6238
  ${agent ? `export CMDR_AGENT=${shellQuote(agent)}
6109
- ` : ""}${agent === "codex" || agent === "zcode" ? "export CMDR_TOOL_TIMEOUT_SEC=600\n" : ""}${codexHome ? `export CODEX_HOME=${shellQuote(codexHome)}
6239
+ ` : ""}${agent === "codex" || agent === "zcode" || agent === "kimi" ? "export CMDR_TOOL_TIMEOUT_SEC=600\n" : ""}${codexHome ? `export CODEX_HOME=${shellQuote(codexHome)}
6110
6240
  ` : ""}exec ${shellQuote(join6(runtime, "bin", entry))} "$@"
6111
6241
  `;
6112
6242
  }
@@ -6124,14 +6254,14 @@ async function runSetup(argv, sourceRoot) {
6124
6254
  });
6125
6255
  if (values.help) {
6126
6256
  console.log(
6127
- "cmdr setup --agent claude-code|codex|zcode [--config-dir PATH] [--dry-run] [--json]\nInstall or upgrade for the current user. CMDR_HOME selects runtime/state storage; --config-dir selects the host user profile."
6257
+ "cmdr setup --agent claude-code|codex|zcode|kimi-code [--config-dir PATH] [--dry-run] [--json]\nInstall or upgrade for the current user. CMDR_HOME selects runtime/state storage; --config-dir selects the host user profile."
6128
6258
  );
6129
6259
  return;
6130
6260
  }
6131
- const agent = values.agent === "claude-code" ? "claude" : values.agent;
6132
- if (positionals.length || !agent || !["claude", "codex", "zcode"].includes(agent))
6261
+ const agent = values.agent === "claude-code" ? "claude" : values.agent === "kimi-code" ? "kimi" : values.agent;
6262
+ if (positionals.length || !agent || !["claude", "codex", "zcode", "kimi"].includes(agent))
6133
6263
  throw new Error(
6134
- "Use cmdr setup --agent claude-code|codex|zcode. Other MCP hosts can use cmdr config --agent HOST."
6264
+ "Use cmdr setup --agent claude-code|codex|zcode|kimi-code. Other MCP hosts can use cmdr config --agent HOST."
6135
6265
  );
6136
6266
  const host = agent;
6137
6267
  const source = await inspectInstallation(sourceRoot);
@@ -6141,17 +6271,28 @@ async function runSetup(argv, sourceRoot) {
6141
6271
  const digest = createHash3("sha256").update(readFileSync4(join6(sourceRoot, "dist/integrity.json"))).digest("hex").slice(0, 16);
6142
6272
  const runtime = join6(state, "runtimes", `${source.version}-${digest}`);
6143
6273
  const bin = join6(state, "bin");
6144
- const hostRoot = values["config-dir"] ? resolve2(values["config-dir"]) : host === "claude" ? resolve2(process.env.CLAUDE_CONFIG_DIR || join6(homedir2(), ".claude")) : host === "codex" ? resolve2(process.env.CODEX_HOME || join6(homedir2(), ".codex")) : join6(homedir2(), ".zcode");
6274
+ const hostRoot = values["config-dir"] ? resolve2(values["config-dir"]) : host === "claude" ? resolve2(process.env.CLAUDE_CONFIG_DIR || join6(homedir2(), ".claude")) : host === "codex" ? resolve2(process.env.CODEX_HOME || join6(homedir2(), ".codex")) : host === "kimi" ? resolve2(process.env.KIMI_CODE_HOME || join6(homedir2(), ".kimi-code")) : join6(homedir2(), ".zcode");
6145
6275
  const profile = createHash3("sha256").update(hostRoot).digest("hex").slice(0, 12);
6146
6276
  const mcp = join6(bin, `cmdr-mcp-${host}-${profile}`), hook = join6(bin, `cmdr-hook-${host}-${profile}`);
6147
6277
  const changes = [];
6148
6278
  const warnings = [];
6279
+ if (host === "kimi" && existsSync2(join6(hostRoot, "plugins/managed/cmdr")))
6280
+ throw new Error(
6281
+ "The cmdr plugin is already enabled. Use its installation, or remove it before running standalone setup to avoid duplicate tools and hooks."
6282
+ );
6149
6283
  const add = (change) => {
6150
6284
  if (change) changes.push(change);
6151
6285
  };
6152
6286
  const writeConfig = (path, config2) => add(fileChange(path, JSON.stringify(config2, null, 2) + "\n"));
6153
6287
  let config;
6154
- if (host === "codex") {
6288
+ if (host === "kimi") {
6289
+ config = configPath(join6(hostRoot, "mcp.json"));
6290
+ const servers = jsonConfig(read(config), config);
6291
+ mergeJsonServer(servers, mcp, "kimi");
6292
+ writeConfig(config, servers);
6293
+ const hooksPath = configPath(join6(hostRoot, "config.toml"));
6294
+ add(fileChange(hooksPath, kimiHooksConfig(read(hooksPath), hook)));
6295
+ } else if (host === "codex") {
6155
6296
  config = configPath(join6(hostRoot, "config.toml"));
6156
6297
  const merged = codexConfig(read(config), mcp);
6157
6298
  add(fileChange(config, merged.text));
@@ -6201,6 +6342,8 @@ async function runSetup(argv, sourceRoot) {
6201
6342
  }
6202
6343
  const skill = join6(hostRoot, "skills/cmdr");
6203
6344
  add(skillChange(skill, join6(runtime, "skills/cmdr")));
6345
+ if (host === "kimi")
6346
+ add(skillChange(join6(hostRoot, "skills/cmdr-identity"), join6(runtime, "kimi-identity")));
6204
6347
  const result = {
6205
6348
  agent: values.agent,
6206
6349
  version: source.version,
@@ -6211,7 +6354,7 @@ async function runSetup(argv, sourceRoot) {
6211
6354
  dry_run: !!values["dry-run"],
6212
6355
  changed: changes.map((change) => change.path),
6213
6356
  warnings,
6214
- restart: "Start a new host session and review any hook trust prompts."
6357
+ restart: host === "kimi" ? "Restart the Kimi Code app: it loads config.toml hooks only at startup." : "Start a new host session and review any hook trust prompts."
6215
6358
  };
6216
6359
  const output = (extra) => {
6217
6360
  if (values.json) console.log(JSON.stringify({ ...result, ...extra }, null, 2));
@@ -6314,6 +6457,7 @@ if (process.argv[2] === "setup") {
6314
6457
  follow: { type: "boolean" },
6315
6458
  full: { type: "boolean" },
6316
6459
  json: { type: "boolean" },
6460
+ "no-open": { type: "boolean" },
6317
6461
  help: { type: "boolean" }
6318
6462
  }
6319
6463
  });
@@ -6399,14 +6543,52 @@ if (process.argv[2] === "setup") {
6399
6543
  } catch {
6400
6544
  checks.zcode_user_hooks = "No user hook config; installed plugin hooks follow plugin enablement.";
6401
6545
  }
6546
+ const kimiRoot = "/Applications/Kimi Code.app/Contents";
6547
+ if (existsSync3(join7(kimiRoot, "Info.plist"))) {
6548
+ try {
6549
+ checks.kimi = {
6550
+ app_version: execFileSync(
6551
+ "/usr/libexec/PlistBuddy",
6552
+ ["-c", "Print :CFBundleShortVersionString", join7(kimiRoot, "Info.plist")],
6553
+ { encoding: "utf8", timeout: 1e3 }
6554
+ ).trim(),
6555
+ config_dir: process.env.KIMI_CODE_HOME || join7(homedir3(), ".kimi-code")
6556
+ };
6557
+ } catch {
6558
+ checks.kimi = "Installed";
6559
+ }
6560
+ } else
6561
+ checks.kimi = "Desktop app not found in /Applications; generic MCP configuration is available.";
6562
+ try {
6563
+ const kconfig = readFileSync5(
6564
+ join7(process.env.KIMI_CODE_HOME || join7(homedir3(), ".kimi-code"), "config.toml"),
6565
+ "utf8"
6566
+ );
6567
+ checks.kimi_hooks = /cmdr-hook/.test(kconfig) ? "cmdr hooks present in config.toml" : "No cmdr hooks in config.toml; run cmdr setup --agent kimi-code.";
6568
+ } catch {
6569
+ checks.kimi_hooks = "Kimi Code configuration unavailable";
6570
+ }
6402
6571
  print(checks);
6403
6572
  }
6404
6573
  try {
6405
6574
  if (v.help)
6406
6575
  process.stdout.write(
6407
- "cmdr status | setup --agent claude-code|codex|zcode [--dry-run] [--json] | list [--all] [--squad ID] | tail [--follow] [--full] [--json] [--after EVENT_SEQ|now] [--actionable] [--format line|json] [--for SID] | standby start|status|stop|resume|watch --session SID [--adapter codex|claude|zcode|manual] [--transport auto|proxy|queue] [--once] | 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"
6576
+ "cmdr status | dashboard [--no-open] [--json] | setup --agent claude-code|codex|zcode|kimi-code [--dry-run] [--json] | list [--all] [--squad ID] | tail [--follow] [--full] [--json] [--after EVENT_SEQ|now] [--actionable] [--format line|json] [--for SID] | standby start|status|stop|resume|watch --session SID [--adapter codex|claude|zcode|kimi|manual] [--transport auto|proxy|queue] [--once] | 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"
6408
6577
  );
6409
- else if (cmd === "config") {
6578
+ else if (cmd === "dashboard") {
6579
+ const result = await call("admin.dashboard", {}, true);
6580
+ print(result);
6581
+ if (!v["no-open"]) {
6582
+ try {
6583
+ execFileSync(process.platform === "darwin" ? "open" : "xdg-open", [result.url], {
6584
+ stdio: "ignore",
6585
+ timeout: 5e3
6586
+ });
6587
+ } catch {
6588
+ console.error("Open the printed URL in a local browser (valid for 60 seconds).");
6589
+ }
6590
+ }
6591
+ } else if (cmd === "config") {
6410
6592
  const agent = v.agent || "generic";
6411
6593
  if (!/^[a-z][a-z0-9_-]{0,63}$/.test(agent))
6412
6594
  throw new Error("Invalid --agent: use lowercase letters, digits, underscores or hyphens.");
@@ -6416,9 +6598,10 @@ if (process.argv[2] === "setup") {
6416
6598
  env: {
6417
6599
  CMDR_AGENT: agent,
6418
6600
  ...v.session ? { CMDR_SESSION_ID: v.session } : {},
6419
- ...agent === "zcode" ? { CMDR_TOOL_TIMEOUT_SEC: "600" } : {}
6601
+ ...agent === "zcode" || agent === "kimi" ? { CMDR_TOOL_TIMEOUT_SEC: "600" } : {}
6420
6602
  },
6421
- ...agent === "zcode" ? { timeoutMs: 6e5 } : {}
6603
+ ...agent === "zcode" ? { timeoutMs: 6e5 } : {},
6604
+ ...agent === "kimi" ? { toolTimeoutMs: 6e5 } : {}
6422
6605
  };
6423
6606
  print(
6424
6607
  agent === "zcode" ? { mcp: { servers: { cmdr: server } } } : { mcpServers: { cmdr: server } }