tmux-ide 2.6.0 → 2.7.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 (271) hide show
  1. package/README.md +14 -9
  2. package/bin/cli.js +1024 -519
  3. package/bin/cli.ts +63 -6
  4. package/bunfig.toml +4 -0
  5. package/package.json +18 -7
  6. package/packages/contracts/package.json +22 -0
  7. package/packages/contracts/src/__tests__/ide-config.test.ts +46 -0
  8. package/packages/contracts/src/__tests__/terminals.test.ts +87 -0
  9. package/packages/contracts/src/actions-contract.ts +310 -0
  10. package/packages/contracts/src/actions-errors.ts +41 -0
  11. package/packages/contracts/src/domain.ts +36 -0
  12. package/packages/contracts/src/ide-config.ts +170 -0
  13. package/packages/contracts/src/index.ts +24 -0
  14. package/packages/contracts/src/lib-internal/auth.ts +13 -0
  15. package/packages/contracts/src/lib-internal/hq.ts +38 -0
  16. package/packages/contracts/src/terminals.ts +116 -0
  17. package/packages/contracts/src/tmux.ts +60 -0
  18. package/packages/contracts/src/workspace.ts +67 -0
  19. package/packages/daemon/dist/agent-explain.d.ts +8 -1
  20. package/packages/daemon/dist/agent-explain.js +19 -3
  21. package/packages/daemon/dist/lib/tui-binary.d.ts +57 -0
  22. package/packages/daemon/dist/lib/tui-binary.js +130 -0
  23. package/packages/daemon/dist/widgets/explorer/breadcrumbs.d.ts +1 -1
  24. package/packages/daemon/dist/widgets/explorer/footer.d.ts +1 -1
  25. package/packages/daemon/dist/widgets/explorer/tree.d.ts +1 -1
  26. package/packages/daemon/dist/widgets/lib/help-overlay.d.ts +1 -1
  27. package/packages/daemon/dist/widgets/setup/agent-naming.d.ts +1 -1
  28. package/packages/daemon/dist/widgets/setup/config-tree.d.ts +1 -1
  29. package/packages/daemon/dist/widgets/setup/detect-panel.d.ts +1 -1
  30. package/packages/daemon/dist/widgets/setup/field-editor.d.ts +1 -1
  31. package/packages/daemon/dist/widgets/setup/footer.d.ts +1 -1
  32. package/packages/daemon/dist/widgets/setup/layout-picker.d.ts +1 -1
  33. package/packages/daemon/src/agent-explain.ts +298 -0
  34. package/packages/daemon/src/attach.ts +20 -0
  35. package/packages/daemon/src/bin.ts +4 -0
  36. package/packages/daemon/src/canonical.ts +7 -0
  37. package/packages/daemon/src/cli.ts +499 -0
  38. package/packages/daemon/src/command-center/actions/contract.ts +2 -0
  39. package/packages/daemon/src/command-center/actions/dispatcher.ts +137 -0
  40. package/packages/daemon/src/command-center/actions/errors.ts +105 -0
  41. package/packages/daemon/src/command-center/actions/handlers/_project-context.ts +30 -0
  42. package/packages/daemon/src/command-center/actions/handlers/_resolve-project.ts +78 -0
  43. package/packages/daemon/src/command-center/actions/handlers/app-set-remote-access.ts +118 -0
  44. package/packages/daemon/src/command-center/actions/handlers/config-actions.ts +113 -0
  45. package/packages/daemon/src/command-center/actions/handlers/daemon-shutdown.ts +38 -0
  46. package/packages/daemon/src/command-center/actions/handlers/project-activate.ts +30 -0
  47. package/packages/daemon/src/command-center/actions/handlers/project-launch.ts +70 -0
  48. package/packages/daemon/src/command-center/actions/handlers/project-open-terminal.ts +87 -0
  49. package/packages/daemon/src/command-center/actions/handlers/project-restart.ts +38 -0
  50. package/packages/daemon/src/command-center/actions/handlers/project-stop.ts +62 -0
  51. package/packages/daemon/src/command-center/actions/handlers/terminal-respawn.ts +119 -0
  52. package/packages/daemon/src/command-center/actions/handlers/terminal-stop.ts +35 -0
  53. package/packages/daemon/src/command-center/actions/registry.ts +149 -0
  54. package/packages/daemon/src/command-center/discovery.ts +96 -0
  55. package/packages/daemon/src/command-center/index.ts +31 -0
  56. package/packages/daemon/src/command-center/schemas.ts +85 -0
  57. package/packages/daemon/src/command-center/server.ts +1260 -0
  58. package/packages/daemon/src/command-center/ws-events.ts +316 -0
  59. package/packages/daemon/src/config.ts +549 -0
  60. package/packages/daemon/src/detect.ts +248 -0
  61. package/packages/daemon/src/doctor.ts +242 -0
  62. package/packages/daemon/src/embed.ts +5 -0
  63. package/packages/daemon/src/index.ts +12 -0
  64. package/packages/daemon/src/init.ts +211 -0
  65. package/packages/daemon/src/inspect.ts +178 -0
  66. package/packages/daemon/src/js-yaml.d.ts +10 -0
  67. package/packages/daemon/src/launch.ts +349 -0
  68. package/packages/daemon/src/lib/active-projects.ts +49 -0
  69. package/packages/daemon/src/lib/agent-discovery.ts +121 -0
  70. package/packages/daemon/src/lib/app-config.ts +427 -0
  71. package/packages/daemon/src/lib/app-settings.ts +53 -0
  72. package/packages/daemon/src/lib/auth/auth-service.ts +227 -0
  73. package/packages/daemon/src/lib/auth/middleware.ts +56 -0
  74. package/packages/daemon/src/lib/auth/types.ts +2 -0
  75. package/packages/daemon/src/lib/auth-token.ts +5 -0
  76. package/packages/daemon/src/lib/authorship.ts +280 -0
  77. package/packages/daemon/src/lib/canonical-daemon.ts +122 -0
  78. package/packages/daemon/src/lib/cli-action-bridge.ts +216 -0
  79. package/packages/daemon/src/lib/daemon-embed.ts +782 -0
  80. package/packages/daemon/src/lib/daemon-watchdog.ts +111 -0
  81. package/packages/daemon/src/lib/daemon.ts +79 -0
  82. package/packages/daemon/src/lib/dot-path.ts +17 -0
  83. package/packages/daemon/src/lib/errors.ts +67 -0
  84. package/packages/daemon/src/lib/filesystem-browser.ts +292 -0
  85. package/packages/daemon/src/lib/launch-plan.ts +90 -0
  86. package/packages/daemon/src/lib/log.ts +134 -0
  87. package/packages/daemon/src/lib/output.ts +76 -0
  88. package/packages/daemon/src/lib/project-init-runner.ts +150 -0
  89. package/packages/daemon/src/lib/project-inspect.ts +80 -0
  90. package/packages/daemon/src/lib/project-onboard.ts +149 -0
  91. package/packages/daemon/src/lib/project-probe.ts +92 -0
  92. package/packages/daemon/src/lib/project-registry.ts +296 -0
  93. package/packages/daemon/src/lib/session-monitor.ts +122 -0
  94. package/packages/daemon/src/lib/session-options.ts +100 -0
  95. package/packages/daemon/src/lib/shell.ts +8 -0
  96. package/packages/daemon/src/lib/sizes.ts +36 -0
  97. package/packages/daemon/src/lib/skill-sync.ts +155 -0
  98. package/packages/daemon/src/lib/slugify.ts +10 -0
  99. package/packages/daemon/src/lib/terminals-store.ts +125 -0
  100. package/packages/daemon/src/lib/tui-binary.ts +165 -0
  101. package/packages/daemon/src/lib/update-check.ts +298 -0
  102. package/packages/daemon/src/lib/update.ts +158 -0
  103. package/packages/daemon/src/lib/workspace-registry.ts +229 -0
  104. package/packages/daemon/src/lib/worktree.ts +289 -0
  105. package/packages/daemon/src/lib/yaml-io.ts +27 -0
  106. package/packages/daemon/src/ls.ts +40 -0
  107. package/packages/daemon/src/restart.ts +24 -0
  108. package/packages/daemon/src/restore.ts +514 -0
  109. package/packages/daemon/src/schemas/domain.ts +2 -0
  110. package/packages/daemon/src/schemas/filesystem.ts +34 -0
  111. package/packages/daemon/src/schemas/ide-config.ts +2 -0
  112. package/packages/daemon/src/schemas/index.ts +59 -0
  113. package/packages/daemon/src/schemas/inspect.ts +67 -0
  114. package/packages/daemon/src/schemas/registry.ts +55 -0
  115. package/packages/daemon/src/schemas/ws-events.ts +135 -0
  116. package/packages/daemon/src/send.ts +171 -0
  117. package/packages/daemon/src/server/README.md +15 -0
  118. package/packages/daemon/src/server/index.ts +74 -0
  119. package/packages/daemon/src/server/pty-bridge.ts +532 -0
  120. package/packages/daemon/src/server/standalone.ts +18 -0
  121. package/packages/daemon/src/server/ws-route.ts +483 -0
  122. package/packages/daemon/src/status.ts +59 -0
  123. package/packages/daemon/src/stop.ts +28 -0
  124. package/packages/daemon/src/terminal/NodePtyAdapter.ts +271 -0
  125. package/packages/daemon/src/terminal/PtyAdapter.ts +140 -0
  126. package/packages/daemon/src/terminal/README.md +92 -0
  127. package/packages/daemon/src/tui/chrome/cheatsheet.ts +260 -0
  128. package/packages/daemon/src/tui/chrome/chip.ts +30 -0
  129. package/packages/daemon/src/tui/chrome/events.ts +119 -0
  130. package/packages/daemon/src/tui/chrome/kitty-keys.ts +55 -0
  131. package/packages/daemon/src/tui/chrome/menu.ts +289 -0
  132. package/packages/daemon/src/tui/chrome/notify.ts +382 -0
  133. package/packages/daemon/src/tui/chrome/panels.ts +111 -0
  134. package/packages/daemon/src/tui/chrome/sidebar.ts +222 -0
  135. package/packages/daemon/src/tui/chrome/snapshot.ts +425 -0
  136. package/packages/daemon/src/tui/chrome/statusline.ts +595 -0
  137. package/packages/daemon/src/tui/chrome/updater.ts +510 -0
  138. package/packages/daemon/src/tui/chrome/welcome.ts +124 -0
  139. package/packages/daemon/src/tui/compiled.ts +121 -0
  140. package/packages/daemon/src/tui/detect/classify.ts +208 -0
  141. package/packages/daemon/src/tui/detect/manifest-loader.ts +193 -0
  142. package/packages/daemon/src/tui/detect/manifest.ts +199 -0
  143. package/packages/daemon/src/tui/detect/manifests.ts +354 -0
  144. package/packages/daemon/src/tui/detect/process-tree.ts +217 -0
  145. package/packages/daemon/src/tui/detect/snapshot.ts +70 -0
  146. package/packages/daemon/src/tui/integrations/claude.ts +176 -0
  147. package/packages/daemon/src/tui/integrations/offer.ts +145 -0
  148. package/packages/daemon/src/tui/main.ts +82 -0
  149. package/packages/daemon/src/tui/mirror/ack-writer.ts +77 -0
  150. package/packages/daemon/src/tui/mirror/agent-chip.ts +97 -0
  151. package/packages/daemon/src/tui/mirror/agent-rows.ts +133 -0
  152. package/packages/daemon/src/tui/mirror/app-state.ts +179 -0
  153. package/packages/daemon/src/tui/mirror/app.tsx +5265 -0
  154. package/packages/daemon/src/tui/mirror/blit.ts +186 -0
  155. package/packages/daemon/src/tui/mirror/control-client.ts +214 -0
  156. package/packages/daemon/src/tui/mirror/control.ts +97 -0
  157. package/packages/daemon/src/tui/mirror/dialog-model.ts +298 -0
  158. package/packages/daemon/src/tui/mirror/dialog-stack.ts +354 -0
  159. package/packages/daemon/src/tui/mirror/diff-model.ts +112 -0
  160. package/packages/daemon/src/tui/mirror/editor-buffer.ts +117 -0
  161. package/packages/daemon/src/tui/mirror/file-tree.ts +97 -0
  162. package/packages/daemon/src/tui/mirror/focus-border.ts +57 -0
  163. package/packages/daemon/src/tui/mirror/folder-picker.ts +124 -0
  164. package/packages/daemon/src/tui/mirror/home-model.ts +174 -0
  165. package/packages/daemon/src/tui/mirror/input-coalescer.ts +105 -0
  166. package/packages/daemon/src/tui/mirror/menu-model.ts +187 -0
  167. package/packages/daemon/src/tui/mirror/palette.ts +274 -0
  168. package/packages/daemon/src/tui/mirror/pane-mirror.ts +561 -0
  169. package/packages/daemon/src/tui/mirror/pane-surface.tsx +415 -0
  170. package/packages/daemon/src/tui/mirror/perf-tap.ts +160 -0
  171. package/packages/daemon/src/tui/mirror/resize-model.ts +85 -0
  172. package/packages/daemon/src/tui/mirror/scrollbar-model.ts +88 -0
  173. package/packages/daemon/src/tui/mirror/search-model.ts +70 -0
  174. package/packages/daemon/src/tui/mirror/selection.ts +262 -0
  175. package/packages/daemon/src/tui/mirror/session-mirror.ts +443 -0
  176. package/packages/daemon/src/tui/mirror/settings-model.ts +345 -0
  177. package/packages/daemon/src/tui/mirror/size-truth.ts +77 -0
  178. package/packages/daemon/src/tui/mirror/spans.ts +46 -0
  179. package/packages/daemon/src/tui/mirror/status-grammar.ts +32 -0
  180. package/packages/daemon/src/tui/team/CONTROL.md +50 -0
  181. package/packages/daemon/src/tui/team/entry.ts +38 -0
  182. package/packages/daemon/src/tui/team/fuzzy.ts +133 -0
  183. package/packages/daemon/src/tui/team/home.ts +170 -0
  184. package/packages/daemon/src/tui/team/index.tsx +1521 -0
  185. package/packages/daemon/src/tui/team/input.ts +34 -0
  186. package/packages/daemon/src/tui/team/keymap.ts +127 -0
  187. package/packages/daemon/src/tui/team/mouse.ts +29 -0
  188. package/packages/daemon/src/tui/team/nav.ts +31 -0
  189. package/packages/daemon/src/tui/team/preview.ts +34 -0
  190. package/packages/daemon/src/tui/team/projects.ts +191 -0
  191. package/packages/daemon/src/tui/team/report.ts +83 -0
  192. package/packages/daemon/src/tui/team/sessions.ts +433 -0
  193. package/packages/daemon/src/tui/team/tree.ts +62 -0
  194. package/packages/daemon/src/types.ts +13 -0
  195. package/packages/daemon/src/ui/index.ts +32 -0
  196. package/packages/daemon/src/ui/terminal/index.ts +9 -0
  197. package/packages/daemon/src/ui/types.ts +91 -0
  198. package/packages/daemon/src/ui/web/base.css +80 -0
  199. package/packages/daemon/src/ui/web/components/Box.tsx +59 -0
  200. package/packages/daemon/src/ui/web/components/Input.tsx +32 -0
  201. package/packages/daemon/src/ui/web/components/ScrollBox.tsx +60 -0
  202. package/packages/daemon/src/ui/web/components/Text.tsx +28 -0
  203. package/packages/daemon/src/ui/web/hooks.ts +106 -0
  204. package/packages/daemon/src/ui/web/index.ts +27 -0
  205. package/packages/daemon/src/ui/web/render.ts +77 -0
  206. package/packages/daemon/src/ui/web/utils/color.ts +27 -0
  207. package/packages/daemon/src/validate.ts +217 -0
  208. package/packages/daemon/src/widgets/changes/README.md +3 -0
  209. package/packages/daemon/src/widgets/changes/index.tsx +691 -0
  210. package/packages/daemon/src/widgets/config/README.md +3 -0
  211. package/packages/daemon/src/widgets/config/index.tsx +481 -0
  212. package/packages/daemon/src/widgets/explorer/README.md +3 -0
  213. package/packages/daemon/src/widgets/explorer/breadcrumbs.tsx +77 -0
  214. package/packages/daemon/src/widgets/explorer/footer.tsx +20 -0
  215. package/packages/daemon/src/widgets/explorer/header.tsx +23 -0
  216. package/packages/daemon/src/widgets/explorer/index.tsx +456 -0
  217. package/packages/daemon/src/widgets/explorer/tree-model.ts +103 -0
  218. package/packages/daemon/src/widgets/explorer/tree.tsx +165 -0
  219. package/packages/daemon/src/widgets/lib/config-model.ts +116 -0
  220. package/packages/daemon/src/widgets/lib/files.ts +88 -0
  221. package/packages/daemon/src/widgets/lib/git.ts +88 -0
  222. package/packages/daemon/src/widgets/lib/grammar.ts +126 -0
  223. package/packages/daemon/src/widgets/lib/help-overlay.tsx +101 -0
  224. package/packages/daemon/src/widgets/lib/pane-comms.ts +209 -0
  225. package/packages/daemon/src/widgets/lib/theme.ts +194 -0
  226. package/packages/daemon/src/widgets/lib/watcher.ts +132 -0
  227. package/packages/daemon/src/widgets/preview/README.md +3 -0
  228. package/packages/daemon/src/widgets/preview/index.tsx +416 -0
  229. package/packages/daemon/src/widgets/resolve.ts +121 -0
  230. package/packages/daemon/src/widgets/setup/README.md +3 -0
  231. package/packages/daemon/src/widgets/setup/agent-naming.tsx +112 -0
  232. package/packages/daemon/src/widgets/setup/config-tree.tsx +246 -0
  233. package/packages/daemon/src/widgets/setup/detect-panel.tsx +72 -0
  234. package/packages/daemon/src/widgets/setup/field-editor.tsx +265 -0
  235. package/packages/daemon/src/widgets/setup/footer.tsx +107 -0
  236. package/packages/daemon/src/widgets/setup/index.tsx +341 -0
  237. package/packages/daemon/src/widgets/setup/layout-picker.tsx +96 -0
  238. package/packages/daemon/src/widgets/setup/orchestrator-panel.tsx +200 -0
  239. package/packages/daemon/src/widgets/setup/review-panel.tsx +140 -0
  240. package/packages/daemon/src/widgets/setup/setup-model.ts +188 -0
  241. package/packages/daemon/src/widgets/sidebar/index.tsx +527 -0
  242. package/packages/tmux-bridge/package.json +22 -0
  243. package/packages/tmux-bridge/src/errors.ts +28 -0
  244. package/packages/tmux-bridge/src/index.ts +31 -0
  245. package/packages/tmux-bridge/src/monitor.ts +77 -0
  246. package/packages/tmux-bridge/src/panes.ts +136 -0
  247. package/packages/tmux-bridge/src/runner.test.ts +501 -0
  248. package/packages/tmux-bridge/src/runner.ts +91 -0
  249. package/packages/tmux-bridge/src/sessions.ts +126 -0
  250. package/packages/tmux-bridge/src/targeting.test.ts +107 -0
  251. package/packages/tmux-bridge/src/targeting.ts +90 -0
  252. package/scripts/build-tui.mjs +11 -4
  253. package/scripts/perf-mirror.mjs +313 -0
  254. package/scripts/postinstall.js +26 -2
  255. package/skill/SKILL.md +22 -0
  256. package/templates/AGENTS.md +14 -7
  257. package/templates/agent-team-monorepo.yml +8 -0
  258. package/templates/agent-team-nextjs.yml +8 -0
  259. package/templates/agent-team.yml +10 -0
  260. package/templates/convex.yml +2 -0
  261. package/templates/default.yml +11 -5
  262. package/templates/go.yml +4 -0
  263. package/templates/missions.yml +6 -0
  264. package/templates/nextjs.yml +4 -0
  265. package/templates/python.yml +4 -0
  266. package/templates/skills/backend.md +5 -12
  267. package/templates/skills/frontend.md +5 -12
  268. package/templates/skills/general-worker.md +5 -12
  269. package/templates/skills/researcher.md +7 -12
  270. package/templates/skills/reviewer.md +7 -16
  271. package/templates/vite.yml +4 -0
