tmux-ide 2.6.0 → 2.6.1

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 (209) hide show
  1. package/bin/cli.js +13 -5
  2. package/bin/cli.ts +1 -1
  3. package/bunfig.toml +4 -0
  4. package/package.json +11 -3
  5. package/packages/contracts/package.json +22 -0
  6. package/packages/contracts/src/__tests__/ide-config.test.ts +46 -0
  7. package/packages/contracts/src/__tests__/terminals.test.ts +87 -0
  8. package/packages/contracts/src/actions-contract.ts +310 -0
  9. package/packages/contracts/src/actions-errors.ts +41 -0
  10. package/packages/contracts/src/domain.ts +36 -0
  11. package/packages/contracts/src/ide-config.ts +170 -0
  12. package/packages/contracts/src/index.ts +24 -0
  13. package/packages/contracts/src/lib-internal/auth.ts +13 -0
  14. package/packages/contracts/src/lib-internal/hq.ts +38 -0
  15. package/packages/contracts/src/terminals.ts +116 -0
  16. package/packages/contracts/src/tmux.ts +60 -0
  17. package/packages/contracts/src/workspace.ts +67 -0
  18. package/packages/daemon/src/agent-explain.ts +270 -0
  19. package/packages/daemon/src/attach.ts +20 -0
  20. package/packages/daemon/src/bin.ts +4 -0
  21. package/packages/daemon/src/canonical.ts +7 -0
  22. package/packages/daemon/src/cli.ts +499 -0
  23. package/packages/daemon/src/command-center/actions/contract.ts +2 -0
  24. package/packages/daemon/src/command-center/actions/dispatcher.ts +137 -0
  25. package/packages/daemon/src/command-center/actions/errors.ts +105 -0
  26. package/packages/daemon/src/command-center/actions/handlers/_project-context.ts +30 -0
  27. package/packages/daemon/src/command-center/actions/handlers/_resolve-project.ts +78 -0
  28. package/packages/daemon/src/command-center/actions/handlers/app-set-remote-access.ts +118 -0
  29. package/packages/daemon/src/command-center/actions/handlers/config-actions.ts +113 -0
  30. package/packages/daemon/src/command-center/actions/handlers/daemon-shutdown.ts +38 -0
  31. package/packages/daemon/src/command-center/actions/handlers/project-activate.ts +30 -0
  32. package/packages/daemon/src/command-center/actions/handlers/project-launch.ts +70 -0
  33. package/packages/daemon/src/command-center/actions/handlers/project-open-terminal.ts +87 -0
  34. package/packages/daemon/src/command-center/actions/handlers/project-restart.ts +38 -0
  35. package/packages/daemon/src/command-center/actions/handlers/project-stop.ts +62 -0
  36. package/packages/daemon/src/command-center/actions/handlers/terminal-respawn.ts +119 -0
  37. package/packages/daemon/src/command-center/actions/handlers/terminal-stop.ts +35 -0
  38. package/packages/daemon/src/command-center/actions/registry.ts +149 -0
  39. package/packages/daemon/src/command-center/discovery.ts +96 -0
  40. package/packages/daemon/src/command-center/index.ts +31 -0
  41. package/packages/daemon/src/command-center/schemas.ts +85 -0
  42. package/packages/daemon/src/command-center/server.ts +1260 -0
  43. package/packages/daemon/src/command-center/ws-events.ts +316 -0
  44. package/packages/daemon/src/config.ts +549 -0
  45. package/packages/daemon/src/detect.ts +248 -0
  46. package/packages/daemon/src/doctor.ts +242 -0
  47. package/packages/daemon/src/embed.ts +5 -0
  48. package/packages/daemon/src/index.ts +12 -0
  49. package/packages/daemon/src/init.ts +211 -0
  50. package/packages/daemon/src/inspect.ts +178 -0
  51. package/packages/daemon/src/js-yaml.d.ts +10 -0
  52. package/packages/daemon/src/launch.ts +349 -0
  53. package/packages/daemon/src/lib/active-projects.ts +49 -0
  54. package/packages/daemon/src/lib/agent-discovery.ts +121 -0
  55. package/packages/daemon/src/lib/app-config.ts +336 -0
  56. package/packages/daemon/src/lib/app-settings.ts +53 -0
  57. package/packages/daemon/src/lib/auth/auth-service.ts +227 -0
  58. package/packages/daemon/src/lib/auth/middleware.ts +56 -0
  59. package/packages/daemon/src/lib/auth/types.ts +2 -0
  60. package/packages/daemon/src/lib/auth-token.ts +5 -0
  61. package/packages/daemon/src/lib/authorship.ts +280 -0
  62. package/packages/daemon/src/lib/canonical-daemon.ts +122 -0
  63. package/packages/daemon/src/lib/cli-action-bridge.ts +216 -0
  64. package/packages/daemon/src/lib/daemon-embed.ts +782 -0
  65. package/packages/daemon/src/lib/daemon-watchdog.ts +111 -0
  66. package/packages/daemon/src/lib/daemon.ts +79 -0
  67. package/packages/daemon/src/lib/dot-path.ts +17 -0
  68. package/packages/daemon/src/lib/errors.ts +67 -0
  69. package/packages/daemon/src/lib/filesystem-browser.ts +292 -0
  70. package/packages/daemon/src/lib/launch-plan.ts +90 -0
  71. package/packages/daemon/src/lib/log.ts +134 -0
  72. package/packages/daemon/src/lib/output.ts +76 -0
  73. package/packages/daemon/src/lib/project-init-runner.ts +150 -0
  74. package/packages/daemon/src/lib/project-inspect.ts +80 -0
  75. package/packages/daemon/src/lib/project-onboard.ts +149 -0
  76. package/packages/daemon/src/lib/project-probe.ts +92 -0
  77. package/packages/daemon/src/lib/project-registry.ts +296 -0
  78. package/packages/daemon/src/lib/session-monitor.ts +122 -0
  79. package/packages/daemon/src/lib/session-options.ts +100 -0
  80. package/packages/daemon/src/lib/shell.ts +8 -0
  81. package/packages/daemon/src/lib/sizes.ts +36 -0
  82. package/packages/daemon/src/lib/skill-sync.ts +155 -0
  83. package/packages/daemon/src/lib/slugify.ts +10 -0
  84. package/packages/daemon/src/lib/terminals-store.ts +125 -0
  85. package/packages/daemon/src/lib/update-check.ts +298 -0
  86. package/packages/daemon/src/lib/update.ts +158 -0
  87. package/packages/daemon/src/lib/workspace-registry.ts +229 -0
  88. package/packages/daemon/src/lib/worktree.ts +289 -0
  89. package/packages/daemon/src/lib/yaml-io.ts +27 -0
  90. package/packages/daemon/src/ls.ts +40 -0
  91. package/packages/daemon/src/restart.ts +24 -0
  92. package/packages/daemon/src/restore.ts +514 -0
  93. package/packages/daemon/src/schemas/domain.ts +2 -0
  94. package/packages/daemon/src/schemas/filesystem.ts +34 -0
  95. package/packages/daemon/src/schemas/ide-config.ts +2 -0
  96. package/packages/daemon/src/schemas/index.ts +59 -0
  97. package/packages/daemon/src/schemas/inspect.ts +67 -0
  98. package/packages/daemon/src/schemas/registry.ts +55 -0
  99. package/packages/daemon/src/schemas/ws-events.ts +135 -0
  100. package/packages/daemon/src/send.ts +171 -0
  101. package/packages/daemon/src/server/README.md +15 -0
  102. package/packages/daemon/src/server/index.ts +74 -0
  103. package/packages/daemon/src/server/pty-bridge.ts +532 -0
  104. package/packages/daemon/src/server/standalone.ts +18 -0
  105. package/packages/daemon/src/server/ws-route.ts +483 -0
  106. package/packages/daemon/src/status.ts +59 -0
  107. package/packages/daemon/src/stop.ts +28 -0
  108. package/packages/daemon/src/terminal/NodePtyAdapter.ts +271 -0
  109. package/packages/daemon/src/terminal/PtyAdapter.ts +140 -0
  110. package/packages/daemon/src/terminal/README.md +92 -0
  111. package/packages/daemon/src/tui/chrome/cheatsheet.ts +260 -0
  112. package/packages/daemon/src/tui/chrome/chip.ts +30 -0
  113. package/packages/daemon/src/tui/chrome/events.ts +119 -0
  114. package/packages/daemon/src/tui/chrome/kitty-keys.ts +55 -0
  115. package/packages/daemon/src/tui/chrome/menu.ts +289 -0
  116. package/packages/daemon/src/tui/chrome/notify.ts +195 -0
  117. package/packages/daemon/src/tui/chrome/panels.ts +111 -0
  118. package/packages/daemon/src/tui/chrome/sidebar.ts +222 -0
  119. package/packages/daemon/src/tui/chrome/snapshot.ts +425 -0
  120. package/packages/daemon/src/tui/chrome/statusline.ts +595 -0
  121. package/packages/daemon/src/tui/chrome/updater.ts +428 -0
  122. package/packages/daemon/src/tui/chrome/welcome.ts +124 -0
  123. package/packages/daemon/src/tui/compiled.ts +113 -0
  124. package/packages/daemon/src/tui/detect/classify.ts +193 -0
  125. package/packages/daemon/src/tui/detect/manifest-loader.ts +192 -0
  126. package/packages/daemon/src/tui/detect/manifest.ts +184 -0
  127. package/packages/daemon/src/tui/detect/manifests.ts +226 -0
  128. package/packages/daemon/src/tui/detect/process-tree.ts +197 -0
  129. package/packages/daemon/src/tui/detect/snapshot.ts +70 -0
  130. package/packages/daemon/src/tui/integrations/claude.ts +176 -0
  131. package/packages/daemon/src/tui/integrations/offer.ts +145 -0
  132. package/packages/daemon/src/tui/main.ts +70 -0
  133. package/packages/daemon/src/tui/mirror/control-client.ts +143 -0
  134. package/packages/daemon/src/tui/mirror/control.ts +97 -0
  135. package/packages/daemon/src/tui/mirror/pane-mirror.ts +81 -0
  136. package/packages/daemon/src/tui/mirror/viewer.tsx +166 -0
  137. package/packages/daemon/src/tui/team/CONTROL.md +50 -0
  138. package/packages/daemon/src/tui/team/entry.ts +11 -0
  139. package/packages/daemon/src/tui/team/fuzzy.ts +133 -0
  140. package/packages/daemon/src/tui/team/home.ts +170 -0
  141. package/packages/daemon/src/tui/team/index.tsx +1521 -0
  142. package/packages/daemon/src/tui/team/input.ts +34 -0
  143. package/packages/daemon/src/tui/team/keymap.ts +127 -0
  144. package/packages/daemon/src/tui/team/mouse.ts +29 -0
  145. package/packages/daemon/src/tui/team/nav.ts +31 -0
  146. package/packages/daemon/src/tui/team/preview.ts +34 -0
  147. package/packages/daemon/src/tui/team/projects.ts +191 -0
  148. package/packages/daemon/src/tui/team/report.ts +73 -0
  149. package/packages/daemon/src/tui/team/sessions.ts +344 -0
  150. package/packages/daemon/src/tui/team/tree.ts +62 -0
  151. package/packages/daemon/src/types.ts +13 -0
  152. package/packages/daemon/src/ui/index.ts +32 -0
  153. package/packages/daemon/src/ui/terminal/index.ts +9 -0
  154. package/packages/daemon/src/ui/types.ts +91 -0
  155. package/packages/daemon/src/ui/web/base.css +80 -0
  156. package/packages/daemon/src/ui/web/components/Box.tsx +59 -0
  157. package/packages/daemon/src/ui/web/components/Input.tsx +32 -0
  158. package/packages/daemon/src/ui/web/components/ScrollBox.tsx +60 -0
  159. package/packages/daemon/src/ui/web/components/Text.tsx +28 -0
  160. package/packages/daemon/src/ui/web/hooks.ts +106 -0
  161. package/packages/daemon/src/ui/web/index.ts +27 -0
  162. package/packages/daemon/src/ui/web/render.ts +77 -0
  163. package/packages/daemon/src/ui/web/utils/color.ts +27 -0
  164. package/packages/daemon/src/validate.ts +217 -0
  165. package/packages/daemon/src/widgets/changes/README.md +3 -0
  166. package/packages/daemon/src/widgets/changes/index.tsx +691 -0
  167. package/packages/daemon/src/widgets/config/README.md +3 -0
  168. package/packages/daemon/src/widgets/config/index.tsx +481 -0
  169. package/packages/daemon/src/widgets/explorer/README.md +3 -0
  170. package/packages/daemon/src/widgets/explorer/breadcrumbs.tsx +77 -0
  171. package/packages/daemon/src/widgets/explorer/footer.tsx +20 -0
  172. package/packages/daemon/src/widgets/explorer/header.tsx +23 -0
  173. package/packages/daemon/src/widgets/explorer/index.tsx +456 -0
  174. package/packages/daemon/src/widgets/explorer/tree-model.ts +103 -0
  175. package/packages/daemon/src/widgets/explorer/tree.tsx +165 -0
  176. package/packages/daemon/src/widgets/lib/config-model.ts +116 -0
  177. package/packages/daemon/src/widgets/lib/files.ts +88 -0
  178. package/packages/daemon/src/widgets/lib/git.ts +88 -0
  179. package/packages/daemon/src/widgets/lib/grammar.ts +126 -0
  180. package/packages/daemon/src/widgets/lib/help-overlay.tsx +101 -0
  181. package/packages/daemon/src/widgets/lib/pane-comms.ts +209 -0
  182. package/packages/daemon/src/widgets/lib/theme.ts +194 -0
  183. package/packages/daemon/src/widgets/lib/watcher.ts +132 -0
  184. package/packages/daemon/src/widgets/preview/README.md +3 -0
  185. package/packages/daemon/src/widgets/preview/index.tsx +416 -0
  186. package/packages/daemon/src/widgets/resolve.ts +121 -0
  187. package/packages/daemon/src/widgets/setup/README.md +3 -0
  188. package/packages/daemon/src/widgets/setup/agent-naming.tsx +112 -0
  189. package/packages/daemon/src/widgets/setup/config-tree.tsx +246 -0
  190. package/packages/daemon/src/widgets/setup/detect-panel.tsx +72 -0
  191. package/packages/daemon/src/widgets/setup/field-editor.tsx +265 -0
  192. package/packages/daemon/src/widgets/setup/footer.tsx +107 -0
  193. package/packages/daemon/src/widgets/setup/index.tsx +341 -0
  194. package/packages/daemon/src/widgets/setup/layout-picker.tsx +96 -0
  195. package/packages/daemon/src/widgets/setup/orchestrator-panel.tsx +200 -0
  196. package/packages/daemon/src/widgets/setup/review-panel.tsx +140 -0
  197. package/packages/daemon/src/widgets/setup/setup-model.ts +188 -0
  198. package/packages/daemon/src/widgets/sidebar/index.tsx +527 -0
  199. package/packages/tmux-bridge/package.json +22 -0
  200. package/packages/tmux-bridge/src/errors.ts +28 -0
  201. package/packages/tmux-bridge/src/index.ts +31 -0
  202. package/packages/tmux-bridge/src/monitor.ts +77 -0
  203. package/packages/tmux-bridge/src/panes.ts +136 -0
  204. package/packages/tmux-bridge/src/runner.test.ts +501 -0
  205. package/packages/tmux-bridge/src/runner.ts +91 -0
  206. package/packages/tmux-bridge/src/sessions.ts +126 -0
  207. package/packages/tmux-bridge/src/targeting.test.ts +107 -0
  208. package/packages/tmux-bridge/src/targeting.ts +90 -0
  209. package/scripts/postinstall.js +26 -2
