seatmesh 1.2.4 → 1.3.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 (91) hide show
  1. package/bin/seatmesh +6 -1
  2. package/dist/commands/agent-forum.d.ts +6 -0
  3. package/dist/commands/agent-forum.d.ts.map +1 -0
  4. package/dist/commands/agent-forum.js +26 -0
  5. package/dist/commands/agent-forum.js.map +1 -0
  6. package/dist/commands/campaign-cli.d.ts +10 -0
  7. package/dist/commands/campaign-cli.d.ts.map +1 -0
  8. package/dist/commands/campaign-cli.js +136 -0
  9. package/dist/commands/campaign-cli.js.map +1 -0
  10. package/dist/commands/chat-cli.d.ts.map +1 -1
  11. package/dist/commands/chat-cli.js +55 -0
  12. package/dist/commands/chat-cli.js.map +1 -1
  13. package/dist/commands/cli-speed.test.d.ts +2 -0
  14. package/dist/commands/cli-speed.test.d.ts.map +1 -0
  15. package/dist/commands/cli-speed.test.js +39 -0
  16. package/dist/commands/cli-speed.test.js.map +1 -0
  17. package/dist/commands/cli-vocal.test.d.ts +2 -0
  18. package/dist/commands/cli-vocal.test.d.ts.map +1 -0
  19. package/dist/commands/cli-vocal.test.js +58 -0
  20. package/dist/commands/cli-vocal.test.js.map +1 -0
  21. package/dist/commands/completion.d.ts +3 -0
  22. package/dist/commands/completion.d.ts.map +1 -1
  23. package/dist/commands/completion.js +55 -14
  24. package/dist/commands/completion.js.map +1 -1
  25. package/dist/commands/completion.test.js +37 -2
  26. package/dist/commands/completion.test.js.map +1 -1
  27. package/dist/commands/contract-lock-cli.d.ts.map +1 -1
  28. package/dist/commands/contract-lock-cli.js +150 -29
  29. package/dist/commands/contract-lock-cli.js.map +1 -1
  30. package/dist/commands/help-text.d.ts.map +1 -1
  31. package/dist/commands/help-text.js +90 -26
  32. package/dist/commands/help-text.js.map +1 -1
  33. package/dist/commands/host-cli.d.ts +5 -0
  34. package/dist/commands/host-cli.d.ts.map +1 -0
  35. package/dist/commands/host-cli.js +120 -0
  36. package/dist/commands/host-cli.js.map +1 -0
  37. package/dist/commands/nav-cli.d.ts +8 -0
  38. package/dist/commands/nav-cli.d.ts.map +1 -0
  39. package/dist/commands/nav-cli.js +54 -0
  40. package/dist/commands/nav-cli.js.map +1 -0
  41. package/dist/commands/notify-cli.d.ts.map +1 -1
  42. package/dist/commands/notify-cli.js +38 -3
  43. package/dist/commands/notify-cli.js.map +1 -1
  44. package/dist/commands/room-cli.d.ts.map +1 -1
  45. package/dist/commands/room-cli.js +23 -6
  46. package/dist/commands/room-cli.js.map +1 -1
  47. package/dist/commands/schedule-cli.d.ts +13 -0
  48. package/dist/commands/schedule-cli.d.ts.map +1 -0
  49. package/dist/commands/schedule-cli.js +74 -0
  50. package/dist/commands/schedule-cli.js.map +1 -0
  51. package/dist/commands/schedule-cli.test.d.ts +2 -0
  52. package/dist/commands/schedule-cli.test.d.ts.map +1 -0
  53. package/dist/commands/schedule-cli.test.js +29 -0
  54. package/dist/commands/schedule-cli.test.js.map +1 -0
  55. package/dist/commands/todo-give.test.js +21 -5
  56. package/dist/commands/todo-give.test.js.map +1 -1
  57. package/dist/fast-spawn.d.ts +3 -0
  58. package/dist/fast-spawn.d.ts.map +1 -0
  59. package/dist/fast-spawn.js +107 -0
  60. package/dist/fast-spawn.js.map +1 -0
  61. package/dist/main.js +323 -44
  62. package/dist/main.js.map +1 -1
  63. package/dist/setup/init.d.ts +11 -0
  64. package/dist/setup/init.d.ts.map +1 -1
  65. package/dist/setup/init.js +38 -0
  66. package/dist/setup/init.js.map +1 -1
  67. package/dist/setup/init.test.js +3 -2
  68. package/dist/setup/init.test.js.map +1 -1
  69. package/dist/setup/install.js +2 -2
  70. package/dist/setup/install.js.map +1 -1
  71. package/package.json +8 -7
  72. package/profiles/minimal/mesh.config.yaml +1 -0
  73. package/templates/cursor/rules/seatmesh-agent.mdc +40 -0
  74. package/templates/docs/RELEASE.md +9 -3
  75. package/templates/docs/cli/README.md +110 -11
  76. package/templates/docs/cli/chat.md +4 -3
  77. package/templates/docs/cli/contract.md +15 -15
  78. package/templates/docs/cli/host.md +12 -0
  79. package/templates/docs/cli/human.md +15 -7
  80. package/templates/docs/cli/kind.md +4 -2
  81. package/templates/docs/cli/nav.md +12 -0
  82. package/templates/docs/cli/{oc-proxy.md → opencode-cpe.md} +10 -10
  83. package/templates/docs/cli/rebuild.md +3 -0
  84. package/templates/docs/cli/reload.md +8 -5
  85. package/templates/docs/cli/room.md +5 -7
  86. package/templates/docs/cli/schedule.md +13 -0
  87. package/templates/docs/cli/secretary.md +1 -1
  88. package/templates/docs/cli/spawn.md +13 -0
  89. package/templates/docs/cli/switch.md +4 -4
  90. package/templates/init/AGENTS.md +10 -10
  91. package/templates/init/mesh.config.yaml +11 -4