@@ -0,0 +1,499 @@
1
+ import { parseArgs } from "node:util";
2
+ import { resolve, dirname } from "node:path";
3
+ import { execFileSync } from "node:child_process";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ const __dirname = dirname(fileURLToPath(import.meta.url));
7
+ import { init } from "./init.ts";
8
+ import { ls } from "./ls.ts";
9
+ import { doctor } from "./doctor.ts";
10
+ import { status } from "./status.ts";
11
+ import { inspect } from "./inspect.ts";
12
+ import { validate } from "./validate.ts";
13
+ import { detect } from "./detect.ts";
14
+ import { config } from "./config.ts";
15
+ import { send } from "./send.ts";
16
+ import { IdeError, TmuxError } from "./lib/errors.ts";
17
+ import { printCommandError } from "./lib/output.ts";
18
+ import { getSessionName } from "./lib/yaml-io.ts";
19
+ import { attachSession } from "@tmux-ide/tmux-bridge";
20
+ import { tryDispatchAction } from "./lib/cli-action-bridge.ts";
21
+ import { startEmbeddedDaemon, type EmbeddedDaemonHandle } from "./index.ts";
22
+ import type { ActionResult } from "./command-center/actions/contract.ts";
23
+
24
+ /**
25
+ * Typed view of parseArgs values. parseArgs runs with `strict: false` so its
26
+ * declared return type widens every option to `string | boolean | undefined`,
27
+ * losing the per-option type that's already declared in the `options` config
28
+ * below. Cast `values` to this interface so call sites get the intended
29
+ * shape without sprinkling assertions everywhere.
30
+ */
31
+ interface CliFlags {
32
+ json?: boolean;
33
+ headless?: boolean;
34
+ tasks?: boolean;
35
+ fix?: boolean;
36
+ row?: string;
37
+ pane?: string;
38
+ title?: string;
39
+ command?: string;
40
+ size?: string;
41
+ write?: boolean;
42
+ template?: string;
43
+ name?: string;
44
+ verbose?: boolean;
45
+ help?: boolean;
46
+ version?: boolean;
47
+ description?: string;
48
+ acceptance?: string;
49
+ priority?: string;
50
+ status?: string;
51
+ assign?: string;
52
+ goal?: string;
53
+ tags?: string;
54
+ proof?: string;
55
+ depends?: string;
56
+ pr?: boolean;
57
+ specialty?: string;
58
+ milestone?: string;
59
+ fulfills?: string;
60
+ summary?: string;
61
+ sequence?: string;
62
+ evidence?: string;
63
+ port?: string;
64
+ provider?: string;
65
+ domain?: string;
66
+ authtoken?: string;
67
+ edit?: boolean;
68
+ wizard?: boolean;
69
+ url?: string;
70
+ "hq-url"?: string;
71
+ to?: string;
72
+ "no-enter"?: boolean;
73
+ }
74
+
75
+ export async function main(): Promise<void> {
76
+ const { positionals, values: rawValues } = parseArgs({
77
+ allowPositionals: true,
78
+ strict: false,
79
+ options: {
80
+ json: { type: "boolean" },
81
+ headless: { type: "boolean" },
82
+ tasks: { type: "boolean" },
83
+ fix: { type: "boolean" },
84
+ row: { type: "string" },
85
+ pane: { type: "string" },
86
+ title: { type: "string" },
87
+ command: { type: "string" },
88
+ size: { type: "string" },
89
+ write: { type: "boolean" },
90
+ template: { type: "string" },
91
+ name: { type: "string" },
92
+ verbose: { type: "boolean", default: false },
93
+ help: { type: "boolean", short: "h" },
94
+ version: { type: "boolean", short: "v" },
95
+ // task command flags
96
+ description: { type: "string", short: "d" },
97
+ acceptance: { type: "string" },
98
+ priority: { type: "string", short: "p" },
99
+ status: { type: "string", short: "s" },
100
+ assign: { type: "string", short: "a" },
101
+ goal: { type: "string", short: "g" },
102
+ tags: { type: "string", short: "t" },
103
+ proof: { type: "string" },
104
+ depends: { type: "string" },
105
+ pr: { type: "boolean" },
106
+ specialty: { type: "string" },
107
+ milestone: { type: "string" },
108
+ fulfills: { type: "string" },
109
+ summary: { type: "string" },
110
+ sequence: { type: "string" },
111
+ evidence: { type: "string" },
112
+ port: { type: "string" },
113
+ // tunnel command flags
114
+ provider: { type: "string" },
115
+ domain: { type: "string" },
116
+ authtoken: { type: "string" },
117
+ // setup command flags
118
+ edit: { type: "boolean" },
119
+ wizard: { type: "boolean" },
120
+ // remote command flags
121
+ url: { type: "string" },
122
+ "hq-url": { type: "string" },
123
+ // send command flags
124
+ to: { type: "string" },
125
+ "no-enter": { type: "boolean" },
126
+ },
127
+ });
128
+ const values = rawValues as CliFlags;
129
+
130
+ const knownCommands = new Set([
131
+ "start",
132
+ "init",
133
+ "stop",
134
+ "attach",
135
+ "restart",
136
+ "ls",
137
+ "doctor",
138
+ "status",
139
+ "inspect",
140
+ "validate",
141
+ "detect",
142
+ "config",
143
+ "setup",
144
+ "send",
145
+ "settings",
146
+ "command-center",
147
+ "server",
148
+ "help",
149
+ ]);
150
+
151
+ // --version / -v
152
+ if (values.version) {
153
+ const pkg = await import("../../../package.json");
154
+ console.log(`tmux-ide v${pkg.default.version}`);
155
+ process.exit(0);
156
+ }
157
+
158
+ if (values.verbose) {
159
+ globalThis.__tmuxIdeVerbose = true;
160
+ }
161
+
162
+ const ALIASES: Record<string, string> = {};
163
+ const firstPositional = positionals[0];
164
+ const resolved = firstPositional ? (ALIASES[firstPositional] ?? firstPositional) : undefined;
165
+ const hasKnownCommand = resolved ? knownCommands.has(resolved) : false;
166
+ const command = hasKnownCommand ? resolved : "start";
167
+ const startTargetDir = hasKnownCommand ? positionals[1] : firstPositional;
168
+ const json = values.json ?? false;
169
+
170
+ const noColor = "NO_COLOR" in process.env;
171
+ const bold = (s: string) => (noColor ? s : `\x1b[1m${s}\x1b[22m`);
172
+ const cyan = (s: string) => (noColor ? s : `\x1b[36m${s}\x1b[39m`);
173
+ const dim = (s: string) => (noColor ? s : `\x1b[2m${s}\x1b[22m`);
174
+
175
+ if (values.help) {
176
+ printHelp();
177
+ process.exit(0);
178
+ }
179
+
180
+ function printHelp() {
181
+ console.log(`${bold("tmux-ide")} — Terminal IDE powered by tmux
182
+
183
+ ${bold("Usage:")}
184
+ ${cyan("tmux-ide")} ${dim("Launch IDE from ide.yml")}
185
+ ${cyan("tmux-ide --headless")} ${dim("Start the canonical daemon without the app")}
186
+ ${cyan("tmux-ide <path>")} ${dim("Launch from a specific directory")}
187
+ ${cyan("tmux-ide setup")} ${dim("Interactive TUI setup wizard")}
188
+ ${cyan("tmux-ide setup --edit")} ${dim("Open config tree editor")}
189
+ ${cyan("tmux-ide settings")} ${dim("Interactive TUI config manager")}
190
+ ${cyan("tmux-ide init")} [--template] ${dim("Scaffold a new ide.yml (auto-detects stack)")}
191
+ ${cyan("tmux-ide stop")} ${dim("Kill the current IDE session")}
192
+ ${cyan("tmux-ide restart")} ${dim("Stop and relaunch the IDE session")}
193
+ ${cyan("tmux-ide attach")} ${dim("Reattach to a running session")}
194
+ ${cyan("tmux-ide ls")} ${dim("List all tmux sessions")}
195
+ ${cyan("tmux-ide status")} [--json] ${dim("Show session status")}
196
+ ${cyan("tmux-ide inspect")} [--json] ${dim("Show effective config and runtime state")}
197
+ ${cyan("tmux-ide doctor")} ${dim("Check system requirements")}
198
+ ${cyan("tmux-ide validate")} [--json] ${dim("Validate ide.yml")}
199
+ ${cyan("tmux-ide detect")} [--json] ${dim("Detect project stack")}
200
+ ${cyan("tmux-ide detect --write")} ${dim("Detect and write ide.yml")}
201
+ ${cyan("tmux-ide config")} [--json] ${dim("Dump config as JSON")}
202
+ ${cyan("tmux-ide config set")} <path> <value>
203
+ ${cyan("tmux-ide config add-pane")} --row <N> --title <T> [--command <C>]
204
+ ${cyan("tmux-ide config remove-pane")} --row <N> --pane <M>
205
+ ${cyan("tmux-ide config add-row")} [--size <percent>]
206
+ ${cyan("tmux-ide config enable-team")} [--name <N>] ${dim("Enable agent teams")}
207
+ ${cyan("tmux-ide config disable-team")} ${dim("Disable agent teams")}
208
+
209
+ ${bold("Pane Messaging:")}
210
+ ${cyan("tmux-ide send")} <target> <message> ${dim("Send message to a pane")}
211
+ ${cyan("tmux-ide send")} --to <name> <message> ${dim("Target by name, title, role, or ID")}
212
+ ${cyan("tmux-ide send")} <target> --no-enter msg ${dim("Send text without pressing Enter")}
213
+
214
+ ${bold("Server:")}
215
+ ${cyan("tmux-ide command-center")} [--port N] ${dim("Start the command-center HTTP API")}
216
+ ${cyan("tmux-ide server")} [--port N] ${dim("Start HTTP + PTY WebSocket server")}
217
+
218
+ ${bold("Flags:")}
219
+ ${cyan("--json")} ${dim("Output as JSON (all commands)")}
220
+ ${cyan("--headless")} ${dim("Run the canonical daemon in this process")}
221
+ ${cyan("--template <name>")} ${dim("Use specific template for init")}
222
+ ${cyan("--write")} ${dim("Write detected config to ide.yml")}
223
+ ${cyan("--verbose")} ${dim("Log all tmux commands (or set TMUX_IDE_DEBUG=1)")}
224
+ ${cyan("-h, --help")} ${dim("Show usage")}
225
+ ${cyan("-v, --version")} ${dim("Show version number")}`);
226
+ }
227
+
228
+ function resolveProjectName(targetDir: string | undefined): string {
229
+ return getSessionName(resolve(targetDir ?? ".")).name;
230
+ }
231
+
232
+ async function dispatchProjectLaunch(
233
+ projectName: string,
234
+ cwd: string,
235
+ ): Promise<ActionResult<"project.launch">> {
236
+ const result = await tryDispatchAction("project.launch", { name: projectName }, { cwd });
237
+ if (!result) {
238
+ throw new IdeError("Canonical daemon is not available", {
239
+ code: "DAEMON_UNAVAILABLE",
240
+ exitCode: 1,
241
+ });
242
+ }
243
+ return result;
244
+ }
245
+
246
+ async function dispatchProjectOpenTerminal(
247
+ projectName: string,
248
+ cwd: string,
249
+ ): Promise<ActionResult<"project.openTerminal">> {
250
+ const result = await tryDispatchAction("project.openTerminal", { name: projectName }, { cwd });
251
+ if (!result) {
252
+ throw new IdeError("Canonical daemon is not available", {
253
+ code: "DAEMON_UNAVAILABLE",
254
+ exitCode: 1,
255
+ });
256
+ }
257
+ return result;
258
+ }
259
+
260
+ async function dispatchProjectStop(
261
+ projectName: string,
262
+ cwd: string,
263
+ ): Promise<ActionResult<"project.stop">> {
264
+ const result = await tryDispatchAction("project.stop", { name: projectName }, { cwd });
265
+ if (!result) {
266
+ throw new IdeError("Canonical daemon is not available", {
267
+ code: "DAEMON_UNAVAILABLE",
268
+ exitCode: 1,
269
+ });
270
+ }
271
+ return result;
272
+ }
273
+
274
+ async function dispatchProjectRestart(
275
+ projectName: string,
276
+ cwd: string,
277
+ ): Promise<ActionResult<"project.restart">> {
278
+ const result = await tryDispatchAction("project.restart", { name: projectName }, { cwd });
279
+ if (!result) {
280
+ throw new IdeError("Canonical daemon is not available", {
281
+ code: "DAEMON_UNAVAILABLE",
282
+ exitCode: 1,
283
+ });
284
+ }
285
+ return result;
286
+ }
287
+
288
+ async function runHeadlessDaemon(): Promise<void> {
289
+ let handle: EmbeddedDaemonHandle | null = null;
290
+ let stopping = false;
291
+ const stop = async () => {
292
+ if (stopping) return;
293
+ stopping = true;
294
+ if (handle) await handle.stop();
295
+ };
296
+ process.on("SIGINT", () => {
297
+ void stop().finally(() => process.exit(0));
298
+ });
299
+ process.on("SIGTERM", () => {
300
+ void stop().finally(() => process.exit(0));
301
+ });
302
+
303
+ handle = await startEmbeddedDaemon({ bindHostname: "127.0.0.1" });
304
+ console.log(`Canonical daemon: ${handle.apiBaseUrl}`);
305
+ await new Promise<void>(() => undefined);
306
+ }
307
+
308
+ try {
309
+ if (values.headless) {
310
+ await runHeadlessDaemon();
311
+ } else
312
+ switch (command) {
313
+ case "start":
314
+ {
315
+ const cwd = resolve(startTargetDir ?? ".");
316
+ const projectName = resolveProjectName(startTargetDir);
317
+ const result = await dispatchProjectLaunch(projectName, cwd);
318
+ if (json) {
319
+ console.log(JSON.stringify(result));
320
+ } else if (result.started) {
321
+ console.log(`Started "${result.sessionName}".`);
322
+ } else {
323
+ console.log(`Session "${result.sessionName}" is already running. Attaching...`);
324
+ }
325
+ attachSession(result.sessionName);
326
+ }
327
+ break;
328
+
329
+ case "init":
330
+ await init({ template: values.template, json });
331
+ break;
332
+
333
+ case "stop":
334
+ {
335
+ const cwd = resolve(positionals[1] ?? ".");
336
+ const projectName = resolveProjectName(positionals[1]);
337
+ const result = await dispatchProjectStop(projectName, cwd);
338
+ if (json) console.log(JSON.stringify(result));
339
+ else
340
+ console.log(
341
+ result.stopped ? `Stopped "${result.sessionName}".` : "No session running.",
342
+ );
343
+ }
344
+ break;
345
+
346
+ case "attach":
347
+ {
348
+ const cwd = resolve(positionals[1] ?? ".");
349
+ const projectName = resolveProjectName(positionals[1]);
350
+ const result = await dispatchProjectOpenTerminal(projectName, cwd);
351
+ if (json) console.log(JSON.stringify(result));
352
+ attachSession(result.sessionName);
353
+ }
354
+ break;
355
+
356
+ case "restart":
357
+ {
358
+ const cwd = resolve(positionals[1] ?? ".");
359
+ const projectName = resolveProjectName(positionals[1]);
360
+ const result = await dispatchProjectRestart(projectName, cwd);
361
+ if (json) console.log(JSON.stringify(result));
362
+ else console.log(`Restarted "${result.sessionName}".`);
363
+ attachSession(result.sessionName);
364
+ }
365
+ break;
366
+
367
+ case "ls":
368
+ await ls({ json });
369
+ break;
370
+
371
+ case "doctor":
372
+ await doctor({ json });
373
+ break;
374
+
375
+ case "status":
376
+ await status(positionals[1], { json });
377
+ break;
378
+
379
+ case "inspect":
380
+ await inspect(positionals[1], { json });
381
+ break;
382
+
383
+ case "validate":
384
+ await validate(positionals[1], { json });
385
+ break;
386
+
387
+ case "detect":
388
+ await detect(positionals[1], { json, write: values.write });
389
+ break;
390
+
391
+ case "config": {
392
+ const sub = positionals[1];
393
+ let action = "dump";
394
+ let configArgs: string[] = [];
395
+
396
+ if (sub === "set") {
397
+ action = "set";
398
+ configArgs = positionals.slice(2);
399
+ } else if (sub === "add-pane") {
400
+ action = "add-pane";
401
+ configArgs = [];
402
+ if (values.row !== undefined) configArgs.push("--row", values.row);
403
+ if (values.title !== undefined) configArgs.push("--title", values.title);
404
+ if (values.command !== undefined) configArgs.push("--command", values.command);
405
+ if (values.size !== undefined) configArgs.push("--size", values.size);
406
+ } else if (sub === "remove-pane") {
407
+ action = "remove-pane";
408
+ configArgs = [];
409
+ if (values.row !== undefined) configArgs.push("--row", values.row);
410
+ if (values.pane !== undefined) configArgs.push("--pane", values.pane);
411
+ } else if (sub === "add-row") {
412
+ action = "add-row";
413
+ configArgs = [];
414
+ if (values.size !== undefined) configArgs.push("--size", values.size);
415
+ } else if (sub === "enable-team") {
416
+ action = "enable-team";
417
+ configArgs = [];
418
+ if (values.name !== undefined) configArgs.push("--name", values.name);
419
+ } else if (sub === "disable-team") {
420
+ action = "disable-team";
421
+ configArgs = [];
422
+ } else if (sub === "edit") {
423
+ const scriptPath = resolve(__dirname, "./widgets/setup/index.tsx");
424
+ execFileSync("bun", [scriptPath, "--dir=" + resolve(startTargetDir || "."), "--edit"], {
425
+ stdio: "inherit",
426
+ });
427
+ break;
428
+ }
429
+
430
+ await config(null, { json, action, args: configArgs });
431
+ break;
432
+ }
433
+
434
+ case "setup": {
435
+ const scriptPath = resolve(__dirname, "./widgets/setup/index.tsx");
436
+ const setupArgs = [scriptPath, "--dir=" + resolve(startTargetDir || ".")];
437
+ if (positionals[1] === "--edit" || values.edit) setupArgs.push("--edit");
438
+ if (positionals[1] === "--wizard" || values.wizard) setupArgs.push("--wizard");
439
+ execFileSync("bun", setupArgs, { stdio: "inherit" });
440
+ break;
441
+ }
442
+
443
+ case "send": {
444
+ const target = values.to ?? positionals[1];
445
+ const messageStart = values.to ? 1 : 2;
446
+ let message = positionals.slice(messageStart).join(" ");
447
+ if (!message && !process.stdin.isTTY) {
448
+ const { readFileSync } = await import("node:fs");
449
+ message = readFileSync(0, "utf-8").trim();
450
+ }
451
+ await send(undefined, { json, to: target, message, noEnter: values["no-enter"] });
452
+ break;
453
+ }
454
+
455
+ case "settings": {
456
+ const scriptPath = resolve(__dirname, "./widgets/config/index.tsx");
457
+ execFileSync("bun", [scriptPath, "--dir=" + resolve(startTargetDir || ".")], {
458
+ stdio: "inherit",
459
+ });
460
+ break;
461
+ }
462
+
463
+ case "command-center": {
464
+ const { startCommandCenter } = await import("./command-center/index.ts");
465
+ await startCommandCenter({ port: parseInt(values.port ?? "4000") });
466
+ break;
467
+ }
468
+
469
+ case "server": {
470
+ if ("bun" in process.versions) {
471
+ const scriptPath = resolve(__dirname, "./server/standalone.ts");
472
+ const serverArgs = ["--experimental-strip-types", scriptPath];
473
+ if (values.port) serverArgs.push("--port", values.port);
474
+ execFileSync("node", serverArgs, { stdio: "inherit" });
475
+ } else {
476
+ const { start } = await import("./server/index.ts");
477
+ await start(values.port ? parseInt(values.port, 10) : undefined);
478
+ }
479
+ break;
480
+ }
481
+
482
+ case "help":
483
+ printHelp();
484
+ break;
485
+
486
+ default:
487
+ throw new IdeError(`Unknown command: ${command}\nRun "tmux-ide help" for usage.`, {
488
+ code: "USAGE",
489
+ exitCode: 1,
490
+ });
491
+ }
492
+ } catch (error) {
493
+ if (error instanceof IdeError || error instanceof TmuxError) {
494
+ printCommandError(error, { json });
495
+ } else {
496
+ throw error;
497
+ }
498
+ }
499
+ }
@@ -0,0 +1,2 @@
1
+ // Action contract moved to @tmux-ide/contracts (T059). Re-export shim.
2
+ export * from "@tmux-ide/contracts";
@@ -0,0 +1,137 @@
1
+ /**
2
+ * v2 action dispatcher — single Hono route that resolves an action name
3
+ * against the registry, parses input + output with Zod, runs the handler,
4
+ * and broadcasts an `action.complete` WS frame on success.
5
+ *
6
+ * Endpoint: `POST /api/v2/action/:name`
7
+ *
8
+ * Wire envelope:
9
+ * 200 OK { ok: true, result }
10
+ * 200 OK { ok: false, error: { code, message, details? } } (typed app error)
11
+ * 400 malformed JSON body
12
+ * 404 unknown action name (transport-level — name isn't in the registry)
13
+ *
14
+ * `ok: false` is intentionally HTTP 200: it represents a typed application
15
+ * outcome the client is expected to handle. HTTP 4xx/5xx remain reserved
16
+ * for transport-level failures.
17
+ */
18
+
19
+ import type { Context } from "hono";
20
+ import { ZodError } from "zod";
21
+ import { isActionName, type ActionErrorEnvelope, type ActionName } from "./contract.ts";
22
+ import { ActionError, wrapInternalError } from "./errors.ts";
23
+ import { getLooseActionEntry } from "./registry.ts";
24
+ import { broadcastActionComplete } from "../ws-events.ts";
25
+
26
+ export interface DispatcherDeps {
27
+ /** Override the WS broadcaster (tests / non-default daemons). */
28
+ broadcast?: (name: string, result: unknown) => void;
29
+ }
30
+
31
+ interface DispatchOk {
32
+ ok: true;
33
+ result: unknown;
34
+ }
35
+
36
+ function errorEnvelope(err: ActionError): ActionErrorEnvelope {
37
+ return { ok: false, error: err.toEnvelope() };
38
+ }
39
+
40
+ function zodErrorEnvelope(err: ZodError): ActionErrorEnvelope {
41
+ return {
42
+ ok: false,
43
+ error: {
44
+ code: "validation_failed",
45
+ message: "Input failed schema validation",
46
+ details: { issues: err.issues },
47
+ },
48
+ };
49
+ }
50
+
51
+ function outputZodErrorEnvelope(err: ZodError): ActionErrorEnvelope {
52
+ // A handler returned a value that does not conform to its declared output
53
+ // schema — log loudly and surface as `internal`. The dashboard treats
54
+ // this the same as any other server bug.
55
+ console.error("[actions] handler output failed schema validation", err.issues);
56
+ return {
57
+ ok: false,
58
+ error: {
59
+ code: "internal",
60
+ message: "Handler returned an invalid result",
61
+ details: { issues: err.issues },
62
+ },
63
+ };
64
+ }
65
+
66
+ /**
67
+ * Build the Hono handler. Exposed as a factory so tests can inject a
68
+ * broadcaster and assert on the WS event without mounting a real server.
69
+ */
70
+ export function createActionDispatcher(deps: DispatcherDeps = {}) {
71
+ const broadcast = deps.broadcast ?? broadcastActionComplete;
72
+
73
+ return async function dispatcher(c: Context): Promise<Response> {
74
+ const name = c.req.param("name");
75
+ if (!name || !isActionName(name)) {
76
+ return c.json(
77
+ {
78
+ ok: false,
79
+ error: {
80
+ code: "validation_failed",
81
+ message: `Unknown action: ${name}`,
82
+ details: { name },
83
+ },
84
+ } satisfies ActionErrorEnvelope,
85
+ 404,
86
+ );
87
+ }
88
+
89
+ let body: unknown;
90
+ try {
91
+ body = await c.req.json();
92
+ } catch (err) {
93
+ // Malformed JSON is a transport problem, not an app outcome.
94
+ return c.json(
95
+ {
96
+ ok: false,
97
+ error: {
98
+ code: "validation_failed",
99
+ message: `Invalid JSON body: ${(err as Error).message ?? String(err)}`,
100
+ },
101
+ } satisfies ActionErrorEnvelope,
102
+ 400,
103
+ );
104
+ }
105
+
106
+ const actionName: ActionName = name;
107
+ const entry = getLooseActionEntry(actionName);
108
+
109
+ const inputParsed = entry.inputSchema.safeParse(body);
110
+ if (!inputParsed.success) {
111
+ return c.json(zodErrorEnvelope(inputParsed.error) satisfies ActionErrorEnvelope, 200);
112
+ }
113
+
114
+ let result: unknown;
115
+ try {
116
+ result = await entry.handler(inputParsed.data);
117
+ } catch (err) {
118
+ const wrapped = wrapInternalError(err);
119
+ return c.json(errorEnvelope(wrapped) satisfies ActionErrorEnvelope, 200);
120
+ }
121
+
122
+ const outputParsed = entry.resultSchema.safeParse(result);
123
+ if (!outputParsed.success) {
124
+ return c.json(outputZodErrorEnvelope(outputParsed.error) satisfies ActionErrorEnvelope, 200);
125
+ }
126
+
127
+ // Fire-and-forget: subscribers learn about the success via the WS bus.
128
+ try {
129
+ broadcast(actionName, outputParsed.data);
130
+ } catch (err) {
131
+ // Broadcast failure must not turn a successful action into a failure.
132
+ console.error("[actions] broadcast failed:", err);
133
+ }
134
+
135
+ return c.json({ ok: true, result: outputParsed.data } satisfies DispatchOk, 200);
136
+ };
137
+ }