package/bin/cli.js CHANGED
@@ -2340,7 +2340,7 @@ function resolveTuiLaunch(input) {
2340
2340
  const reasons = [];
2341
2341
  if (!input.checkoutExists) {
2342
2342
  reasons.push(
2343
- "the TUI widget sources are absent (they ship only in a cloned tmux-ide checkout)"
2343
+ "the TUI widget sources are absent (reinstall tmux-ide \u2014 releases since v2.6.1 ship them)"
2344
2344
  );
2345
2345
  }
2346
2346
  if (!input.bunAvailable) {
@@ -10975,7 +10975,7 @@ var require_package = __commonJS({
10975
10975
  "package.json"(exports, module) {
10976
10976
  module.exports = {
10977
10977
  name: "tmux-ide",
10978
- version: "2.6.0",
10978
+ version: "2.6.1",
10979
10979
  description: "Turn any project into a tmux-powered terminal IDE with a simple ide.yml",
10980
10980
  type: "module",
10981
10981
  bin: {
@@ -10987,7 +10987,13 @@ var require_package = __commonJS({
10987
10987
  "skill",
10988
10988
  "templates",
10989
10989
  "packages/daemon/dist",
10990
- "!packages/daemon/dist/tui"
10990
+ "!packages/daemon/dist/tui",
10991
+ "packages/daemon/src",
10992
+ "bunfig.toml",
10993
+ "packages/contracts/src",
10994
+ "packages/tmux-bridge/src",
10995
+ "packages/tmux-bridge/package.json",
10996
+ "packages/contracts/package.json"
10991
10997
  ],
10992
10998
  scripts: {
10993
10999
  build: "pnpm build:cli",
@@ -11035,7 +11041,6 @@ var require_package = __commonJS({
11035
11041
  "@hono/node-server": "^1.19.11",
11036
11042
  "@hono/zod-validator": "^0.7.6",
11037
11043
  "@opentui/core": "^0.1.88",
11038
- "@opentui/core-darwin-arm64": "^0.1.88",
11039
11044
  "@opentui/solid": "^0.1.88",
11040
11045
  "@parcel/watcher": "^2.5.6",
11041
11046
  "@types/ws": "^8.18.1",
@@ -11068,6 +11073,9 @@ var require_package = __commonJS({
11068
11073
  turbo: "^2.3.3",
11069
11074
  typescript: "^5.9.3",
11070
11075
  vitest: "^4.1.0"
11076
+ },
11077
+ optionalDependencies: {
11078
+ "@opentui/core-darwin-arm64": "^0.1.88"
11071
11079
  }
11072
11080
  };
11073
11081
  }
@@ -12781,7 +12789,7 @@ function execBunWidget(surface, scriptPath, args, commandLabel, extraEnv = {}) {
12781
12789
  if (launch2.mode === "unavailable") {
12782
12790
  throw new IdeError(
12783
12791
  `\`tmux-ide ${commandLabel}\` is unavailable because ${launch2.reasons.join(" and ")}.
12784
- Run it from a cloned tmux-ide checkout with bun installed, or install a release that ships the compiled binary.`,
12792
+ Install bun (https://bun.sh) \u2014 the TUI surfaces run on it. Sources ship with the npm package since v2.6.1.`,
12785
12793
  { code: "USAGE", exitCode: 1 }
12786
12794
  );
12787
12795
  }
package/bin/cli.ts CHANGED
@@ -264,7 +264,7 @@ function execBunWidget(
264
264
  if (launch.mode === "unavailable") {
265
265
  throw new IdeError(
266
266
  `\`tmux-ide ${commandLabel}\` is unavailable because ${launch.reasons.join(" and ")}.\n` +
267
- `Run it from a cloned tmux-ide checkout with bun installed, or install a release that ships the compiled binary.`,
267
+ `Install bun (https://bun.sh) — the TUI surfaces run on it. Sources ship with the npm package since v2.6.1.`,
268
268
  { code: "USAGE", exitCode: 1 },
269
269
  );
270
270
  }
package/bunfig.toml ADDED
@@ -0,0 +1,4 @@
1
+ preload = ["@opentui/solid/preload"]
2
+
3
+ [test]
4
+ root = "./src"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tmux-ide",
3
- "version": "2.6.0",
3
+ "version": "2.6.1",
4
4
  "description": "Turn any project into a tmux-powered terminal IDE with a simple ide.yml",
5
5
  "type": "module",
6
6
  "bin": {
@@ -12,7 +12,13 @@
12
12
  "skill",
13
13
  "templates",
14
14
  "packages/daemon/dist",
15
- "!packages/daemon/dist/tui"
15
+ "!packages/daemon/dist/tui",
16
+ "packages/daemon/src",
17
+ "bunfig.toml",
18
+ "packages/contracts/src",
19
+ "packages/tmux-bridge/src",
20
+ "packages/tmux-bridge/package.json",
21
+ "packages/contracts/package.json"
16
22
  ],
17
23
  "scripts": {
18
24
  "build": "pnpm build:cli",
@@ -60,7 +66,6 @@
60
66
  "@hono/node-server": "^1.19.11",
61
67
  "@hono/zod-validator": "^0.7.6",
62
68
  "@opentui/core": "^0.1.88",
63
- "@opentui/core-darwin-arm64": "^0.1.88",
64
69
  "@opentui/solid": "^0.1.88",
65
70
  "@parcel/watcher": "^2.5.6",
66
71
  "@types/ws": "^8.18.1",
@@ -93,5 +98,8 @@
93
98
  "turbo": "^2.3.3",
94
99
  "typescript": "^5.9.3",
95
100
  "vitest": "^4.1.0"
101
+ },
102
+ "optionalDependencies": {
103
+ "@opentui/core-darwin-arm64": "^0.1.88"
96
104
  }
97
105
  }
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "@tmux-ide/contracts",
3
+ "version": "0.0.1",
4
+ "private": true,
5
+ "type": "module",
6
+ "main": "src/index.ts",
7
+ "exports": {
8
+ ".": "./src/index.ts"
9
+ },
10
+ "scripts": {
11
+ "lint": "eslint src",
12
+ "typecheck": "tsc --noEmit",
13
+ "test": "vitest run"
14
+ },
15
+ "dependencies": {
16
+ "zod": "^4.3.6"
17
+ },
18
+ "devDependencies": {
19
+ "typescript": "^5.9.3",
20
+ "vitest": "^4.1.0"
21
+ }
22
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Schema tests for the `sidebar` ide.yml sugar (M15.2).
3
+ */
4
+ import { describe, expect, it } from "vitest";
5
+ import { IdeConfigSchema, SidebarConfigSchema } from "../ide-config";
6
+
7
+ const base = { rows: [{ panes: [{ title: "shell" }] }] };
8
+
9
+ describe("SidebarConfigSchema", () => {
10
+ it("accepts the boolean form", () => {
11
+ expect(SidebarConfigSchema.parse(true)).toBe(true);
12
+ expect(SidebarConfigSchema.parse(false)).toBe(false);
13
+ });
14
+
15
+ it("accepts the object form with an optional width", () => {
16
+ expect(SidebarConfigSchema.parse({ width: "30" })).toEqual({ width: "30" });
17
+ expect(SidebarConfigSchema.parse({})).toEqual({});
18
+ });
19
+
20
+ it("rejects a bare number", () => {
21
+ expect(() => SidebarConfigSchema.parse(30)).toThrow();
22
+ });
23
+ });
24
+
25
+ describe("IdeConfigSchema — sidebar", () => {
26
+ it("parses `sidebar: true`", () => {
27
+ const cfg = IdeConfigSchema.parse({ ...base, sidebar: true });
28
+ expect(cfg.sidebar).toBe(true);
29
+ });
30
+
31
+ it("parses `sidebar: { width }`", () => {
32
+ const cfg = IdeConfigSchema.parse({ ...base, sidebar: { width: "40" } });
33
+ expect(cfg.sidebar).toEqual({ width: "40" });
34
+ });
35
+
36
+ it("leaves sidebar undefined when omitted", () => {
37
+ expect(IdeConfigSchema.parse(base).sidebar).toBeUndefined();
38
+ });
39
+
40
+ it("accepts a pane of type sidebar", () => {
41
+ const cfg = IdeConfigSchema.parse({
42
+ rows: [{ panes: [{ title: "nav", type: "sidebar" }] }],
43
+ });
44
+ expect(cfg.rows[0]!.panes[0]!.type).toBe("sidebar");
45
+ });
46
+ });
@@ -0,0 +1,87 @@
1
+ /**
2
+ * `createScriptTerminalId` determinism tests (G20-P1).
3
+ *
4
+ * The whole point of the script-derived id is determinism — two
5
+ * callers asking for "the run script for project X, scope Y, script
6
+ * Z" must collapse to one bridge. These tests pin that contract.
7
+ */
8
+
9
+ import { describe, expect, it } from "vitest";
10
+ import { createScriptTerminalId } from "../terminals";
11
+
12
+ describe("createScriptTerminalId", () => {
13
+ it("is deterministic for the same input", async () => {
14
+ const a = await createScriptTerminalId({
15
+ projectId: "proj",
16
+ scopeId: "feat-x",
17
+ kind: "run",
18
+ script: "pnpm dev",
19
+ });
20
+ const b = await createScriptTerminalId({
21
+ projectId: "proj",
22
+ scopeId: "feat-x",
23
+ kind: "run",
24
+ script: "pnpm dev",
25
+ });
26
+ expect(a).toBe(b);
27
+ expect(a).toHaveLength(32);
28
+ });
29
+
30
+ it("changes when scopeId changes (same project + script)", async () => {
31
+ const a = await createScriptTerminalId({
32
+ projectId: "proj",
33
+ scopeId: "feat-x",
34
+ kind: "run",
35
+ script: "pnpm dev",
36
+ });
37
+ const b = await createScriptTerminalId({
38
+ projectId: "proj",
39
+ scopeId: "feat-y",
40
+ kind: "run",
41
+ script: "pnpm dev",
42
+ });
43
+ expect(a).not.toBe(b);
44
+ });
45
+
46
+ it("changes when kind changes", async () => {
47
+ const run = await createScriptTerminalId({
48
+ projectId: "proj",
49
+ scopeId: "feat-x",
50
+ kind: "run",
51
+ script: "pnpm dev",
52
+ });
53
+ const setup = await createScriptTerminalId({
54
+ projectId: "proj",
55
+ scopeId: "feat-x",
56
+ kind: "setup",
57
+ script: "pnpm dev",
58
+ });
59
+ expect(run).not.toBe(setup);
60
+ });
61
+
62
+ it("accepts taskId as a backward-compat alias for scopeId", async () => {
63
+ const viaScope = await createScriptTerminalId({
64
+ projectId: "proj",
65
+ scopeId: "feat-x",
66
+ kind: "run",
67
+ script: "pnpm dev",
68
+ });
69
+ const viaTask = await createScriptTerminalId({
70
+ projectId: "proj",
71
+ taskId: "feat-x",
72
+ kind: "run",
73
+ script: "pnpm dev",
74
+ });
75
+ expect(viaScope).toBe(viaTask);
76
+ });
77
+
78
+ it("rejects calls without scope or task", async () => {
79
+ await expect(
80
+ createScriptTerminalId({
81
+ projectId: "proj",
82
+ kind: "run",
83
+ script: "pnpm dev",
84
+ }),
85
+ ).rejects.toThrow(/scopeId/);
86
+ });
87
+ });
@@ -0,0 +1,310 @@
1
+ /**
2
+ * Action contract for the v2 dispatcher.
3
+ *
4
+ * CLI and command-center clients import the schemas in this file — input +
5
+ * output Zod shapes for every action. The server-side dispatcher wires each
6
+ * name to a handler that consumes the input and returns the typed result.
7
+ *
8
+ * Adding a new action:
9
+ * 1. Define `<Name>InputSchemaZ` and `<Name>ResultSchemaZ` here
10
+ * 2. Add it to {@link ActionContractsZ} below
11
+ * 3. Add a handler in `handlers/` and register it in `registry.ts`
12
+ *
13
+ * Invariants:
14
+ * - Names are dot-namespaced (`<noun>.<verb>`)
15
+ * - Both schemas live here so the wire format never drifts between clients
16
+ * - Action names ARE the discriminator — keep them stable; never rename
17
+ */
18
+
19
+ import { z } from "zod";
20
+ import { IdeConfigSchema, PaneSchema } from "./ide-config.ts";
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // project.openTerminal
24
+ // ---------------------------------------------------------------------------
25
+
26
+ export const ProjectOpenTerminalInputZ = z.object({
27
+ name: z.string().min(1),
28
+ });
29
+ export const ProjectOpenTerminalResultZ = z.object({
30
+ sessionName: z.string(),
31
+ cwd: z.string().min(1),
32
+ terminalTabId: z.string(),
33
+ /**
34
+ * `true` when the dispatcher had to launch the tmux session as part of
35
+ * resolving the terminal. `false` when the session was already running.
36
+ */
37
+ launched: z.boolean(),
38
+ });
39
+
40
+ // ---------------------------------------------------------------------------
41
+ // project.launch
42
+ // ---------------------------------------------------------------------------
43
+
44
+ export const ProjectLaunchInputZ = z.object({
45
+ name: z.string().min(1),
46
+ });
47
+ export const ProjectLaunchResultZ = z.object({
48
+ sessionName: z.string(),
49
+ /**
50
+ * `false` when the session was already running (idempotent no-op),
51
+ * `true` when this call started a fresh session.
52
+ */
53
+ started: z.boolean(),
54
+ });
55
+
56
+ // ---------------------------------------------------------------------------
57
+ // project.stop
58
+ // ---------------------------------------------------------------------------
59
+
60
+ export const ProjectStopInputZ = z.object({
61
+ name: z.string().min(1),
62
+ });
63
+ export const ProjectStopResultZ = z.object({
64
+ sessionName: z.string(),
65
+ /**
66
+ * `false` when no session was running (idempotent no-op),
67
+ * `true` when this call killed a session.
68
+ */
69
+ stopped: z.boolean(),
70
+ });
71
+
72
+ // ---------------------------------------------------------------------------
73
+ // project.restart
74
+ // ---------------------------------------------------------------------------
75
+
76
+ export const ProjectRestartInputZ = z.object({
77
+ name: z.string().min(1),
78
+ });
79
+ export const ProjectRestartResultZ = z.object({
80
+ sessionName: z.string(),
81
+ restarted: z.literal(true),
82
+ });
83
+
84
+ // ---------------------------------------------------------------------------
85
+ // project.activate
86
+ // ---------------------------------------------------------------------------
87
+
88
+ export const ProjectActivateInputZ = z.object({
89
+ name: z.string().min(1),
90
+ });
91
+ export const ProjectActivateResultZ = z.object({
92
+ active: z.boolean(),
93
+ projectName: z.string(),
94
+ });
95
+
96
+ // ---------------------------------------------------------------------------
97
+ // terminal.respawn
98
+ // ---------------------------------------------------------------------------
99
+
100
+ export const TerminalRespawnInputZ = z.object({
101
+ sessionName: z.string().min(1),
102
+ terminalId: z.string().min(1),
103
+ /**
104
+ * Optional cwd override. Omit to respawn at the bridge's current cwd
105
+ * (re-using the `lastCwd` recorded by the PTY bridge).
106
+ */
107
+ cwd: z.string().min(1).optional(),
108
+ });
109
+ export const TerminalRespawnResultZ = z.object({
110
+ respawned: z.literal(true),
111
+ cwd: z.string().min(1),
112
+ });
113
+
114
+ // ---------------------------------------------------------------------------
115
+ // terminal.stop
116
+ // ---------------------------------------------------------------------------
117
+
118
+ export const TerminalStopInputZ = z.object({
119
+ sessionName: z.string().min(1),
120
+ terminalId: z.string().min(1),
121
+ });
122
+ export const TerminalStopResultZ = z.object({
123
+ stopped: z.literal(true),
124
+ });
125
+
126
+ // ---------------------------------------------------------------------------
127
+ // config.*
128
+ // ---------------------------------------------------------------------------
129
+
130
+ export const ConfigSetInputZ = z.object({
131
+ projectName: z.string().min(1).optional(),
132
+ path: z.string().min(1),
133
+ value: z.unknown(),
134
+ });
135
+ export const ConfigResultZ = z.object({
136
+ config: IdeConfigSchema,
137
+ });
138
+
139
+ export const ConfigAddPaneInputZ = PaneSchema.partial().extend({
140
+ projectName: z.string().min(1).optional(),
141
+ rowIndex: z.number().int().min(0),
142
+ });
143
+ export const ConfigAddPaneResultZ = ConfigResultZ;
144
+
145
+ export const ConfigRemovePaneInputZ = z.object({
146
+ projectName: z.string().min(1).optional(),
147
+ rowIndex: z.number().int().min(0),
148
+ paneIndex: z.number().int().min(0),
149
+ });
150
+ export const ConfigRemovePaneResultZ = ConfigResultZ;
151
+
152
+ export const ConfigAddRowInputZ = z.object({
153
+ projectName: z.string().min(1).optional(),
154
+ size: z.string().optional(),
155
+ });
156
+ export const ConfigAddRowResultZ = ConfigResultZ;
157
+
158
+ export const ConfigEnableTeamInputZ = z.object({
159
+ projectName: z.string().min(1).optional(),
160
+ name: z.string().min(1).optional(),
161
+ });
162
+ export const ConfigEnableTeamResultZ = ConfigResultZ;
163
+
164
+ export const ConfigDisableTeamInputZ = z.object({
165
+ projectName: z.string().min(1).optional(),
166
+ });
167
+ export const ConfigDisableTeamResultZ = ConfigResultZ;
168
+
169
+ // ---------------------------------------------------------------------------
170
+ // app.*
171
+ // ---------------------------------------------------------------------------
172
+
173
+ export const AppSetRemoteAccessInputZ = z.object({
174
+ enabled: z.boolean(),
175
+ });
176
+ export const AppSetRemoteAccessResultZ = z.object({
177
+ enabled: z.boolean(),
178
+ url: z.string().nullable(),
179
+ token: z.string().nullable(),
180
+ qrPayload: z.string().nullable(),
181
+ });
182
+
183
+ // ---------------------------------------------------------------------------
184
+ // daemon.*
185
+ // ---------------------------------------------------------------------------
186
+
187
+ export const DaemonShutdownInputZ = z.object({
188
+ reason: z.string().optional(),
189
+ });
190
+ export const DaemonShutdownResultZ = z.object({
191
+ stopping: z.literal(true),
192
+ });
193
+
194
+ // ---------------------------------------------------------------------------
195
+ // Registry of action contracts (name → input/output schemas)
196
+ // ---------------------------------------------------------------------------
197
+
198
+ export const ActionContractsZ = {
199
+ "project.openTerminal": {
200
+ input: ProjectOpenTerminalInputZ,
201
+ result: ProjectOpenTerminalResultZ,
202
+ },
203
+ "project.launch": {
204
+ input: ProjectLaunchInputZ,
205
+ result: ProjectLaunchResultZ,
206
+ },
207
+ "project.stop": {
208
+ input: ProjectStopInputZ,
209
+ result: ProjectStopResultZ,
210
+ },
211
+ "project.restart": {
212
+ input: ProjectRestartInputZ,
213
+ result: ProjectRestartResultZ,
214
+ },
215
+ "project.activate": {
216
+ input: ProjectActivateInputZ,
217
+ result: ProjectActivateResultZ,
218
+ },
219
+ "terminal.respawn": {
220
+ input: TerminalRespawnInputZ,
221
+ result: TerminalRespawnResultZ,
222
+ },
223
+ "terminal.stop": {
224
+ input: TerminalStopInputZ,
225
+ result: TerminalStopResultZ,
226
+ },
227
+ "config.set": {
228
+ input: ConfigSetInputZ,
229
+ result: ConfigResultZ,
230
+ },
231
+ "config.addPane": {
232
+ input: ConfigAddPaneInputZ,
233
+ result: ConfigAddPaneResultZ,
234
+ },
235
+ "config.removePane": {
236
+ input: ConfigRemovePaneInputZ,
237
+ result: ConfigRemovePaneResultZ,
238
+ },
239
+ "config.addRow": {
240
+ input: ConfigAddRowInputZ,
241
+ result: ConfigAddRowResultZ,
242
+ },
243
+ "config.enableTeam": {
244
+ input: ConfigEnableTeamInputZ,
245
+ result: ConfigEnableTeamResultZ,
246
+ },
247
+ "config.disableTeam": {
248
+ input: ConfigDisableTeamInputZ,
249
+ result: ConfigDisableTeamResultZ,
250
+ },
251
+ "app.setRemoteAccess": {
252
+ input: AppSetRemoteAccessInputZ,
253
+ result: AppSetRemoteAccessResultZ,
254
+ },
255
+ "daemon.shutdown": {
256
+ input: DaemonShutdownInputZ,
257
+ result: DaemonShutdownResultZ,
258
+ },
259
+ } as const;
260
+
261
+ export type ActionName = keyof typeof ActionContractsZ;
262
+
263
+ export const ACTION_NAMES = Object.keys(ActionContractsZ) as ActionName[];
264
+
265
+ // ---------------------------------------------------------------------------
266
+ // Typed input / result helpers
267
+ // ---------------------------------------------------------------------------
268
+
269
+ export type ActionInput<N extends ActionName> = z.infer<(typeof ActionContractsZ)[N]["input"]>;
270
+ export type ActionResult<N extends ActionName> = z.infer<(typeof ActionContractsZ)[N]["result"]>;
271
+
272
+ // ---------------------------------------------------------------------------
273
+ // Wire envelope (what the HTTP endpoint actually returns)
274
+ // ---------------------------------------------------------------------------
275
+
276
+ export interface ActionErrorEnvelope {
277
+ ok: false;
278
+ error: {
279
+ code: string;
280
+ message: string;
281
+ details?: unknown;
282
+ };
283
+ }
284
+
285
+ export interface ActionOkEnvelope<R> {
286
+ ok: true;
287
+ result: R;
288
+ }
289
+
290
+ export type ActionResponse<N extends ActionName> =
291
+ | ActionOkEnvelope<ActionResult<N>>
292
+ | ActionErrorEnvelope;
293
+
294
+ /**
295
+ * Validate that a string is a known action name. Used by the dispatcher to
296
+ * narrow URL params before looking up the handler.
297
+ */
298
+ export function isActionName(name: string): name is ActionName {
299
+ return name in ActionContractsZ;
300
+ }
301
+
302
+ /**
303
+ * Helper for a tagged WS broadcast frame so subscribers can decode without
304
+ * special-casing every action name.
305
+ */
306
+ export interface ActionCompleteFrame<N extends ActionName = ActionName> {
307
+ type: "action.complete";
308
+ name: N;
309
+ result: ActionResult<N>;
310
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Wire codes for the v2 action dispatcher's error envelope. Codes are
3
+ * stable identifiers — never rename without a coordinated dashboard
4
+ * release. New codes go at the bottom of the union.
5
+ *
6
+ * The runtime class that throws these (`ActionError`, `TerminalCwdError`,
7
+ * etc.) lives in @tmux-ide/daemon's command-center/actions/errors.ts; the
8
+ * type lives here so dashboards and other UI clients can branch on the
9
+ * code without reaching into daemon-internal modules.
10
+ */
11
+ export type ActionErrorCode =
12
+ | "project_not_found"
13
+ | "cwd_not_found"
14
+ | "cwd_not_directory"
15
+ | "cwd_stat_failed"
16
+ | "session_already_running"
17
+ | "session_not_running"
18
+ | "launch_failed"
19
+ | "stop_failed"
20
+ | "terminal_not_found"
21
+ | "validation_failed"
22
+ | "task_not_found"
23
+ | "goal_not_found"
24
+ | "milestone_not_found"
25
+ | "mission_not_set"
26
+ | "task_dependency_unmet"
27
+ | "task_already_assigned"
28
+ | "skill_invalid"
29
+ | "skill_not_found"
30
+ | "ide_yml_missing"
31
+ | "config_path_invalid"
32
+ | "config_validation_failed"
33
+ | "validation_assertion_not_found"
34
+ | "webhook_not_found"
35
+ | "webhook_test_failed"
36
+ | "remote_access_restart_failed"
37
+ | "shutdown_already_in_progress"
38
+ | "bad_request"
39
+ | "thread_not_found"
40
+ | "permission_request_not_found"
41
+ | "internal";
@@ -0,0 +1,36 @@
1
+ import { z } from "zod";
2
+
3
+ // ---------------------------------------------------------------------------
4
+ // PaneInfo — live tmux pane metadata (from src/command-center/discovery.ts)
5
+ // ---------------------------------------------------------------------------
6
+
7
+ export const PaneInfoSchemaZ = z.object({
8
+ id: z.string(),
9
+ index: z.number(),
10
+ title: z.string(),
11
+ currentCommand: z.string(),
12
+ width: z.number(),
13
+ height: z.number(),
14
+ active: z.boolean(),
15
+ role: z
16
+ .enum(["lead", "teammate", "planner", "validator", "researcher", "widget", "shell"])
17
+ .nullable(),
18
+ name: z.string().nullable(),
19
+ type: z.string().nullable(),
20
+ });
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // SessionOverview — minimal session listing entry
24
+ // ---------------------------------------------------------------------------
25
+
26
+ export const SessionOverviewSchemaZ = z.object({
27
+ name: z.string(),
28
+ dir: z.string(),
29
+ });
30
+
31
+ // ---------------------------------------------------------------------------
32
+ // Inferred types
33
+ // ---------------------------------------------------------------------------
34
+
35
+ export type PaneInfo = z.infer<typeof PaneInfoSchemaZ>;
36
+ export type SessionOverview = z.infer<typeof SessionOverviewSchemaZ>;