package/dist/main.js CHANGED
@@ -1,23 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawnSync } from "node:child_process";
3
3
  import { findDotSmConfig } from "@seat-mesh/core";
4
- import { loadProfile, profilePaths, loadRoleIndex, renderRoleIndex, validateRoleIndex, runStackPassthrough, roleAllows, isCoordKind, isManagerKind, managerColumnIds, addBaseColumn, removeBaseColumn, listBaseColumns, chatRoomConfigForLoaded, clearAckEndedSync, resolveAckRedirectDefaults, } from "@seat-mesh/core";
4
+ import { loadProfile, profilePaths, loadRoleIndex, renderRoleIndex, validateRoleIndex, runStackPassthrough, roleAllows, isCoordKind, isManagerKind, managerColumnIds, addBaseColumn, removeBaseColumn, listBaseColumns, chatRoomConfigForLoaded, clearAckEndedSync, resolveAckRedirectDefaults, expandTargetSpec, targetRangeOptsFromProfile, } from "@seat-mesh/core";
5
5
  import { snapshotConnectivity, formatStatus } from "@seat-mesh/connectivity";
6
- import { createRegistryForProfile } from "@seat-mesh/providers";
7
- import { printWhoami, whoamiJson, whoamiJsonWithValidate, validateWhoamiRoleIndex, printAgentCard, printAgentContext, printAgentContextAllRoles, runWhoami, roleKindFromWhoami, requireCoordRole, requireInboxLifecycleRole, assertAgentDispatch, printAgentUnauthorized, stripAgentFromArgv, AGENT_META, capturePaneSnapshot, listSessionPanes, sessionAttach, sessionUp, sessionDown, sessionStatus, sessionSync, tmuxHasSession, relayoutMeshSession, realignAllLayouts, reloadMesh, ensureMeshInbox, startMeshInbox, stopMeshInbox, restartMeshInbox, meshInboxEverConfigured, meshInboxPort, printMeshInboxStatus, waitForMeshInbox, printInboxMeta, tailInboxLog, listInboxInstances, parseInboxStatusFlags, inboxStatusArgv, listInbox, resolveInbox, secretaryLaunch, secretaryDispatch, secretaryCollect, secretaryMeshWatch, secretarySupervise, secretaryStatus, printMiniList, miniSpawn, miniPrompt, miniDone, miniSpawnAll, runMeshSmoke, printSmokeResults, launchSession, printLaunchResults, defaultHarnessTypeForSeat, enqueuePrompt, injectPromptDirect, printPeerVerify, runPeerVerify, runRemind, printRemindResults, runNight, runContinue, printContinueResults, runSlotAdvice, verifyMeshSession, printVerify, labelMeshSession, liveMeshSession, ensureBaseLayout, realignBaseLayout, applyMeshSessionBorders, runFlush, printFlushResults, runSwitch, runPaneResume, runSeatSwap, runSet, runTag, setPaneTitle, setPaneStatus, printSeatContexts, runAgentHub, runPeek, runPaneKind, runPaneMeta, runPpa, runToSlot, runToMini, runPeer, parseRemotePeerTarget, runRemotePeer, applyMeshState, saveMeshSession, saveMeshSessionDetailed, assertSaveAllowed, formatSaveSummary, submitPaneOp, clearPaneOpsQueue, printPaneOpsList, printRelayoutPlan, assertRelayoutSafe, buildFullColdStartBrief, enqueueColdStart, runSeatInit, applyAgentContractBundle, balanceLeadCommand, } from "@seat-mesh/tmux";
8
- import { parseAgentApplyArgs } from "@seat-mesh/core";
9
- import { buildChatCommands } from "./commands/chat-cli.js";
6
+ import { createRegistryForProfile, loadDropInProviders } from "@seat-mesh/providers";
7
+ import { printWhoami, whoamiJson, whoamiJsonWithValidate, validateWhoamiRoleIndex, printAgentCard, printAgentContext, printAgentContextAllRoles, runWhoami, roleKindFromWhoami, requireCoordRole, requireInboxLifecycleRole, assertAgentDispatch, printAgentUnauthorized, stripAgentFromArgv, AGENT_META, capturePaneSnapshot, listSessionPanes, sessionAttach, sessionUp, sessionDown, sessionStatus, sessionSync, tmuxHasSession, relayoutMeshSession, realignAllLayouts, scaleLayout, layoutReload, reloadMesh, ensureMeshInbox, startMeshInbox, stopMeshInbox, restartMeshInbox, meshInboxEverConfigured, meshInboxPort, printMeshInboxStatus, waitForMeshInbox, printInboxMeta, tailInboxLog, listInboxInstances, parseInboxStatusFlags, inboxStatusArgv, listInbox, resolveInbox, secretaryLaunch, secretaryDispatch, secretaryCollect, secretaryMeshWatch, secretarySupervise, secretaryStatus, printMiniList, miniSpawn, miniPrompt, miniDone, miniSpawnAll, runMeshSmoke, printSmokeResults, launchSession, printLaunchResults, defaultHarnessTypeForSeat, enqueuePrompt, injectPromptDirect, printPeerVerify, runPeerVerify, runRemind, printRemindResults, runNight, runContinue, printContinueResults, runSlotAdvice, verifyMeshSession, printVerify, labelMeshSession, liveMeshSession, ensureBaseLayout, realignBaseLayout, applyMeshSessionBorders, runFlush, printFlushResults, runSwitch, runSwitchFast, runPaneResume, runSeatSwap, runSet, runTag, setPaneTitle, setPaneStatus, printSeatContexts, runAgentHub, runPeek, runPaneKind, runPaneMeta, runPaneCapture, runPpa, runToSlot, runToMini, runPeer, parseRemotePeerTarget, runRemotePeer, applyMeshState, saveMeshSession, saveMeshSessionDetailed, assertSaveAllowed, formatSaveSummary, submitPaneOp, clearPaneOpsQueue, printPaneOpsList, printRelayoutPlan, assertRelayoutSafe, buildFullColdStartBrief, enqueueColdStart, runSeatInit, applyAgentContractBundle, balanceLeadCommand, kindsForLoaded, } from "@seat-mesh/tmux";
8
+ import { parseAgentApplyArgs, lookupResolvedKind } from "@seat-mesh/core";
10
9
  import { buildCheckbackCommands } from "./commands/checkback-cli.js";
