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,57 @@
1
+ /** The `<os>-<arch>` tags we publish a prebuilt TUI binary for. */
2
+ export type TuiPlatformTag = "darwin-arm64" | "darwin-x64" | "linux-x64" | "linux-arm64";
3
+ /** The GitHub repo the release assets live under. */
4
+ export declare const RELEASE_REPO = "wavyrai/tmux-ide";
5
+ /** A downloaded binary smaller than this is treated as corrupt/truncated. */
6
+ export declare const MIN_TUI_BINARY_BYTES: number;
7
+ /**
8
+ * PURE — map a Node `process.platform`/`process.arch` pair to the release tag,
9
+ * or null when we don't publish a binary for it (e.g. windows, freebsd) so the
10
+ * caller can fall back to the "install bun" message.
11
+ */
12
+ export declare function tuiPlatformTag(platform?: NodeJS.Platform, arch?: string): TuiPlatformTag | null;
13
+ /** PURE — the `bun build --compile --target` flag for a tag (`bun-<tag>`). */
14
+ export declare function bunTargetForTag(tag: TuiPlatformTag): string;
15
+ /** PURE — the release asset filename for a tag (gzip-compressed binary). */
16
+ export declare function releaseAssetName(tag: TuiPlatformTag): string;
17
+ /** PURE — strip a leading `v` from a version string; `2.6.1` and `v2.6.1` both → `2.6.1`. */
18
+ export declare function normalizeVersion(version: string): string;
19
+ /**
20
+ * PURE — the GitHub download URL for a platform's asset at a given version:
21
+ * `https://github.com/<repo>/releases/download/v<version>/tmux-ide-tui-<tag>.gz`.
22
+ */
23
+ export declare function releaseAssetUrl(version: string, tag: TuiPlatformTag): string;
24
+ /**
25
+ * PURE — where a downloaded binary lives: `<home>/bin/tmux-ide-tui-<tag>-<version>`.
26
+ * The version is stamped INTO the name so a `tmux-ide update` to a new version
27
+ * misses the old download and re-fetches (rather than launching a stale binary).
28
+ */
29
+ export declare function downloadedTuiPath(home: string, tag: TuiPlatformTag, version: string): string;
30
+ /**
31
+ * io — the tmux-ide state home (`TMUX_IDE_HOME` override, else `~/.tmux-ide`),
32
+ * the same resolution the update-check cache and welcome marker use.
33
+ */
34
+ export declare function tuiStateHome(): string;
35
+ /**
36
+ * io — locate a previously downloaded per-platform binary for THIS version and
37
+ * platform, or null. Feeds the resolution order in `tui/compiled.ts` after the
38
+ * shipped/local compiled binary and before the honest "unavailable" error.
39
+ */
40
+ export declare function findDownloadedTui(version?: string): string | null;
41
+ /**
42
+ * io — download, verify, and install the per-platform TUI binary for the running
43
+ * version. Fetches the gzip asset, inflates it, rejects anything under
44
+ * {@link MIN_TUI_BINARY_BYTES} (a truncated download or an HTML error page),
45
+ * writes it `0o755`, and atomically renames it into place (temp-in-same-dir →
46
+ * rename, so a crashed download never leaves a half-written executable).
47
+ *
48
+ * Throws with an actionable message on an unsupported platform or a failed
49
+ * fetch. Returns the installed path and byte size.
50
+ */
51
+ export declare function downloadTuiBinary(opts?: {
52
+ version?: string;
53
+ log?: (msg: string) => void;
54
+ }): Promise<{
55
+ path: string;
56
+ bytes: number;
57
+ }>;
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Per-platform TUI binary: the runtime-download fallback that lets a clean
3
+ * `npm i -g tmux-ide` run the full OpenTUI/Solid cockpit WITHOUT `bun`.
4
+ *
5
+ * The dev checkout runs the `.tsx` surfaces via bun; an npm install with no bun
6
+ * needs a self-contained binary. We do NOT bundle that ~70MB blob in the npm
7
+ * tarball (a surprise on every install) — instead the release workflow
8
+ * (`.github/workflows/release-binaries.yml`) cross-compiles one per platform and
9
+ * uploads them as GitHub release assets, and this module downloads the right one
10
+ * on demand (explicit `tmux-ide update --tui-binary`, or a consented first run).
11
+ *
12
+ * The mapping/URL/path helpers are PURE (unit-tested); {@link downloadTuiBinary}
13
+ * and {@link findDownloadedTui} are the thin io that fetch and probe.
14
+ */
15
+ import { chmodSync, existsSync, mkdirSync, renameSync, writeFileSync } from "node:fs";
16
+ import { homedir } from "node:os";
17
+ import { dirname, join } from "node:path";
18
+ import { gunzipSync } from "node:zlib";
19
+ import { getCurrentVersion } from "./update-check.js";
20
+ /** The GitHub repo the release assets live under. */
21
+ export const RELEASE_REPO = "wavyrai/tmux-ide";
22
+ /** A downloaded binary smaller than this is treated as corrupt/truncated. */
23
+ export const MIN_TUI_BINARY_BYTES = 10 * 1024 * 1024;
24
+ const SUPPORTED = {
25
+ "darwin-arm64": "darwin-arm64",
26
+ "darwin-x64": "darwin-x64",
27
+ "linux-x64": "linux-x64",
28
+ "linux-arm64": "linux-arm64",
29
+ };
30
+ // ---------------------------------------------------------------------------
31
+ // Pure
32
+ // ---------------------------------------------------------------------------
33
+ /**
34
+ * PURE — map a Node `process.platform`/`process.arch` pair to the release tag,
35
+ * or null when we don't publish a binary for it (e.g. windows, freebsd) so the
36
+ * caller can fall back to the "install bun" message.
37
+ */
38
+ export function tuiPlatformTag(platform = process.platform, arch = process.arch) {
39
+ return SUPPORTED[`${platform}-${arch}`] ?? null;
40
+ }
41
+ /** PURE — the `bun build --compile --target` flag for a tag (`bun-<tag>`). */
42
+ export function bunTargetForTag(tag) {
43
+ return `bun-${tag}`;
44
+ }
45
+ /** PURE — the release asset filename for a tag (gzip-compressed binary). */
46
+ export function releaseAssetName(tag) {
47
+ return `tmux-ide-tui-${tag}.gz`;
48
+ }
49
+ /** PURE — strip a leading `v` from a version string; `2.6.1` and `v2.6.1` both → `2.6.1`. */
50
+ export function normalizeVersion(version) {
51
+ return version.startsWith("v") ? version.slice(1) : version;
52
+ }
53
+ /**
54
+ * PURE — the GitHub download URL for a platform's asset at a given version:
55
+ * `https://github.com/<repo>/releases/download/v<version>/tmux-ide-tui-<tag>.gz`.
56
+ */
57
+ export function releaseAssetUrl(version, tag) {
58
+ return `https://github.com/${RELEASE_REPO}/releases/download/v${normalizeVersion(version)}/${releaseAssetName(tag)}`;
59
+ }
60
+ /**
61
+ * PURE — where a downloaded binary lives: `<home>/bin/tmux-ide-tui-<tag>-<version>`.
62
+ * The version is stamped INTO the name so a `tmux-ide update` to a new version
63
+ * misses the old download and re-fetches (rather than launching a stale binary).
64
+ */
65
+ export function downloadedTuiPath(home, tag, version) {
66
+ return join(home, "bin", `tmux-ide-tui-${tag}-${normalizeVersion(version)}`);
67
+ }
68
+ // ---------------------------------------------------------------------------
69
+ // io
70
+ // ---------------------------------------------------------------------------
71
+ /**
72
+ * io — the tmux-ide state home (`TMUX_IDE_HOME` override, else `~/.tmux-ide`),
73
+ * the same resolution the update-check cache and welcome marker use.
74
+ */
75
+ export function tuiStateHome() {
76
+ return process.env.TMUX_IDE_HOME ?? join(homedir(), ".tmux-ide");
77
+ }
78
+ /**
79
+ * io — locate a previously downloaded per-platform binary for THIS version and
80
+ * platform, or null. Feeds the resolution order in `tui/compiled.ts` after the
81
+ * shipped/local compiled binary and before the honest "unavailable" error.
82
+ */
83
+ export function findDownloadedTui(version = getCurrentVersion()) {
84
+ const tag = tuiPlatformTag();
85
+ if (!tag)
86
+ return null;
87
+ const path = downloadedTuiPath(tuiStateHome(), tag, version);
88
+ return existsSync(path) ? path : null;
89
+ }
90
+ /**
91
+ * io — download, verify, and install the per-platform TUI binary for the running
92
+ * version. Fetches the gzip asset, inflates it, rejects anything under
93
+ * {@link MIN_TUI_BINARY_BYTES} (a truncated download or an HTML error page),
94
+ * writes it `0o755`, and atomically renames it into place (temp-in-same-dir →
95
+ * rename, so a crashed download never leaves a half-written executable).
96
+ *
97
+ * Throws with an actionable message on an unsupported platform or a failed
98
+ * fetch. Returns the installed path and byte size.
99
+ */
100
+ export async function downloadTuiBinary(opts = {}) {
101
+ const log = opts.log ?? (() => { });
102
+ const version = normalizeVersion(opts.version ?? getCurrentVersion());
103
+ const tag = tuiPlatformTag();
104
+ if (!tag) {
105
+ throw new Error(`no prebuilt TUI binary is published for ${process.platform}-${process.arch} — ` +
106
+ `install bun (https://bun.sh) to run the TUI surfaces from source instead`);
107
+ }
108
+ const url = releaseAssetUrl(version, tag);
109
+ const dest = downloadedTuiPath(tuiStateHome(), tag, version);
110
+ mkdirSync(dirname(dest), { recursive: true });
111
+ log(`downloading ${url}`);
112
+ const res = await fetch(url);
113
+ if (!res.ok) {
114
+ throw new Error(`could not download the TUI binary (${url} → HTTP ${res.status} ${res.statusText}). ` +
115
+ `Check that release v${version} exists and published its assets.`);
116
+ }
117
+ const gz = Buffer.from(await res.arrayBuffer());
118
+ const bin = gunzipSync(gz);
119
+ if (bin.byteLength < MIN_TUI_BINARY_BYTES) {
120
+ throw new Error(`the downloaded TUI binary is only ${bin.byteLength} bytes (expected >10MB) — ` +
121
+ `treating it as corrupt and leaving the previous binary (if any) in place`);
122
+ }
123
+ const tmp = `${dest}.${process.pid}.tmp`;
124
+ writeFileSync(tmp, bin, { mode: 0o755 });
125
+ chmodSync(tmp, 0o755);
126
+ renameSync(tmp, dest);
127
+ const mb = (bin.byteLength / 1024 / 1024).toFixed(1);
128
+ log(`installed ${dest} (${mb} MB)`);
129
+ return { path: dest, bytes: bin.byteLength };
130
+ }
@@ -6,5 +6,5 @@ interface BreadcrumbsProps {
6
6
  theme: WidgetTheme;
7
7
  onNavigate: (absolutePath: string) => void;
8
8
  }