11
10
  import { buildTargetCommands } from "./commands/target-cli.js";
12
11
  import { buildAckCommands } from "./commands/ack-cli.js";
13
12
  import { resolvePeerEndedAckId, shouldAutoAckReply, } from "./commands/ack-reply.js";
14
13
  import { buildLimitCommands } from "./commands/limit-cli.js";
15
14
  import { buildRolesCommands } from "./commands/roles-cli.js";
16
- import { buildNotifyCommand } from "./commands/notify-cli.js";
17
- import { buildPreviewCommand } from "./commands/preview-cli.js";
18
- import { runMdsCommand } from "./commands/mds-cli.js";
19
- import { buildContractLockCommands } from "./commands/contract-lock-cli.js";
20
- import { buildRoomCommands } from "./commands/room-cli.js";
21
15
  import { runInit } from "./setup/init.js";
22
16
  import { runAgentContextInit } from "./setup/agent-context-init.js";
23
17
  import { printSessionCheck, printSessionRepair, runSessionCheck, runSessionRepair, } from "./setup/session-health.js";
@@ -62,8 +56,10 @@ Setup
62
56
 
63
57
  Put an agent on a pane
64
58
  help human ← cheat sheet (aliases: put-agent | panes)
65
- switch <target> <opencode|oc-proxy|claude|agent|kiro|empty>
66
- launch <target|all>
59
+ spawn <target> <opencode|opencode-cpe|claude|agent|kiro> empty → CLI
60
+ switch <target> <…|empty> replace live agent (or → shell)
61
+ kill|empty <target> pane → plain terminal (+ save empty)
62
+ launch <target|all> resume configured CLI
67
63
  kind <target>
68
64
 
69
65
  Work / status
@@ -73,7 +69,9 @@ Work / status
73
69
  report | verify | test | save|auto | inbox […] | labels
74
70
 
75
71
  Session shape
76
- reload [--layout] | layout […] | realign | ops list|clear
72
+ rebuild|reload [--layout] npm build + labels (not config→pane)
73
+ layout reload re-grid + resume from mesh-agents.json
74
+ layout […] | realign | ops list|clear
77
75
 
78
76
  Agent panes use the gateway (not listed here by default):
79
77
  seatmesh agent … · seatmesh agent help
@@ -104,16 +102,17 @@ Setup (run once per project, by a human)
104
102
  config check | config upgrade validate yaml · how to bump CLI (npm i -g)
105
103
  web up|down|restart|status|open|url operator hub :3190 (npx seatmesh web up)
106
104
  roles status|migrate [--to VER]|steps locked role-pack up/down (1.1.x)
105
+ host up|down|status opt-in single host-supervisor for all registered meshes (Phase 1)
107
106
  version [--json] [--check-registry] CLI vs npm latest vs profile .seatmesh-version
108
107
 
109
108
  Put an agent on a pane (human — most common)
110
109
  help human ← full cheat sheet (aliases: put-agent | panes)
111
- switch <target> <opencode|oc-proxy|claude|agent|kiro> empty shell → agent CLI
110
+ switch <target> <opencode|opencode-cpe|claude|agent|kiro> empty shell → agent CLI
112
111
  switch <target> empty agent → plain shell
113
112
  launch <target|all> resume configured CLI (no type pick)
114
113
  pane resume [target] autodetect session id → resume/relaunch
115
114
  kind <target> agent vs terminal?
116
- Examples: switch slot-1 opencode · switch secretary oc-proxy · pane resume here
115
+ Examples: switch slot-1 opencode · switch secretary opencode-cpe · pane resume here
117
116
 
118
117
  Give a seat a todo (human or base agent — contracts optional)
119
118
  todo give <target> "…" ← FOCUS+TASK+inject+CB≥20m (preferred)
@@ -284,6 +283,13 @@ async function main() {
284
283
  console.log(` bin dir: ${r.binDir} (ensure it is on PATH)`);
285
284
  return;
286
285
  }
286
+ if (cmd === "host") {
287
+ const { runHostCommand } = await import("./commands/host-cli.js");
288
+ const code = runHostCommand([sub, ...tail].filter((a) => a != null));
289
+ if (code !== 0)
290
+ process.exit(code);
291
+ return;
292
+ }
287
293
  if (cmd === "update") {
288
294
  const { runUpdate } = await import("./setup/update.js");
289
295
  const { formatInstallStatusLine, isNpxEphemeralInstall, readInstalledCliVersion, resolveLatestRegistryVersion, semverLess, } = await import("./ui/version-nudge.js");
@@ -645,13 +651,13 @@ async function main() {
645
651
  const inboxOk = printMeshInboxStatus(loaded);
646
652
  process.exit(layoutOk && inboxOk ? 0 : 1);
647
653
  }
648
- if (cmd === "reload") {
654
+ if (cmd === "reload" || cmd === "rebuild") {
649
655
  const loaded = meshLoaded(profileArg);
650
656
  const layout = rest.includes("--layout");
651
657
  reloadMesh(loaded, { layout });
652
658
  console.log(layout
653
- ? `OK: reload + relayout session ${loaded.sessionName}`
654
- : `OK: reload (build + labels) session ${loaded.sessionName}`);
659
+ ? `OK: rebuild + relayout session ${loaded.sessionName}`
660
+ : `OK: rebuild (npm build + labels) session ${loaded.sessionName} — not config→pane`);
655
661
  printMeshInboxStatus(loaded);
656
662
  return;
657
663
  }
@@ -668,6 +674,87 @@ async function main() {
668
674
  }
669
675
  return;
670
676
  }
677
+ if (cmd === "layout" && sub === "scale") {
678
+ const loaded = meshLoaded(profileArg);
679
+ const kindRaw = (tail[0] ?? "").toLowerCase();
680
+ const kind = kindRaw === "workers" || kindRaw === "worker" || kindRaw === "w"
681
+ ? "workers"
682
+ : kindRaw === "minis" || kindRaw === "mini" || kindRaw === "m"
683
+ ? "minis"
684
+ : null;
685
+ const action = (tail[1] ?? "").toLowerCase();
686
+ const force = rest.includes("--yes");
687
+ const dryRun = rest.includes("--dry-run");
688
+ if (!kind || !action) {
689
+ console.error("usage: layout scale workers|minis up|down|<N> [--yes] [--dry-run]");
690
+ process.exit(2);
691
+ }
692
+ requireCoordRole(loaded, "layout scale");
693
+ if (action !== "up" && action !== "down") {
694
+ const n = Number(action);
695
+ if (!Number.isInteger(n) || n < 1) {
696
+ console.error("usage: layout scale workers|minis up|down|<N> [--yes] [--dry-run]");
697
+ process.exit(2);
698
+ }
699
+ }
700
+ try {
701
+ const r = action === "up" || action === "down"
702
+ ? scaleLayout(loaded, {
703
+ kind,
704
+ dir: action,
705
+ force,
706
+ dryRun,
707
+ })
708
+ : scaleLayout(loaded, {
709
+ kind,
710
+ to: Number(action),
711
+ force,
712
+ dryRun,
713
+ });
714
+ if (r.dryRun) {
715
+ console.log(`dry-run: scale ${r.kind} ${r.from} → ${r.to} (${r.grid})`);
716
+ return;
717
+ }
718
+ if (r.from === r.to) {
719
+ console.log(`OK: scale ${r.kind} already ${r.to} (${r.grid})`);
720
+ return;
721
+ }
722
+ console.log(`OK: scale ${r.kind} ${r.from} → ${r.to} (${r.grid})${r.saved ? ` saved ${r.saved}` : ""}`);
723
+ }
724
+ catch (e) {
725
+ console.error(e.message);
726
+ process.exit(1);
727
+ }
728
+ return;
729
+ }
730
+ if (cmd === "layout" && sub === "reload") {
731
+ const loaded = meshLoaded(profileArg);
732
+ const force = rest.includes("--yes");
733
+ const skipLeads = rest.includes("--no-leads");
734
+ const noResume = rest.includes("--no-resume");
735
+ requireCoordRole(loaded, "layout reload");
736
+ try {
737
+ const r = layoutReload(loaded, {
738
+ force,
739
+ skipMinisLeads: skipLeads,
740
+ noResume,
741
+ });
742
+ const launched = r.repaired.filter((x) => x.status === "launched");
743
+ const failed = r.repaired.filter((x) => x.status === "failed");
744
+ console.log(`OK: layout reload ${loaded.sessionName} repaired=${launched.length}${r.saved ? ` saved ${r.saved}` : ""}`);
745
+ for (const x of launched) {
746
+ console.log(` resume ${x.label} ${x.paneId}`);
747
+ }
748
+ for (const x of failed) {
749
+ console.warn(` fail ${x.label}: ${x.reason ?? x.status}`);
750
+ }
751
+ }
752
+ catch (e) {
753
+ console.error(e.message);
754
+ process.exit(1);
755
+ }
756
+ return;
757
+ }
671
758
  if (cmd === "layout" && sub === "column") {
672
759
  const loaded = meshLoaded(profileArg);
673
760
  const action = tail[0];
@@ -739,7 +826,7 @@ async function main() {
739
826
  const leadNote = skipLeads || !m
740
827
  ? ""
741
828
  : ` leads=[${Array.isArray(m.leads) ? m.leads.join(",") : "1,2"}]`;
742
- console.log(`OK: relayout ${loaded.sessionName} (workers 3x2, minis ${grid}${leadNote})`);
829
+ console.log(`OK: relayout ${loaded.sessionName} (workers ${loaded.profile.layout?.workers.grid ?? "?"}, minis ${grid}${leadNote})`);
743
830
  console.log(`OK: layout saved ${saved}`);
744
831
  });