9
- export declare function Breadcrumbs(props: BreadcrumbsProps): any;
9
+ export declare function Breadcrumbs(props: BreadcrumbsProps): import("solid-js").JSX.Element;
10
10
  export {};
@@ -2,5 +2,5 @@ import type { WidgetTheme } from "../lib/theme.ts";
2
2
  interface FooterProps {
3
3
  theme: WidgetTheme;
4
4
  }
5
- export declare function Footer(props: FooterProps): any;
5
+ export declare function Footer(props: FooterProps): import("solid-js").JSX.Element;
6
6
  export {};
@@ -9,5 +9,5 @@ interface FileTreeProps {
9
9
  onActivate: (node: TreeNode) => void;
10
10
  onInputModeChange: (mode: "keyboard" | "mouse") => void;
11
11
  }
12
- export declare function FileTree(props: FileTreeProps): any;
12
+ export declare function FileTree(props: FileTreeProps): import("solid-js").JSX.Element;
13
13
  export {};
@@ -16,4 +16,4 @@ export interface HelpOverlayProps {
16
16
  * the surface's own keys. Replaces the surface body while `?` is held open;
17
17
  * `esc`/`q`/`?` close it.
18
18
  */
19
- export declare function HelpOverlay(props: HelpOverlayProps): any;
19
+ export declare function HelpOverlay(props: HelpOverlayProps): import("solid-js").JSX.Element;
@@ -5,5 +5,5 @@ interface AgentNamingProps {
5
5
  onContinue: (names: string[]) => void;
6
6
  theme: WidgetTheme;
7
7
  }
8
- export declare function AgentNaming(props: AgentNamingProps): any;
8
+ export declare function AgentNaming(props: AgentNamingProps): import("solid-js").JSX.Element;
9
9
  export {};
@@ -9,5 +9,5 @@ interface ConfigTreeProps {
9
9
  onConfigChange: (config: IdeConfig) => void;
10
10
  theme: WidgetTheme;
11
11
  }
12
- export declare function ConfigTree(props: ConfigTreeProps): any;
12
+ export declare function ConfigTree(props: ConfigTreeProps): import("solid-js").JSX.Element;
13
13
  export {};
@@ -10,5 +10,5 @@ interface DetectPanelProps {
10
10
  onContinue: () => void;
11
11
  theme: WidgetTheme;
12
12
  }
13
- export declare function DetectPanel(props: DetectPanelProps): any;
13
+ export declare function DetectPanel(props: DetectPanelProps): import("solid-js").JSX.Element;
14
14
  export {};
@@ -9,4 +9,4 @@ export interface FieldEditorProps {
9
9
  onCancel: () => void;
10
10
  theme: WidgetTheme;
11
11
  }
12
- export declare function FieldEditor(props: FieldEditorProps): any;
12
+ export declare function FieldEditor(props: FieldEditorProps): import("solid-js").JSX.Element;
@@ -13,5 +13,5 @@ interface FooterProps {
13
13
  theme: WidgetTheme;
14
14
  actions?: FooterActions;
15
15
  }
16
- export declare function Footer(props: FooterProps): any;
16
+ export declare function Footer(props: FooterProps): import("solid-js").JSX.Element;
17
17
  export {};
@@ -5,5 +5,5 @@ interface LayoutPickerProps {
5
5
  onSelect: (preset: LayoutPreset) => void;
6
6
  theme: WidgetTheme;
7
7
  }
8
- export declare function LayoutPicker(props: LayoutPickerProps): any;
8
+ export declare function LayoutPicker(props: LayoutPickerProps): import("solid-js").JSX.Element;
9
9
  export {};
@@ -0,0 +1,298 @@
1
+ /**
2
+ * `tmux-ide agent explain <pane>` — the detection debugger.
3
+ *
4
+ * For a single pane it prints EVERYTHING the fleet detector reasons over:
5
+ * - the pane's command + pid,
6
+ * - the `@agent_state` authority option (raw + parsed verdict + staleness),
7
+ * - the `@agent_hint` override option,
8
+ * - which manifest resolved and via which path (hint/fast/tree),
9
+ * - each state's rule → matched or not (via `explain()`),
10
+ * - the winning instantaneous classification, and
11
+ * - the bottom 5 snapshot lines it judged.
12
+ *
13
+ * This is READ-ONLY: it captures and inspects, it never sends keys or mutates
14
+ * the target pane.
15
+ */
16
+ import { execFileSync } from "node:child_process";
17
+ import { explain, type DetectedState, type ManifestConfidence } from "./tui/detect/manifest.ts";
18
+ import {
19
+ classifyInstant,
20
+ parseAuthority,
21
+ type AgentStatus,
22
+ type InstantState,
23
+ } from "./tui/detect/classify.ts";
24
+ import { getManifests } from "./tui/detect/manifest-loader.ts";
25
+ import {
26
+ describeSubtree,
27
+ readProcessTable,
28
+ resolveAgentCommand,
29
+ } from "./tui/detect/process-tree.ts";
30
+ import { readPaneSnapshot } from "./tui/detect/snapshot.ts";
31
+ import { IdeError } from "./lib/errors.ts";
32
+
33
+ /** Working/blocked authority reports older than this are stale (mirrors classify.ts). */
34
+ const AUTHORITY_STALE_SECONDS = 600;
35
+
36
+ interface PaneInfo {
37
+ id: string;
38
+ pid: number;
39
+ cmd: string;
40
+ title: string;
41
+ authorityRaw: string;
42
+ hintRaw: string;
43
+ }
44
+
45
+ export interface ExplainReport {
46
+ pane: { id: string; cmd: string; pid: number; title: string };
47
+ authority: {
48
+ raw: string | null;
49
+ state: string | null;
50
+ epoch: number | null;
51
+ ageSeconds: number | null;
52
+ stale: boolean;
53
+ verdict: AgentStatus | null;
54
+ };
55
+ hint: { raw: string | null; applied: boolean };
56
+ resolution: {
57
+ manifestId: string | null;
58
+ matchedCommand: string;
59
+ source: "hint" | "fast" | "tree" | "none";
60
+ /** The matched manifest's evidence confidence (null when nothing resolved). */
61
+ confidence: ManifestConfidence | null;
62
+ /**
63
+ * Executable-ish tokens seen in the pane's process subtree. Populated for
64
+ * the "no manifest matched" case so the report can say what it DID find.
65
+ */
66
+ subtree: string[];
67
+ };
68
+ states: Array<{ state: DetectedState; matched: boolean }>;
69
+ winner: DetectedState | null;
70
+ instant: InstantState;
71
+ classification: AgentStatus;
72
+ bottomLines: string[];
73
+ }
74
+
75
+ function tmux(args: string[]): string {
76
+ try {
77
+ return execFileSync("tmux", args, {
78
+ encoding: "utf8",
79
+ stdio: ["ignore", "pipe", "ignore"],
80
+ }).trim();
81
+ } catch {
82
+ return "";
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Resolve a target to a concrete pane's fields. `%N` is a pane id; anything
88
+ * else is treated as a session (or any tmux target) whose ACTIVE pane is used.
89
+ */
90
+ function readPaneInfo(target: string): PaneInfo | null {
91
+ const fmt =
92
+ "#{pane_id}\t#{pane_pid}\t#{pane_current_command}\t#{@agent_state}\t#{@agent_hint}\t#{pane_title}";
93
+ const raw = tmux(["display-message", "-p", "-t", target, "-F", fmt]);
94
+ if (!raw) return null;
95
+ const [id = "", pid = "", cmd = "", authorityRaw = "", hintRaw = "", ...titleParts] =
96
+ raw.split("\t");
97
+ if (!id) return null;
98
+ return {
99
+ id,
100
+ pid: Number(pid) || 0,
101
+ cmd,
102
+ authorityRaw,
103
+ hintRaw,
104
+ title: titleParts.join("\t"),
105
+ };
106
+ }
107
+
108
+ /** Build the full report for a pane. Does all the io (tmux + ps + capture). */
109
+ export function buildReport(target: string): ExplainReport {
110
+ const info = readPaneInfo(target);
111
+ if (!info) {
112
+ throw new IdeError(
113
+ `No pane found for "${target}". Pass a pane id (%N) or a live session name.`,
114
+ { code: "USAGE", exitCode: 1 },
115
+ );
116
+ }
117
+
118
+ const nowSec = Math.floor(Date.now() / 1000);
119
+
120
+ // Authority: parse richly for the report (parseAuthority collapses to a
121
+ // verdict; here we also expose the raw state/epoch/staleness).
122
+ const authRaw = info.authorityRaw || null;
123
+ let authState: string | null = null;
124
+ let authEpoch: number | null = null;
125
+ let ageSeconds: number | null = null;
126
+ let stale = false;
127
+ if (authRaw) {
128
+ const sep = authRaw.lastIndexOf(":");
129
+ if (sep !== -1) {
130
+ authState = authRaw.slice(0, sep);
131
+ const epoch = Number(authRaw.slice(sep + 1));
132
+ if (Number.isFinite(epoch)) {
133
+ authEpoch = epoch;
134
+ ageSeconds = nowSec - epoch;
135
+ stale =
136
+ (authState === "working" || authState === "blocked") &&
137
+ ageSeconds > AUTHORITY_STALE_SECONDS;
138
+ }
139
+ }
140
+ }
141
+ const verdict = parseAuthority(info.authorityRaw, nowSec);
142
+
143
+ // Resolution: hint > fast > tree, over the loaded (bundled + override) set.
144
+ const manifests = getManifests();
145
+ const table = readProcessTable();
146
+ const resolved = resolveAgentCommand(info.cmd, info.pid, table, {
147
+ manifests,
148
+ hint: info.hintRaw,
149
+ });
150
+ const manifest = resolved.manifest;
151
+ // When nothing resolved, record what the process-tree DID see so `explain`
152
+ // can point the user at an `@agent_hint` target instead of a dead end.
153
+ const subtree = manifest ? [] : describeSubtree(table, info.pid);
154
+
155
+ // Snapshot + per-state explanation.
156
+ const snapshot = { ...readPaneSnapshot(info.id), title: info.title };
157
+ const explained = manifest
158
+ ? explain(snapshot, manifest)
159
+ : {
160
+ state: null as DetectedState | null,
161
+ checked: [] as Array<{ state: DetectedState; matched: boolean }>,
162
+ };
163
+ const instant = classifyInstant(snapshot, manifest);
164
+
165
+ // Final classification mirrors sessions.ts: a fresh authority verdict wins;
166
+ // otherwise the instantaneous manifest classification. (The cross-tick `done`
167
+ // is not derivable from a single snapshot, so this is instant-only.)
168
+ const classification: AgentStatus = verdict ?? instant;
169
+
170
+ return {
171
+ pane: { id: info.id, cmd: info.cmd, pid: info.pid, title: info.title },
172
+ authority: {
173
+ raw: authRaw,
174
+ state: authState,
175
+ epoch: authEpoch,
176
+ ageSeconds,
177
+ stale,
178
+ verdict,
179
+ },
180
+ hint: { raw: info.hintRaw || null, applied: resolved.source === "hint" },
181
+ resolution: {
182
+ manifestId: manifest?.id ?? null,
183
+ matchedCommand: resolved.matchedCommand,
184
+ source: resolved.source,
185
+ confidence: manifest ? (manifest.confidence ?? "conservative") : null,
186
+ subtree,
187
+ },
188
+ states: explained.checked,
189
+ winner: explained.state,
190
+ instant,
191
+ classification,
192
+ bottomLines: snapshot.bottomNonEmpty.slice(-5),
193
+ };
194
+ }
195
+
196
+ const STATUS_COLOR: Record<AgentStatus, string> = {
197
+ blocked: "\x1b[31m", // red
198
+ working: "\x1b[33m", // yellow
199
+ done: "\x1b[32m", // green
200
+ idle: "\x1b[36m", // cyan
201
+ unknown: "\x1b[90m", // grey
202
+ };
203
+
204
+ /** Render the report as colored human-readable text. */
205
+ export function renderReport(r: ExplainReport, opts: { color?: boolean } = {}): string {
206
+ const color = opts.color ?? !("NO_COLOR" in process.env);
207
+ const c = (code: string, s: string) => (color ? `${code}${s}\x1b[0m` : s);
208
+ const bold = (s: string) => c("\x1b[1m", s);
209
+ const dim = (s: string) => c("\x1b[2m", s);
210
+ const label = (s: string) => c("\x1b[36m", s);
211
+ const status = (s: AgentStatus) => c(STATUS_COLOR[s] ?? "", s);
212
+ const yesno = (v: boolean) => (v ? c("\x1b[32m", "yes") : dim("no"));
213
+
214
+ const out: string[] = [];
215
+ out.push(bold(`agent explain — ${r.pane.id}`));
216
+ out.push(` ${label("command")} ${r.pane.cmd} ${dim(`(pid ${r.pane.pid})`)}`);
217
+ if (r.pane.title) out.push(` ${label("title")} ${r.pane.title}`);
218
+
219
+ // Authority
220
+ if (r.authority.raw) {
221
+ const age =
222
+ r.authority.ageSeconds !== null ? ` ${dim(`(${r.authority.ageSeconds}s ago)`)}` : "";
223
+ const staleTag = r.authority.stale ? " " + c("\x1b[31m", "[STALE → ignored]") : "";
224
+ const verdict = r.authority.verdict
225
+ ? status(r.authority.verdict)
226
+ : dim("none (stale/malformed)");
227
+ out.push(` ${label("authority")} ${r.authority.raw}${age}${staleTag} → ${verdict}`);
228
+ } else {
229
+ out.push(` ${label("authority")} ${dim("(unset — falling back to scraping)")}`);
230
+ }
231
+
232
+ // Hint
233
+ if (r.hint.raw) {
234
+ out.push(
235
+ ` ${label("hint")} @agent_hint=${r.hint.raw} → ${yesno(r.hint.applied)} applied`,
236
+ );
237
+ } else {
238
+ out.push(` ${label("hint")} ${dim("(unset)")}`);
239
+ }
240
+
241
+ // Resolution
242
+ if (r.resolution.manifestId) {
243
+ const conf =
244
+ r.resolution.confidence === "tuned"
245
+ ? c("\x1b[32m", "tuned")
246
+ : dim(r.resolution.confidence ?? "conservative");
247
+ out.push(
248
+ ` ${label("manifest")} ${r.resolution.manifestId} ${dim(`via ${r.resolution.source}` + (r.resolution.matchedCommand ? ` "${r.resolution.matchedCommand}"` : ""))} [${conf}]`,
249
+ );
250
+ } else {
251
+ // No manifest matched — say so, and report what the process-tree DID see so
252
+ // the user can set an `@agent_hint` or add a manifest.
253
+ const saw =
254
+ r.resolution.subtree.length > 0 ? r.resolution.subtree.join(", ") : r.pane.cmd || "(nothing)";
255
+ out.push(` ${label("manifest")} ${dim("none matched")} — ${dim(`process-tree saw: ${saw}`)}`);
256
+ out.push(` ${dim("set `tmux set-option -p @agent_hint <agent>` to force one")}`);
257
+ }
258
+
259
+ // Per-state rule results
260
+ out.push("");
261
+ out.push(bold(" state rules"));
262
+ if (r.states.length === 0) {
263
+ out.push(` ${dim("(no manifest resolved — nothing to evaluate)")}`);
264
+ } else {
265
+ for (const s of r.states) {
266
+ const mark = s.matched ? c("\x1b[32m", "✓ matched") : dim("· no match");
267
+ const win = r.winner === s.state ? " " + c("\x1b[1m", "← winner") : "";
268
+ out.push(` ${s.state.padEnd(8)} ${mark}${win}`);
269
+ }
270
+ }
271
+
272
+ // Final classification
273
+ out.push("");
274
+ out.push(
275
+ ` ${bold("classification")} ${status(r.classification)} ${dim(`(instant: ${r.instant})`)}`,
276
+ );
277
+
278
+ // Snapshot tail
279
+ out.push("");
280
+ out.push(bold(" bottom 5 lines judged"));
281
+ if (r.bottomLines.length === 0) {
282
+ out.push(` ${dim("(empty capture)")}`);
283
+ } else {
284
+ for (const line of r.bottomLines) out.push(` ${dim("│")} ${line}`);
285
+ }
286
+
287
+ return out.join("\n");
288
+ }
289
+
290
+ /** CLI entry: build + print the report (human or JSON). */
291
+ export function agentExplain(target: string, opts: { json?: boolean } = {}): void {
292
+ const report = buildReport(target);
293
+ if (opts.json) {
294
+ console.log(JSON.stringify(report, null, 2));
295
+ } else {
296
+ console.log(renderReport(report));
297
+ }
298
+ }
@@ -0,0 +1,20 @@
1
+ import { resolve } from "node:path";
2
+ import { getSessionName } from "./lib/yaml-io.ts";
3
+ import { outputError } from "./lib/output.ts";
4
+ import { attachSession, getSessionState } from "@tmux-ide/tmux-bridge";
5
+
6
+ export async function attach(
7
+ targetDir: string | undefined,
8
+ { json: _json }: { json?: boolean } = {},
9
+ ): Promise<void> {
10
+ const dir = resolve(targetDir ?? ".");
11
+ const { name: session } = getSessionName(dir);
12
+ const state = getSessionState(session);
13
+
14
+ if (!state.running) {
15
+ outputError(`Session "${session}" is not running. Start it with: tmux-ide`, "NOT_RUNNING");
16
+ return;
17
+ }
18
+
19
+ attachSession(session);
20
+ }
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env bun
2
+ import { main } from "./cli.ts";
3
+
4
+ main();
@@ -0,0 +1,7 @@
1
+ export {
2
+ clearCanonicalDaemonInfo,
3
+ isCanonicalDaemonAlive,
4
+ readCanonicalDaemonInfo,
5
+ writeCanonicalDaemonInfo,
6
+ } from "./lib/canonical-daemon.ts";
7
+ export type { CanonicalDaemonInfo } from "./lib/canonical-daemon.ts";