745
832
  return;
@@ -962,15 +1049,52 @@ async function main() {
962
1049
  printPeerVerify(r);
963
1050
  process.exit(r.pass ? 0 : 1);
964
1051
  }
965
- const [target, ...textParts] = args;
1052
+ const [targetRaw, ...textParts] = args;
966
1053
  const rawMsg = textParts.join(" ").trim();
967
- if (!target || !rawMsg) {
1054
+ if (!targetRaw || !rawMsg) {
968
1055
  console.error("usage: peer verify [target] | peer [--direct] [--ended|--ack [ack-id]] [--redirect-from <seat>] <target> <msg...>\n" +
1056
+ " target: seat | 1..4 | slot-1..3 | mini-1..2 | 1,3,5 (Ruby-style fanout)\n" +
969
1057
  " --ack / --ended [id] close open ask (id optional = auto-match from target)\n" +
970
1058
  " ack-class msg (ACK/FYI/PASS) auto-closes matching open ask\n" +
971
1059
  " or: agent ack reply <id> [msg] (peer back + close in one shot)");
972
1060
  process.exit(2);
973
1061
  }
1062
+ const peerTargets = expandTargetSpec(targetRaw, targetRangeOptsFromProfile(loaded.profile));
1063
+ if (peerTargets.length > 1 && endedId) {
1064
+ console.error("WARN: --ack/--ended ignored on range fanout (send-only)");
1065
+ endedId = undefined;
1066
+ }
1067
+ const target = peerTargets[0];
1068
+ if (peerTargets.length > 1) {
1069
+ for (const t of peerTargets) {
1070
+ try {
1071
+ runPeer(loaded, t, rawMsg);
1072
+ }
1073
+ catch (e) {
1074
+ if (e.message !== "__peer_coord__") {
1075
+ console.error(`FAIL peer ${t}: ${e.message}`);
1076
+ continue;
1077
+ }
1078
+ requireCoordRole(loaded, "peer");
1079
+ if (direct) {
1080
+ const reg = createRegistryForProfile(loaded.profile);
1081
+ injectPromptDirect(loaded, reg, t, rawMsg, { manager: true, force: true });
1082
+ console.log(`SENT: peer --direct -> ${t}`);
1083
+ }
1084
+ else {
1085
+ const { paneId, targetLabel, token, via } = enqueuePrompt(loaded, t, rawMsg, {
1086
+ manager: true,
1087
+ armCheckback: false,
1088
+ });
1089
+ console.log(via === "queued"
1090
+ ? `QUEUED: peer -> ${targetLabel} pane=${paneId} token=${token ?? "-"}`
1091
+ : `SENT: peer -> ${targetLabel} pane=${paneId}`);
1092
+ }
1093
+ }
1094
+ }
1095
+ console.log(`ok fanout peer → ${peerTargets.join(",")} (${peerTargets.length})`);
1096
+ return;
1097
+ }
974
1098
  if (shouldAutoAckReply(rawMsg, endedId)) {
975
1099
  endedId = "__auto__";
976
1100
  }
@@ -1180,6 +1304,13 @@ async function main() {
1180
1304
  secretaryLaunch(loaded);
1181
1305
  return;
1182
1306
  }
1307
+ if (sub === "stop") {
1308
+ const reg = createRegistryForProfile(loaded.profile);
1309
+ runSwitch(loaded, reg, "secretary", "empty", { reason: "stop" });
1310
+ saveMeshSession(loaded, reg);
1311
+ console.log("OK: secretary stopped (pane -> empty shell)");
1312
+ return;
1313
+ }
1183
1314
  if (sub === "restart" || sub === "switch") {
1184
1315
  const reg = createRegistryForProfile(loaded.profile);
1185
1316
  const keepResume = rest.includes("--keep-resume") ||
@@ -1193,7 +1324,8 @@ async function main() {
1193
1324
  "claude",
1194
1325
  "kiro",
1195
1326
  "opencode",
1196
- "oc-proxy",
1327
+ "opencode-cpe",
1328
+ "oc-proxy", // legacy alias → normalizeAgentKind → opencode-cpe
1197
1329
  "cursor-agent",
1198
1330
  "oc",
1199
1331
  "cursor",
@@ -1301,6 +1433,37 @@ async function main() {
1301
1433
  }
1302
1434
  return;
1303
1435
  }
1436
+ if (cmd === "kind" && (sub === "list" || sub === "show")) {
1437
+ const loaded = meshLoaded(profileArg);
1438
+ const kinds = kindsForLoaded(loaded);
1439
+ if (sub === "list") {
1440
+ for (const id of Object.keys(kinds).sort()) {
1441
+ const k = kinds[id];
1442
+ const launch = k.launch === undefined
1443
+ ? "default"
1444
+ : k.launch === null
1445
+ ? "empty"
1446
+ : "builtin" in k.launch
1447
+ ? `builtin:${k.launch.builtin}`
1448
+ : `script:${k.launch.command}`;
1449
+ const aliases = k.aliases?.length ? ` aliases=${k.aliases.join(",")}` : "";
1450
+ console.log(`${id}\tprovider=${k.provider}\tlaunch=${launch}${aliases}`);
1451
+ }
1452
+ return;
1453
+ }
1454
+ const id = tail[0];
1455
+ if (!id) {
1456
+ console.error("usage: kind show <id>");
1457
+ process.exit(2);
1458
+ }
1459
+ const found = lookupResolvedKind(kinds, id);
1460
+ if (!found) {
1461
+ console.error(`kind show: unknown kind "${id}" (see: seatmesh kind list)`);
1462
+ process.exit(1);
1463
+ }
1464
+ console.log(JSON.stringify(found, null, 2));
1465
+ return;
1466
+ }
1304
1467
  if (cmd === "kind" || cmd === "what" || cmd === "typeof") {
1305
1468
  const loaded = meshLoaded(profileArg);
1306
1469
  const reg = createRegistryForProfile(loaded.profile);
@@ -1327,6 +1490,17 @@ async function main() {
1327
1490
  }
1328
1491
  return;
1329
1492
  }
1493
+ if (cmd === "capture") {
1494
+ const loaded = meshLoaded(profileArg);
1495
+ try {
1496
+ runPaneCapture(loaded, [sub, ...tail].filter((a) => Boolean(a)));
1497
+ }
1498
+ catch (e) {
1499
+ console.error(e.message);
1500
+ process.exit(1);
1501
+ }
1502
+ return;
1503
+ }
1330
1504
  if (cmd === "pane") {
1331
1505
  const loaded = meshLoaded(profileArg);
1332
1506
  const reg = createRegistryForProfile(loaded.profile);
@@ -1338,7 +1512,7 @@ async function main() {
1338
1512
  Targets: here (default) | secretary | manager | slot-N | mini-N
1339
1513
 
1340
1514
  Live OpenCode: pastes resume [ses_…]
1341
- Shell / dead OC: relaunches oc-proxy|opencode|claude with that session`);
1515
+ Shell / dead OC: relaunches opencode-cpe|opencode|claude with that session`);
1342
1516
  return;
1343
1517
  }
1344
1518
  if (action === "resume") {
@@ -1475,12 +1649,16 @@ async function main() {
1475
1649
  const loaded = meshLoaded(profileArg);
1476
1650
  requireCoordRole(loaded, "launch");
1477
1651
  const rawArgs = [sub, ...tail].filter((a) => Boolean(a) && a !== "--");
1478
- const launchNow = rawArgs.includes("--now");
1479
- const targets = rawArgs.filter((a) => a !== "--now");
1480
- const label = targets.length ? targets.join(",") : "all";
1652
+ const launchNow = rawArgs.includes("--now") || rawArgs.includes("--fast");
1653
+ const targetsRaw = rawArgs.filter((a) => a !== "--now" && a !== "--fast");
1654
+ const rangeOpts = targetRangeOptsFromProfile(loaded.profile);
1655
+ const targets = targetsRaw.length
1656
+ ? targetsRaw.flatMap((t) => expandTargetSpec(t, rangeOpts))
1657
+ : undefined;
1658
+ const label = targets?.length ? targets.join(",") : "all";
1481
1659
  const runLaunch = () => {
1482
1660
  const results = launchSession(loaded, {
1483
- targets: targets.length ? targets : undefined,
1661
+ targets,
1484
1662
  });
1485
1663
  printLaunchResults(results);
1486
1664
  if (results.some((r) => r.status === "failed"))
@@ -1490,7 +1668,7 @@ async function main() {
1490
1668
  runLaunch();
1491
1669
  return;
1492
1670
  }
1493
- submitPaneOp(loaded, "launch", { targets: targets.length ? targets : undefined }, `launch ${label}`, runLaunch);
1671
+ submitPaneOp(loaded, "launch", { targets }, `launch ${label}`, runLaunch);
1494
1672
  return;
1495
1673
  }
1496
1674
  if (cmd === "prompt") {
@@ -1614,17 +1792,43 @@ async function main() {
1614
1792
  printFlushResults(results);
1615
1793
  return;
1616
1794
  }
1617
- if (cmd === "switch" || cmd === "handoff") {
1795
+ if (cmd === "kill" || cmd === "empty") {
1618
1796
  const loaded = meshLoaded(profileArg);
1619
- requireCoordRole(loaded, cmd);
1620
- const reg = createRegistryForProfile(loaded.profile);
1797
+ requireCoordRole(loaded, "switch");
1798
+ const targetRaw = sub;
1799
+ if (!targetRaw) {
1800
+ console.error(`usage: ${cmd} <target|1..4|slot-N|mini-N|here>`);
1801
+ console.error(" → plain terminal (respawn-pane -k) + save empty in mesh-agents.json");
1802
+ process.exit(2);
1803
+ }
1804
+ const targets = expandTargetSpec(targetRaw, targetRangeOptsFromProfile(loaded.profile));
1805
+ for (const t of targets) {
1806
+ runSwitchFast(loaded, t, "empty", {
1807
+ fresh: true,
1808
+ persistEmpty: true,
1809
+ reason: cmd === "kill" ? "kill" : "empty",
1810
+ });
1811
+ }
1812
+ if (targets.length > 1) {
1813
+ console.log(`ok fanout ${cmd} → ${targets.join(",")} (${targets.length})`);
1814
+ }
1815
+ return;
1816
+ }
1817
+ if (cmd === "switch" || cmd === "handoff" || cmd === "spawn") {
1818
+ const loaded = meshLoaded(profileArg);
1819
+ requireCoordRole(loaded, cmd === "spawn" ? "switch" : cmd);
1621
1820
  const args = [sub, ...tail].filter(Boolean);
1821
+ const vocal = cmd === "spawn" ? "spawn" : "switch";
1822
+ // spawn defaults fast (paste like typing opencode); switch defaults thorough
1823
+ let fast = cmd === "spawn";
1622
1824
  if (args.length < 2) {
1623
- console.error("usage: switch <target> <agent|cursor|claude|cc|kiro|oc|opencode|empty> [--keep-resume] [--resume ID] [--queue] [reason...]");
1624
- console.error(" targets: secretary|sec, manager|mgr, slot-N, mini-N, here|self (this pane's role)");
1825
+ console.error(cmd === "spawn"
1826
+ ? "usage: spawn <target|1..4> <agent|cursor|claude|oc|opencode> [--fast|--slow] [--keep-resume] [--resume ID]"
1827
+ : "usage: switch <target|1..4> <agent|…|empty> [--fast|--slow] [--keep-resume] [--resume ID] [reason...]");
1828
+ console.error(" empty→CLI: spawn (fast by default) · replace live: switch · --fast = skip verify waits");
1625
1829
  process.exit(2);
1626
1830
  }
1627
- const target = args[0];
1831
+ const targetRaw = args[0];
1628
1832
  const newType = args[1];
1629
1833
  let fresh;
1630
1834
  let queue = false;
@@ -1636,6 +1840,10 @@ async function main() {
1636
1840
  fresh = true;
1637
1841
  else if (a === "--keep-resume" || a === "--no-fresh")
1638
1842
  fresh = false;
1843
+ else if (a === "--fast")
1844
+ fast = true;
1845
+ else if (a === "--slow" || a === "--verify")
1846
+ fast = false;
1639
1847
  else if (a === "--queue")
1640
1848
  queue = true;
1641
1849
  else if (a === "--resume" && args[i + 1])
@@ -1646,17 +1854,29 @@ async function main() {
1646
1854
  reasonParts.push(a);
1647
1855
  }
1648
1856
  const reason = reasonParts.join(" ") || undefined;
1649
- const run = () => runSwitch(loaded, reg, target, newType, {
1857
+ const targets = expandTargetSpec(targetRaw, targetRangeOptsFromProfile(loaded.profile));
1858
+ // Fast path skips provider registry build (expensive).
1859
+ const reg = fast ? null : createRegistryForProfile(loaded.profile);
1860
+ const runOne = (target) => runSwitch(loaded, reg, target, newType, {
1650
1861
  fresh,
1651
1862
  resumeId,
1652
1863
  reason,
1864
+ fast,
1653
1865
  });
1866
+ const run = () => {
1867
+ for (const t of targets)
1868
+ runOne(t);
1869
+ if (targets.length > 1) {
1870
+ console.log(`ok fanout ${vocal} → ${targets.join(",")} (${targets.length})`);
1871
+ }
1872
+ };
1654
1873
  if (queue) {
1655
- submitPaneOp(loaded, "switch", { target, newType, fresh, resumeId, reason }, `switch ${target} -> ${newType}`, run);
1874
+ submitPaneOp(loaded, "switch", { target: targetRaw, newType, fresh, resumeId, reason, fast }, `${vocal} ${targetRaw} -> ${newType}${fast ? " [fast]" : ""}`, run);
1656
1875
  }
1657
1876
  else {
1658
1877
  run();
1659
- saveMeshSession(loaded, reg);
1878
+ if (!fast && reg)
1879
+ saveMeshSession(loaded, reg);
1660
1880
  }
1661
1881
  return;
1662
1882
  }
@@ -1776,7 +1996,8 @@ async function main() {
1776
1996
  }
1777
1997
  if (sub && AGENT_META.has(sub)) {
1778
1998
  if (sub === "forum" || sub === "golf") {
1779
- console.log("agent forum/golf — see docs/cli/forum.md (not bundled in this build)");
1999
+ const { printAgentForum } = await import("./commands/agent-forum.js");
2000
+ printAgentForum();
1780
2001
  return;
1781
2002
  }
1782
2003
  if (sub === "apply") {
@@ -1968,7 +2189,7 @@ async function main() {
1968
2189
  if (sub === "reset") {
1969
2190
  const { spawnSync } = await import("node:child_process");
1970
2191
  const path = await import("node:path");
1971
- const script = path.join(loaded.workspace, "scripts/oc-proxy-reset.sh");
2192
+ const script = path.join(loaded.workspace, "scripts/opencode-cpe-reset.sh");
1972
2193
  const r = spawnSync("bash", [script], {
1973
2194
  cwd: loaded.workspace,
1974
2195
  encoding: "utf8",
@@ -1982,6 +2203,10 @@ async function main() {
1982
2203
  if (cmd === "providers") {
1983
2204
  const loaded = meshLoaded(profileArg);
1984
2205
  const reg = createRegistryForProfile(loaded.profile);
2206
+ const path = await import("node:path");
2207
+ const dropIn = await loadDropInProviders(path.join(loaded.profileDir, "providers"), (line) => console.error(line));
2208
+ for (const p of dropIn.providers)
2209
+ reg.register(p);
1985
2210
  if (sub === "list") {
1986
2211
  for (const p of reg.all())
1987
2212
  console.log(p.id);
@@ -2046,10 +2271,10 @@ async function main() {
2046
2271
  const loaded = meshLoaded(profileArg);
2047
2272
  const getLoaded = () => loaded;
2048
2273
  const branch = cmd === "room"
2049
- ? buildRoomCommands(getLoaded)
2274
+ ? (await import("./commands/room-cli.js")).buildRoomCommands(getLoaded)
2050
2275
  : cmd === "contract"
2051
- ? buildContractLockCommands(getLoaded)
2052
- : buildChatCommands(getLoaded);
2276
+ ? (await import("./commands/contract-lock-cli.js")).buildContractLockCommands(getLoaded)
2277
+ : (await import("./commands/chat-cli.js")).buildChatCommands(getLoaded);
2053
2278
  // contract with no sub → status (simple agent path)
2054
2279
  if (!sub && cmd === "contract") {
2055
2280
  try {
@@ -2173,7 +2398,7 @@ async function main() {
2173
2398
  if (cmd === "notify") {
2174
2399
  const loaded = meshLoaded(profileArg);
2175
2400
  const getLoaded = () => loaded;
2176
- const branch = buildNotifyCommand(getLoaded);
2401
+ const branch = (await import("./commands/notify-cli.js")).buildNotifyCommand(getLoaded);
2177
2402
  try {
2178
2403
  await branch.parseAsync(rest.slice(1), { from: "user" });
2179
2404
  }
@@ -2187,12 +2412,66 @@ async function main() {
2187
2412
  }
2188
2413
  if (cmd === "mds") {
2189
2414
  const loaded = meshLoaded(profileArg);
2415
+ const { runMdsCommand } = await import("./commands/mds-cli.js");
2190
2416
  process.exit(await runMdsCommand(loaded, rest.slice(1)));
2191
2417
  }
2192
2418
  if (cmd === "preview") {
2193
2419
  const loaded = meshLoaded(profileArg);
2194
2420
  const getLoaded = () => loaded;
2195
- const branch = buildPreviewCommand(getLoaded);
2421
+ const branch = (await import("./commands/preview-cli.js")).buildPreviewCommand(getLoaded);
2422
+ try {
2423
+ await branch.parseAsync(rest.slice(1), { from: "user" });
2424
+ }
2425
+ catch (e) {
2426
+ const err = e;
2427
+ if (err.code === "commander.helpDisplayed" || err.code === "commander.version")
2428
+ return;
2429
+ throw e;
2430
+ }
2431
+ return;
2432
+ }
2433
+ if (cmd === "nav") {
2434
+ const loaded = meshLoaded(profileArg);
2435
+ const getLoaded = () => loaded;
2436
+ const branch = (await import("./commands/nav-cli.js")).buildNavCommands(getLoaded);
2437
+ if (!sub) {
2438
+ branch.outputHelp();
2439
+ return;
2440
+ }
2441
+ try {
2442
+ await branch.parseAsync([sub, ...tail], { from: "user" });
2443
+ }
2444
+ catch (e) {
2445
+ const err = e;
2446
+ if (err.code === "commander.helpDisplayed" || err.code === "commander.version")
2447
+ return;
2448
+ throw e;
2449
+ }
2450
+ return;
2451
+ }
2452
+ if (cmd === "campaign") {
2453
+ const loaded = meshLoaded(profileArg);
2454
+ const getLoaded = () => loaded;
2455
+ const branch = (await import("./commands/campaign-cli.js")).buildCampaignCommands(getLoaded);
2456
+ if (!sub) {
2457
+ branch.outputHelp();
2458
+ return;
2459
+ }
2460
+ try {
2461
+ await branch.parseAsync([sub, ...tail], { from: "user" });
2462
+ }
2463
+ catch (e) {
2464
+ const err = e;
2465
+ if (err.code === "commander.helpDisplayed" || err.code === "commander.version")
2466
+ return;
2467
+ throw e;
2468
+ }
2469
+ return;
2470
+ }
2471
+ if (cmd === "schedule") {
2472
+ const loaded = meshLoaded(profileArg);
2473
+ const getLoaded = () => loaded;
2474
+ const branch = (await import("./commands/schedule-cli.js")).buildScheduleCommand(getLoaded);
2196
2475
  try {
2197
2476
  await branch.parseAsync(rest.slice(1), { from: "user" });
2198
2477
  }