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
package/bin/cli.js CHANGED
@@ -1628,7 +1628,7 @@ var init_manifest = __esm({
1628
1628
  });
1629
1629
 
1630
1630
  // packages/daemon/src/tui/detect/manifests.ts
1631
- var BRAILLE_SPINNER, CLAUDE, CODEX, OPENCODE, GEMINI, AIDER, COPILOT, SHELL, BUNDLED_MANIFESTS;
1631
+ var BRAILLE_SPINNER, CLAUDE, CODEX, OPENCODE, GEMINI, AIDER, COPILOT, CURSOR, GOOSE, AMP, SHELL, BUNDLED_MANIFESTS;
1632
1632
  var init_manifests = __esm({
1633
1633
  "packages/daemon/src/tui/detect/manifests.ts"() {
1634
1634
  "use strict";
@@ -1636,6 +1636,7 @@ var init_manifests = __esm({
1636
1636
  CLAUDE = {
1637
1637
  id: "claude",
1638
1638
  commands: ["claude"],
1639
+ confidence: "tuned",
1639
1640
  states: {
1640
1641
  // Approval / confirmation prompts — Claude is waiting on the user.
1641
1642
  // Claude's approval UI is a bordered box asking a "Do you want …?" question
@@ -1682,40 +1683,56 @@ var init_manifests = __esm({
1682
1683
  };
1683
1684
  CODEX = {
1684
1685
  id: "codex",
1685
- commands: ["codex"],
1686
+ commands: ["codex", "codex.exe"],
1687
+ confidence: "tuned",
1686
1688
  states: {
1689
+ // TUNED against real captures (codex-cli v0.142.5, driven through a turn).
1690
+ // The command-approval dialog and the directory-trust prompt are the two
1691
+ // "blocked" screens. Codex's approval menu uses a "› 1." numbered arrow —
1692
+ // note the arrow is "›" (U+203A), NOT claude's "❯".
1687
1693
  blocked: {
1688
1694
  any: [
1689
- // untuned — needs real captures. Codex approval prompts (docs/common
1690
- // knowledge): a command-approval question before running a shell
1691
- // command. Kept high-precision so it can't fire on the idle "›" box.
1692
- { region: "bottom", contains: "Allow command", caseInsensitive: true },
1693
- { region: "bottom", contains: "Do you want" },
1694
- { region: "bottom", contains: "approve", caseInsensitive: true },
1695
- { region: "bottom", contains: "(y/n)", caseInsensitive: true }
1695
+ // seen (command approval): "Would you like to run the following
1696
+ // command?" above a "$ <cmd>" preview and the numbered menu.
1697
+ { region: "bottom", contains: "Would you like to run", caseInsensitive: true },
1698
+ // seen: the highlighted approval option "› 1. Yes, proceed".
1699
+ { region: "bottom", contains: "Yes, proceed" },
1700
+ // seen: "3. No, and tell Codex what to do differently (esc)".
1701
+ { region: "bottom", contains: "No, and tell Codex" },
1702
+ // seen: the confirm footer under the approval menu.
1703
+ { region: "bottom", contains: "Press enter to confirm", caseInsensitive: true },
1704
+ // seen (directory-trust prompt on first launch in an untrusted dir):
1705
+ // "Do you trust the contents of this directory?" + "1. Yes, continue".
1706
+ { region: "bottom", contains: "Do you trust the contents", caseInsensitive: true }
1696
1707
  ]
1697
1708
  },
1698
1709
  working: {
1699
1710
  any: [
1700
- // untuned for the exact string — Codex shows an elapsed-time working
1701
- // line with an interrupt hint while a turn runs. "esc to interrupt" is
1702
- // the shared CLI-TUI invariant; the spinner is the fallback.
1711
+ // seen (verbatim): the working status line is
1712
+ // "• Working (6s • esc to interrupt)".
1713
+ // Both the "Working (" prefix and the shared "esc to interrupt" hint
1714
+ // are present for the whole turn.
1715
+ { region: "bottom", regex: "Working \\(\\d" },
1703
1716
  { region: "bottom", contains: "esc to interrupt", caseInsensitive: true },
1704
1717
  { region: "bottom", regex: BRAILLE_SPINNER },
1705
1718
  { region: "title", regex: BRAILLE_SPINNER }
1706
1719
  ]
1707
1720
  }
1708
- // done: omitted. NOTE (seen, NOT used): idle Codex shows a "›" input prompt
1709
- // and a "gpt-5.5 high · <cwd> Goal achieved (5m)" status line —
1710
- // "Goal achieved" is a finished/idle marker, not "working", so it is left
1711
- // out (a done rule would collapse to idle in the instant classifier anyway).
1721
+ // done: omitted. NOTE (seen, NOT used): a finished turn leaves the agent's
1722
+ // answer above the idle "›" input box (placeholder "Find and fix a bug in
1723
+ // @filename") and a "gpt-5.5 xhigh · <cwd>" status line; older builds also
1724
+ // showed "Goal achieved (5m)". None are working/blocked evidence, so codex
1725
+ // correctly falls through to idle and the classifier infers done.
1712
1726
  }
1713
1727
  };
1714
1728
  OPENCODE = {
1715
1729
  id: "opencode",
1716
- commands: ["opencode"],
1730
+ commands: ["opencode", "opencode.exe"],
1731
+ confidence: "conservative",
1717
1732
  states: {
1718
- // untuned — needs real captures. High-precision only.
1733
+ // conservative — a live capture was attempted (opencode v1.17.10) but its
1734
+ // local auth DB errored ("no such column: name") and the TUI rendered
1735
+ // blank, so these stay best-effort. High-precision only.
1719
1736
  blocked: {
1720
1737
  any: [
1721
1738
  { region: "bottom", contains: "(y/n)", caseInsensitive: true },
@@ -1735,8 +1752,10 @@ var init_manifests = __esm({
1735
1752
  GEMINI = {
1736
1753
  id: "gemini",
1737
1754
  commands: ["gemini"],
1755
+ confidence: "conservative",
1738
1756
  states: {
1739
- // untuned — needs real captures. gemini-cli. High-precision only.
1757
+ // conservative — gemini-cli needs a Google account/API key to reach a
1758
+ // working state, so no live capture was taken. High-precision only.
1740
1759
  blocked: {
1741
1760
  any: [
1742
1761
  { region: "bottom", contains: "(y/n)", caseInsensitive: true },
@@ -1758,28 +1777,43 @@ var init_manifests = __esm({
1758
1777
  AIDER = {
1759
1778
  id: "aider",
1760
1779
  commands: ["aider"],
1780
+ confidence: "tuned",
1761
1781
  states: {
1762
- // untuned — needs real captures. aider uses "(Y)es/(N)o" confirmation
1763
- // prompts, which are its most reliable blocked signal.
1782
+ // TUNED from aider's installed source (v0.86.2). Every confirmation renders
1783
+ // through `io.confirm_ask` (io.py), which appends the literal option string
1784
+ // " (Y)es/(N)o" (plus "/(A)ll/(S)kip all" or "/(D)on't ask again") and a
1785
+ // "[Yes]:"/"[No]:" default — so "(Y)es/(N)o" is aider's exact, universal
1786
+ // blocked marker. The specific questions below are verbatim from
1787
+ // base_coder.py / commands.py.
1764
1788
  blocked: {
1765
1789
  any: [
1766
1790
  { region: "bottom", contains: "(Y)es/(N)o", caseInsensitive: true },
1767
- { region: "bottom", contains: "? [Yes]", caseInsensitive: true },
1768
- { region: "bottom", contains: "Add file to the chat", caseInsensitive: true }
1791
+ { region: "bottom", contains: "Add file to the chat", caseInsensitive: true },
1792
+ { region: "bottom", contains: "Allow edits to file", caseInsensitive: true },
1793
+ { region: "bottom", contains: "Add command output to the chat", caseInsensitive: true },
1794
+ { region: "bottom", contains: "Run pip install", caseInsensitive: true }
1769
1795
  ]
1770
1796
  },
1771
- // working: aider streams tokens without a stable status line, so we leave
1772
- // it to idle-by-default rather than risk a false positive.
1797
+ // TUNED: while a turn runs aider shows a `WaitingSpinner` (waiting.py)
1798
+ // rendered as "[░█ ] Waiting for <model>" — the text is literally
1799
+ // "Waiting for LLM" or "Waiting for " + the model name (base_coder.py:1440).
1800
+ // aider's spinner uses a "░█" scanner, NOT braille, so "Waiting for " is the
1801
+ // real invariant; the braille probe is kept only as a harmless fallback.
1773
1802
  working: {
1774
- any: [{ region: "bottom", regex: BRAILLE_SPINNER }]
1803
+ any: [
1804
+ { region: "bottom", contains: "Waiting for ", caseInsensitive: false },
1805
+ { region: "bottom", regex: BRAILLE_SPINNER }
1806
+ ]
1775
1807
  }
1776
1808
  }
1777
1809
  };
1778
1810
  COPILOT = {
1779
1811
  id: "copilot",
1780
1812
  commands: ["copilot", "github-copilot", "github-copilot-cli"],
1813
+ confidence: "conservative",
1781
1814
  states: {
1782
- // untuned — needs real captures. github-copilot-cli. High-precision only.
1815
+ // conservative — github-copilot-cli needs a GitHub account, so no live
1816
+ // capture was taken. High-precision only.
1783
1817
  blocked: {
1784
1818
  any: [
1785
1819
  { region: "bottom", contains: "(y/n)", caseInsensitive: true },
@@ -1796,9 +1830,89 @@ var init_manifests = __esm({
1796
1830
  }
1797
1831
  }
1798
1832
  };
1833
+ CURSOR = {
1834
+ id: "cursor",
1835
+ commands: ["cursor-agent", "cursor"],
1836
+ confidence: "conservative",
1837
+ states: {
1838
+ // conservative — cursor-agent (Cursor CLI) was launched live but sits on a
1839
+ // "Press any key to log in…" pre-auth screen without an account, so no
1840
+ // working/blocked turn could be captured. The pre-auth splash ("Cursor
1841
+ // Agent" / "Press any key to log in") is idle chrome and deliberately NOT
1842
+ // matched here. Markers below are high-precision guesses from public
1843
+ // knowledge of its approval/streaming UI. NOTE: cursor-agent runs under
1844
+ // `node`, so it resolves via the process-tree (argv0 basename), not the
1845
+ // pane's `current_command`.
1846
+ blocked: {
1847
+ any: [
1848
+ { region: "bottom", contains: "Do you want", caseInsensitive: false },
1849
+ { region: "bottom", contains: "Run this command", caseInsensitive: true },
1850
+ { region: "bottom", contains: "Apply this edit", caseInsensitive: true },
1851
+ { region: "bottom", contains: "(y/n)", caseInsensitive: true }
1852
+ ]
1853
+ },
1854
+ working: {
1855
+ any: [
1856
+ { region: "bottom", contains: "esc to interrupt", caseInsensitive: true },
1857
+ { region: "bottom", regex: BRAILLE_SPINNER },
1858
+ { region: "title", regex: BRAILLE_SPINNER }
1859
+ ]
1860
+ }
1861
+ }
1862
+ };
1863
+ GOOSE = {
1864
+ id: "goose",
1865
+ commands: ["goose"],
1866
+ confidence: "conservative",
1867
+ states: {
1868
+ // conservative — Block's goose CLI needs a configured provider, so no live
1869
+ // capture was taken. High-precision only; markers are best-effort from
1870
+ // public knowledge of its confirmation/streaming UI.
1871
+ blocked: {
1872
+ any: [
1873
+ { region: "bottom", contains: "Do you want", caseInsensitive: false },
1874
+ { region: "bottom", contains: "Allow this tool", caseInsensitive: true },
1875
+ { region: "bottom", contains: "(y/n)", caseInsensitive: true },
1876
+ { region: "bottom", contains: "[y/n]", caseInsensitive: true }
1877
+ ]
1878
+ },
1879
+ working: {
1880
+ any: [
1881
+ { region: "bottom", contains: "esc to interrupt", caseInsensitive: true },
1882
+ { region: "bottom", regex: BRAILLE_SPINNER },
1883
+ { region: "title", regex: BRAILLE_SPINNER }
1884
+ ]
1885
+ }
1886
+ }
1887
+ };
1888
+ AMP = {
1889
+ id: "amp",
1890
+ commands: ["amp"],
1891
+ confidence: "conservative",
1892
+ states: {
1893
+ // conservative — Sourcegraph's amp CLI needs an account, so no live capture
1894
+ // was taken. High-precision only; markers are best-effort from public
1895
+ // knowledge of its approval/streaming UI.
1896
+ blocked: {
1897
+ any: [
1898
+ { region: "bottom", contains: "Do you want", caseInsensitive: false },
1899
+ { region: "bottom", contains: "Allow", caseInsensitive: false },
1900
+ { region: "bottom", contains: "(y/n)", caseInsensitive: true }
1901
+ ]
1902
+ },
1903
+ working: {
1904
+ any: [
1905
+ { region: "bottom", contains: "esc to interrupt", caseInsensitive: true },
1906
+ { region: "bottom", regex: BRAILLE_SPINNER },
1907
+ { region: "title", regex: BRAILLE_SPINNER }
1908
+ ]
1909
+ }
1910
+ }
1911
+ };
1799
1912
  SHELL = {
1800
1913
  id: "shell",
1801
1914
  commands: ["bash", "zsh", "sh", "fish", "nu"],
1915
+ confidence: "conservative",
1802
1916
  states: {
1803
1917
  // Catch-all: a raw shell is almost always idle. We only flag an explicit
1804
1918
  // interactive confirmation as blocked; "working" is unreliable to read
@@ -1818,6 +1932,9 @@ var init_manifests = __esm({
1818
1932
  GEMINI,
1819
1933
  AIDER,
1820
1934
  COPILOT,
1935
+ CURSOR,
1936
+ GOOSE,
1937
+ AMP,
1821
1938
  SHELL
1822
1939
  ];
1823
1940
  }
@@ -1829,7 +1946,8 @@ __export(classify_exports, {
1829
1946
  classifyInstant: () => classifyInstant,
1830
1947
  classifyPaneCommand: () => classifyPaneCommand,
1831
1948
  createStatusTracker: () => createStatusTracker,
1832
- parseAuthority: () => parseAuthority
1949
+ parseAuthority: () => parseAuthority,
1950
+ parseAuthorityEpoch: () => parseAuthorityEpoch
1833
1951
  });
1834
1952
  function parseAuthority(raw, nowSec) {
1835
1953
  if (!raw) return null;
@@ -1843,6 +1961,13 @@ function parseAuthority(raw, nowSec) {
1843
1961
  }
1844
1962
  return state;
1845
1963
  }
1964
+ function parseAuthorityEpoch(raw) {
1965
+ if (!raw) return null;
1966
+ const sep2 = raw.lastIndexOf(":");
1967
+ if (sep2 === -1) return null;
1968
+ const epoch = Number(raw.slice(sep2 + 1));
1969
+ return Number.isFinite(epoch) ? epoch : null;
1970
+ }
1846
1971
  function classifyInstant(snapshot, manifest) {
1847
1972
  if (!manifest) return "unknown";
1848
1973
  const { state } = evaluateManifest(snapshot, manifest);
@@ -2004,7 +2129,8 @@ function normalizeStates(m) {
2004
2129
  if (m.states.blocked) states.blocked = m.states.blocked;
2005
2130
  if (m.states.working) states.working = m.states.working;
2006
2131
  if (m.states.done) states.done = m.states.done;
2007
- return { id: m.id, commands: m.commands, states };
2132
+ const confidence = m.confidence === "tuned" ? "tuned" : "conservative";
2133
+ return { id: m.id, commands: m.commands, states, confidence };
2008
2134
  }
2009
2135
  function warnOnce(path2, reason) {
2010
2136
  if (warned.has(path2)) return;
@@ -2070,6 +2196,16 @@ function subtreeCommands(entries, rootPid, maxDepth = 6) {
2070
2196
  walk(rootPid, 0);
2071
2197
  return commands;
2072
2198
  }
2199
+ function describeSubtree(entries, rootPid, limit = 8) {
2200
+ const seen = [];
2201
+ for (const command2 of subtreeCommands(entries, rootPid)) {
2202
+ for (const token of commandTokens(command2)) {
2203
+ if (!seen.includes(token)) seen.push(token);
2204
+ if (seen.length >= limit) return seen;
2205
+ }
2206
+ }
2207
+ return seen;
2208
+ }
2073
2209
  function readProcessTable() {
2074
2210
  try {
2075
2211
  const raw = execFileSync2("ps", ["-axo", "pid=,ppid=,command="], {
@@ -2159,6 +2295,7 @@ var init_snapshot = __esm({
2159
2295
  var sessions_exports = {};
2160
2296
  __export(sessions_exports, {
2161
2297
  SIDEBAR_PANE_OPTION: () => SIDEBAR_PANE_OPTION,
2298
+ buildAgentEntry: () => buildAgentEntry,
2162
2299
  excludeSidebarPanes: () => excludeSidebarPanes,
2163
2300
  isListableSession: () => isListableSession,
2164
2301
  listTeamSessions: () => listTeamSessions,
@@ -2166,6 +2303,22 @@ __export(sessions_exports, {
2166
2303
  rollupWindows: () => rollupWindows
2167
2304
  });
2168
2305
  import { execFileSync as execFileSync3 } from "node:child_process";
2306
+ function buildAgentEntry(input) {
2307
+ const { manifest, pane } = input;
2308
+ if (!manifest || manifest.id === "shell") return null;
2309
+ return {
2310
+ paneId: pane.id,
2311
+ windowIndex: pane.windowIndex,
2312
+ session: input.sessionName,
2313
+ kind: manifest.id,
2314
+ state: input.state,
2315
+ confidence: manifest.confidence ?? "conservative",
2316
+ since: input.since,
2317
+ title: pane.title,
2318
+ command: pane.cmd,
2319
+ dir: pane.dir
2320
+ };
2321
+ }
2169
2322
  function excludeSidebarPanes(panes) {
2170
2323
  return panes.filter((pane) => !pane.sidebar);
2171
2324
  }
@@ -2196,23 +2349,23 @@ function listTeamSessions(tracker, opts = {}) {
2196
2349
  const panes = excludeSidebarPanes(panesBySession.get(name) ?? []);
2197
2350
  const seen = opts.viewed === name;
2198
2351
  const nowSec = Math.floor(Date.now() / 1e3);
2199
- const wantPane = typeof opts.onPane === "function";
2352
+ const agents = [];
2200
2353
  const statuses = panes.map((pane) => {
2201
2354
  const authority = parseAuthority(pane.authority, nowSec);
2202
2355
  let status2;
2203
2356
  let manifest;
2357
+ let since = null;
2204
2358
  if (authority !== null) {
2359
+ since = parseAuthorityEpoch(pane.authority);
2205
2360
  if (authority === "done" && seen) {
2206
2361
  ackDone(pane.id, nowSec);
2207
2362
  status2 = "idle";
2208
2363
  } else {
2209
2364
  status2 = authority;
2210
2365
  }
2211
- if (wantPane) {
2212
- manifest = resolveAgentCommand(pane.cmd, pane.pid, processTable, {
2213
- hint: pane.hint
2214
- }).manifest;
2215
- }
2366
+ manifest = resolveAgentCommand(pane.cmd, pane.pid, processTable, {
2367
+ hint: pane.hint
2368
+ }).manifest;
2216
2369
  } else {
2217
2370
  manifest = resolveAgentCommand(pane.cmd, pane.pid, processTable, {
2218
2371
  hint: pane.hint
@@ -2226,6 +2379,8 @@ function listTeamSessions(tracker, opts = {}) {
2226
2379
  agent: manifest && manifest.id !== "shell" ? manifest.id : null,
2227
2380
  status: status2
2228
2381
  });
2382
+ const entry = buildAgentEntry({ sessionName: name, pane, manifest, state: status2, since });
2383
+ if (entry) agents.push(entry);
2229
2384
  return status2;
2230
2385
  });
2231
2386
  return {
@@ -2236,7 +2391,8 @@ function listTeamSessions(tracker, opts = {}) {
2236
2391
  status: rollupStatus(statuses),
2237
2392
  // `panes` and `statuses` are parallel (statuses = panes.map(...)), so
2238
2393
  // the pure rollup can group each pane's window with its resolved status.
2239
- windowList: rollupWindows(panes, statuses)
2394
+ windowList: rollupWindows(panes, statuses),
2395
+ agents
2240
2396
  };
2241
2397
  });
2242
2398
  }
@@ -2245,9 +2401,11 @@ function collectPanes() {
2245
2401
  "list-panes",
2246
2402
  "-a",
2247
2403
  "-F",
2248
- // Window fields sit before pane_title so the (tab-safe) title stays the
2249
- // trailing catch-all — window names don't contain tabs in practice.
2250
- `#{session_name} #{pane_id} #{pane_pid} #{pane_current_command} #{@agent_state} #{@agent_hint} #{${SIDEBAR_PANE_OPTION}} #{window_index} #{window_name} #{window_active} #{pane_title}`
2404
+ // Window fields + pane_current_path sit before pane_title so the (tab-safe)
2405
+ // title stays the trailing catch-all — window names/paths don't contain tabs
2406
+ // in practice. pane_current_path rides this SAME list-panes call (no extra
2407
+ // tmux round-trip) so per-pane agent entries can carry a working dir.
2408
+ `#{session_name} #{pane_id} #{pane_pid} #{pane_current_command} #{@agent_state} #{@agent_hint} #{${SIDEBAR_PANE_OPTION}} #{window_index} #{window_name} #{window_active} #{pane_current_path} #{pane_title}`
2251
2409
  ]);
2252
2410
  const bySession = /* @__PURE__ */ new Map();
2253
2411
  for (const line of raw.split("\n").filter(Boolean)) {
@@ -2262,6 +2420,7 @@ function collectPanes() {
2262
2420
  windowIndex = "0",
2263
2421
  windowName = "",
2264
2422
  windowActive = "0",
2423
+ dir = "",
2265
2424
  ...titleParts
2266
2425
  ] = line.split(" ");
2267
2426
  if (!session) continue;
@@ -2276,6 +2435,7 @@ function collectPanes() {
2276
2435
  windowIndex: Number(windowIndex) || 0,
2277
2436
  windowName,
2278
2437
  windowActive: windowActive === "1",
2438
+ dir,
2279
2439
  title: titleParts.join(" ")
2280
2440
  });
2281
2441
  bySession.set(session, list);
@@ -2325,10 +2485,277 @@ var init_sessions2 = __esm({
2325
2485
  }
2326
2486
  });
2327
2487
 
2328
- // packages/daemon/src/tui/compiled.ts
2329
- import { existsSync } from "node:fs";
2330
- import { dirname, resolve as resolve4 } from "node:path";
2488
+ // packages/daemon/src/lib/update-check.ts
2489
+ var update_check_exports = {};
2490
+ __export(update_check_exports, {
2491
+ CHECK_INTERVAL_MS: () => CHECK_INTERVAL_MS,
2492
+ REGISTRY_URL: () => REGISTRY_URL,
2493
+ compareSemver: () => compareSemver,
2494
+ deriveStatus: () => deriveStatus,
2495
+ fetchLatestVersion: () => fetchLatestVersion,
2496
+ getCurrentVersion: () => getCurrentVersion,
2497
+ getUpdateStatus: () => getUpdateStatus,
2498
+ isNewer: () => isNewer,
2499
+ markUpdateNotified: () => markUpdateNotified,
2500
+ maybeCheckForUpdate: () => maybeCheckForUpdate,
2501
+ parseRegistryResponse: () => parseRegistryResponse,
2502
+ readUpdateCache: () => readUpdateCache,
2503
+ runUpdateCheck: () => runUpdateCheck,
2504
+ shouldCheck: () => shouldCheck,
2505
+ updateCachePath: () => updateCachePath,
2506
+ writeUpdateCache: () => writeUpdateCache
2507
+ });
2508
+ import { existsSync, mkdirSync, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
2509
+ import { homedir as homedir2 } from "node:os";
2510
+ import { dirname, join as join2 } from "node:path";
2331
2511
  import { fileURLToPath } from "node:url";
2512
+ function parseSemver(version) {
2513
+ const core = version.trim().replace(/^v/i, "").split("+")[0] ?? "";
2514
+ const dash = core.indexOf("-");
2515
+ const main = dash === -1 ? core : core.slice(0, dash);
2516
+ const pre = dash === -1 ? "" : core.slice(dash + 1);
2517
+ const parts = main.split(".");
2518
+ const num = (i) => {
2519
+ const n = Number.parseInt(parts[i] ?? "", 10);
2520
+ return Number.isFinite(n) && n >= 0 ? n : 0;
2521
+ };
2522
+ return { nums: [num(0), num(1), num(2)], pre };
2523
+ }
2524
+ function compareSemver(a, b) {
2525
+ const pa = parseSemver(a);
2526
+ const pb = parseSemver(b);
2527
+ for (let i = 0; i < 3; i++) {
2528
+ if (pa.nums[i] !== pb.nums[i]) return pa.nums[i] < pb.nums[i] ? -1 : 1;
2529
+ }
2530
+ if (pa.pre === pb.pre) return 0;
2531
+ if (pa.pre === "") return 1;
2532
+ if (pb.pre === "") return -1;
2533
+ return pa.pre < pb.pre ? -1 : 1;
2534
+ }
2535
+ function isNewer(latest, current) {
2536
+ return compareSemver(latest, current) === 1;
2537
+ }
2538
+ function shouldCheck(lastCheckedAt, nowMs) {
2539
+ if (lastCheckedAt === null) return true;
2540
+ return nowMs - lastCheckedAt >= CHECK_INTERVAL_MS;
2541
+ }
2542
+ function parseRegistryResponse(json2) {
2543
+ try {
2544
+ const parsed = JSON.parse(json2);
2545
+ if (!parsed || typeof parsed !== "object") return null;
2546
+ const version = parsed.version;
2547
+ return typeof version === "string" && version.length > 0 ? version : null;
2548
+ } catch {
2549
+ return null;
2550
+ }
2551
+ }
2552
+ function deriveStatus(latest, currentVersion) {
2553
+ return {
2554
+ latest,
2555
+ updateAvailable: latest !== null && isNewer(latest, currentVersion)
2556
+ };
2557
+ }
2558
+ function updateCachePath() {
2559
+ const home = process.env.TMUX_IDE_HOME ?? join2(homedir2(), ".tmux-ide");
2560
+ return join2(home, "update-check.json");
2561
+ }
2562
+ function readUpdateCache() {
2563
+ const path2 = updateCachePath();
2564
+ if (!existsSync(path2)) return null;
2565
+ try {
2566
+ const parsed = JSON.parse(readFileSync3(path2, "utf-8"));
2567
+ if (!parsed || typeof parsed !== "object") return null;
2568
+ const obj = parsed;
2569
+ const lastCheckedAt = typeof obj.lastCheckedAt === "number" ? obj.lastCheckedAt : null;
2570
+ const latest = typeof obj.latest === "string" && obj.latest.length > 0 ? obj.latest : null;
2571
+ const notified = Array.isArray(obj.notified) ? obj.notified.filter((v) => typeof v === "string") : void 0;
2572
+ return { lastCheckedAt, latest, ...notified ? { notified } : {} };
2573
+ } catch {
2574
+ return null;
2575
+ }
2576
+ }
2577
+ function writeUpdateCache(cache3) {
2578
+ const path2 = updateCachePath();
2579
+ try {
2580
+ mkdirSync(dirname(path2), { recursive: true });
2581
+ writeFileSync2(path2, JSON.stringify(cache3));
2582
+ } catch {
2583
+ }
2584
+ }
2585
+ async function fetchLatestVersion(timeoutMs = 3e3) {
2586
+ const controller = new AbortController();
2587
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
2588
+ try {
2589
+ const res = await fetch(REGISTRY_URL, { signal: controller.signal });
2590
+ if (!res.ok) return null;
2591
+ return parseRegistryResponse(await res.text());
2592
+ } catch {
2593
+ return null;
2594
+ } finally {
2595
+ clearTimeout(timer);
2596
+ }
2597
+ }
2598
+ function getCurrentVersion() {
2599
+ const here = dirname(fileURLToPath(import.meta.url));
2600
+ const candidates = [
2601
+ join2(here, "../package.json"),
2602
+ // bundled bin/cli.js → repo root
2603
+ join2(here, "../../../../package.json")
2604
+ // dev src/lib → repo root
2605
+ ];
2606
+ for (const candidate of candidates) {
2607
+ try {
2608
+ const parsed = JSON.parse(readFileSync3(candidate, "utf-8"));
2609
+ if (typeof parsed.version === "string" && parsed.version.length > 0) return parsed.version;
2610
+ } catch {
2611
+ }
2612
+ }
2613
+ return "0.0.0";
2614
+ }
2615
+ function getUpdateStatus({
2616
+ currentVersion = getCurrentVersion()
2617
+ } = {}) {
2618
+ const cache3 = readUpdateCache();
2619
+ return deriveStatus(cache3?.latest ?? null, currentVersion);
2620
+ }
2621
+ async function runUpdateCheck({ now = Date.now() } = {}) {
2622
+ const cache3 = readUpdateCache();
2623
+ if (!shouldCheck(cache3?.lastCheckedAt ?? null, now)) return;
2624
+ const fetched = await fetchLatestVersion();
2625
+ writeUpdateCache({
2626
+ lastCheckedAt: now,
2627
+ latest: fetched ?? cache3?.latest ?? null,
2628
+ ...cache3?.notified ? { notified: cache3.notified } : {}
2629
+ });
2630
+ }
2631
+ function maybeCheckForUpdate({
2632
+ enabled,
2633
+ now = Date.now(),
2634
+ currentVersion = getCurrentVersion()
2635
+ }) {
2636
+ if (!enabled) return { latest: null, updateAvailable: false };
2637
+ const status2 = getUpdateStatus({ now, currentVersion });
2638
+ void runUpdateCheck({ now }).catch(() => {
2639
+ });
2640
+ return status2;
2641
+ }
2642
+ function markUpdateNotified(version) {
2643
+ const cache3 = readUpdateCache() ?? { lastCheckedAt: null, latest: null };
2644
+ const notified = cache3.notified ?? [];
2645
+ if (notified.includes(version)) return false;
2646
+ writeUpdateCache({ ...cache3, notified: [...notified, version] });
2647
+ return true;
2648
+ }
2649
+ var REGISTRY_URL, CHECK_INTERVAL_MS;
2650
+ var init_update_check = __esm({
2651
+ "packages/daemon/src/lib/update-check.ts"() {
2652
+ "use strict";
2653
+ REGISTRY_URL = "https://registry.npmjs.org/tmux-ide/latest";
2654
+ CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
2655
+ }
2656
+ });
2657
+
2658
+ // packages/daemon/src/lib/tui-binary.ts
2659
+ var tui_binary_exports = {};
2660
+ __export(tui_binary_exports, {
2661
+ MIN_TUI_BINARY_BYTES: () => MIN_TUI_BINARY_BYTES,
2662
+ RELEASE_REPO: () => RELEASE_REPO,
2663
+ bunTargetForTag: () => bunTargetForTag,
2664
+ downloadTuiBinary: () => downloadTuiBinary,
2665
+ downloadedTuiPath: () => downloadedTuiPath,
2666
+ findDownloadedTui: () => findDownloadedTui,
2667
+ normalizeVersion: () => normalizeVersion,
2668
+ releaseAssetName: () => releaseAssetName,
2669
+ releaseAssetUrl: () => releaseAssetUrl,
2670
+ tuiPlatformTag: () => tuiPlatformTag,
2671
+ tuiStateHome: () => tuiStateHome
2672
+ });
2673
+ import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2, renameSync, writeFileSync as writeFileSync3 } from "node:fs";
2674
+ import { homedir as homedir3 } from "node:os";
2675
+ import { dirname as dirname2, join as join3 } from "node:path";
2676
+ import { gunzipSync } from "node:zlib";
2677
+ function tuiPlatformTag(platform = process.platform, arch = process.arch) {
2678
+ return SUPPORTED[`${platform}-${arch}`] ?? null;
2679
+ }
2680
+ function bunTargetForTag(tag) {
2681
+ return `bun-${tag}`;
2682
+ }
2683
+ function releaseAssetName(tag) {
2684
+ return `tmux-ide-tui-${tag}.gz`;
2685
+ }
2686
+ function normalizeVersion(version) {
2687
+ return version.startsWith("v") ? version.slice(1) : version;
2688
+ }
2689
+ function releaseAssetUrl(version, tag) {
2690
+ return `https://github.com/${RELEASE_REPO}/releases/download/v${normalizeVersion(version)}/${releaseAssetName(tag)}`;
2691
+ }
2692
+ function downloadedTuiPath(home, tag, version) {
2693
+ return join3(home, "bin", `tmux-ide-tui-${tag}-${normalizeVersion(version)}`);
2694
+ }
2695
+ function tuiStateHome() {
2696
+ return process.env.TMUX_IDE_HOME ?? join3(homedir3(), ".tmux-ide");
2697
+ }
2698
+ function findDownloadedTui(version = getCurrentVersion()) {
2699
+ const tag = tuiPlatformTag();
2700
+ if (!tag) return null;
2701
+ const path2 = downloadedTuiPath(tuiStateHome(), tag, version);
2702
+ return existsSync2(path2) ? path2 : null;
2703
+ }
2704
+ async function downloadTuiBinary(opts = {}) {
2705
+ const log = opts.log ?? (() => {
2706
+ });
2707
+ const version = normalizeVersion(opts.version ?? getCurrentVersion());
2708
+ const tag = tuiPlatformTag();
2709
+ if (!tag) {
2710
+ throw new Error(
2711
+ `no prebuilt TUI binary is published for ${process.platform}-${process.arch} \u2014 install bun (https://bun.sh) to run the TUI surfaces from source instead`
2712
+ );
2713
+ }
2714
+ const url = releaseAssetUrl(version, tag);
2715
+ const dest = downloadedTuiPath(tuiStateHome(), tag, version);
2716
+ mkdirSync2(dirname2(dest), { recursive: true });
2717
+ log(`downloading ${url}`);
2718
+ const res = await fetch(url);
2719
+ if (!res.ok) {
2720
+ throw new Error(
2721
+ `could not download the TUI binary (${url} \u2192 HTTP ${res.status} ${res.statusText}). Check that release v${version} exists and published its assets.`
2722
+ );
2723
+ }
2724
+ const gz = Buffer.from(await res.arrayBuffer());
2725
+ const bin = gunzipSync(gz);
2726
+ if (bin.byteLength < MIN_TUI_BINARY_BYTES) {
2727
+ throw new Error(
2728
+ `the downloaded TUI binary is only ${bin.byteLength} bytes (expected >10MB) \u2014 treating it as corrupt and leaving the previous binary (if any) in place`
2729
+ );
2730
+ }
2731
+ const tmp = `${dest}.${process.pid}.tmp`;
2732
+ writeFileSync3(tmp, bin, { mode: 493 });
2733
+ chmodSync(tmp, 493);
2734
+ renameSync(tmp, dest);
2735
+ const mb = (bin.byteLength / 1024 / 1024).toFixed(1);
2736
+ log(`installed ${dest} (${mb} MB)`);
2737
+ return { path: dest, bytes: bin.byteLength };
2738
+ }
2739
+ var RELEASE_REPO, MIN_TUI_BINARY_BYTES, SUPPORTED;
2740
+ var init_tui_binary = __esm({
2741
+ "packages/daemon/src/lib/tui-binary.ts"() {
2742
+ "use strict";
2743
+ init_update_check();
2744
+ RELEASE_REPO = "wavyrai/tmux-ide";
2745
+ MIN_TUI_BINARY_BYTES = 10 * 1024 * 1024;
2746
+ SUPPORTED = {
2747
+ "darwin-arm64": "darwin-arm64",
2748
+ "darwin-x64": "darwin-x64",
2749
+ "linux-x64": "linux-x64",
2750
+ "linux-arm64": "linux-arm64"
2751
+ };
2752
+ }
2753
+ });
2754
+
2755
+ // packages/daemon/src/tui/compiled.ts
2756
+ import { existsSync as existsSync3 } from "node:fs";
2757
+ import { dirname as dirname3, resolve as resolve4 } from "node:path";
2758
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
2332
2759
  import { execFileSync as execFileSync4 } from "node:child_process";
2333
2760
  function resolveTuiLaunch(input) {
2334
2761
  if (input.checkoutExists && input.bunAvailable) {
@@ -2340,30 +2767,30 @@ function resolveTuiLaunch(input) {
2340
2767
  const reasons = [];
2341
2768
  if (!input.checkoutExists) {
2342
2769
  reasons.push(
2343
- "the TUI widget sources are absent (they ship only in a cloned tmux-ide checkout)"
2770
+ "the TUI widget sources are absent (reinstall tmux-ide \u2014 releases since v2.6.1 ship them)"
2344
2771
  );
2345
2772
  }
2346
2773
  if (!input.bunAvailable) {
2347
2774
  reasons.push("the `bun` runtime is not installed (https://bun.sh)");
2348
2775
  }
2349
2776
  reasons.push(
2350
- "no compiled `tmux-ide-tui` binary was found (build one with `pnpm build:tui`, or reinstall a release that ships it)"
2777
+ "no compiled `tmux-ide-tui` binary was found (build one with `pnpm build:tui`, download it with `tmux-ide update --tui-binary`, or reinstall a release that ships it)"
2351
2778
  );
2352
2779
  return { mode: "unavailable", reasons };
2353
2780
  }
2354
2781
  function findCompiledTui() {
2355
2782
  const override = process.env.TMUX_IDE_TUI_BIN;
2356
- if (override) return existsSync(override) ? override : null;
2783
+ if (override) return existsSync3(override) ? override : null;
2357
2784
  const anchors = [];
2358
- if (process.argv[1]) anchors.push(dirname(process.argv[1]));
2785
+ if (process.argv[1]) anchors.push(dirname3(process.argv[1]));
2359
2786
  anchors.push(__dirname);
2360
2787
  for (const anchor of anchors) {
2361
2788
  for (const rel of BINARY_RELS) {
2362
2789
  const candidate = resolve4(anchor, rel);
2363
- if (existsSync(candidate)) return candidate;
2790
+ if (existsSync3(candidate)) return candidate;
2364
2791
  }
2365
2792
  }
2366
- return null;
2793
+ return findDownloadedTui();
2367
2794
  }
2368
2795
  function isBunAvailable() {
2369
2796
  try {
@@ -2377,7 +2804,8 @@ var __dirname, BINARY_RELS;
2377
2804
  var init_compiled = __esm({
2378
2805
  "packages/daemon/src/tui/compiled.ts"() {
2379
2806
  "use strict";
2380
- __dirname = dirname(fileURLToPath(import.meta.url));
2807
+ init_tui_binary();
2808
+ __dirname = dirname3(fileURLToPath2(import.meta.url));
2381
2809
  BINARY_RELS = [
2382
2810
  "../packages/daemon/dist/tui/tmux-ide-tui",
2383
2811
  "../../dist/tui/tmux-ide-tui",
@@ -2405,15 +2833,15 @@ __export(sidebar_exports, {
2405
2833
  sidebarWidgetCommand: () => sidebarWidgetCommand,
2406
2834
  sidebarWidgetScript: () => sidebarWidgetScript
2407
2835
  });
2408
- import { existsSync as existsSync2 } from "node:fs";
2409
- import { dirname as dirname2, resolve as resolve5 } from "node:path";
2410
- import { fileURLToPath as fileURLToPath2 } from "node:url";
2836
+ import { existsSync as existsSync4 } from "node:fs";
2837
+ import { dirname as dirname4, resolve as resolve5 } from "node:path";
2838
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
2411
2839
  function sidebarWidgetScript() {
2412
2840
  const candidates = [
2413
2841
  resolve5(__dirname2, "../../widgets/sidebar/index.tsx"),
2414
2842
  resolve5(__dirname2, "../packages/daemon/src/widgets/sidebar/index.tsx")
2415
2843
  ];
2416
- return candidates.find((p) => existsSync2(p)) ?? candidates[0];
2844
+ return candidates.find((p) => existsSync4(p)) ?? candidates[0];
2417
2845
  }
2418
2846
  function sidebarWidgetCommand(scriptPath, session, dir, theme) {
2419
2847
  const args = [`--session=${session}`, `--dir=${dir}`];
@@ -2422,7 +2850,7 @@ function sidebarWidgetCommand(scriptPath, session, dir, theme) {
2422
2850
  surface: "sidebar",
2423
2851
  scriptPath,
2424
2852
  args,
2425
- checkoutExists: existsSync2(scriptPath),
2853
+ checkoutExists: existsSync4(scriptPath),
2426
2854
  bunAvailable: isBunAvailable(),
2427
2855
  compiledBinary: findCompiledTui()
2428
2856
  });
@@ -2501,7 +2929,7 @@ var init_sidebar = __esm({
2501
2929
  init_shell();
2502
2930
  init_sessions2();
2503
2931
  init_compiled();
2504
- __dirname2 = dirname2(fileURLToPath2(import.meta.url));
2932
+ __dirname2 = dirname4(fileURLToPath3(import.meta.url));
2505
2933
  SIDEBAR_KEY = "M-b";
2506
2934
  DEFAULT_SIDEBAR_WIDTH = 30;
2507
2935
  }
@@ -2514,12 +2942,12 @@ __export(resolve_exports, {
2514
2942
  resolveWidgetCommand: () => resolveWidgetCommand,
2515
2943
  resolveWidgetSpawn: () => resolveWidgetSpawn
2516
2944
  });
2517
- import { resolve as resolve6, dirname as dirname3 } from "node:path";
2518
- import { existsSync as existsSync3 } from "node:fs";
2519
- import { fileURLToPath as fileURLToPath3 } from "node:url";
2945
+ import { resolve as resolve6, dirname as dirname5 } from "node:path";
2946
+ import { existsSync as existsSync5 } from "node:fs";
2947
+ import { fileURLToPath as fileURLToPath4 } from "node:url";
2520
2948
  function widgetEntryPath(entry) {
2521
2949
  const sibling = resolve6(__dirname3, entry);
2522
- if (existsSync3(sibling)) return sibling;
2950
+ if (existsSync5(sibling)) return sibling;
2523
2951
  return resolve6(__dirname3, "../packages/daemon/src/widgets", entry);
2524
2952
  }
2525
2953
  function widgetArgs(opts) {
@@ -2536,7 +2964,7 @@ function resolveWidgetCommand(type, opts) {
2536
2964
  surface: type,
2537
2965
  scriptPath,
2538
2966
  args: widgetArgs(opts),
2539
- checkoutExists: existsSync3(scriptPath),
2967
+ checkoutExists: existsSync5(scriptPath),
2540
2968
  bunAvailable: isBunAvailable(),
2541
2969
  compiledBinary: findCompiledTui()
2542
2970
  });
@@ -2557,7 +2985,7 @@ function resolveWidgetSpawn(type, opts) {
2557
2985
  surface: type,
2558
2986
  scriptPath,
2559
2987
  args: widgetArgs(opts),
2560
- checkoutExists: existsSync3(scriptPath),
2988
+ checkoutExists: existsSync5(scriptPath),
2561
2989
  bunAvailable: isBunAvailable(),
2562
2990
  compiledBinary: findCompiledTui()
2563
2991
  });
@@ -2573,7 +3001,7 @@ var init_resolve = __esm({
2573
3001
  "use strict";
2574
3002
  init_shell();
2575
3003
  init_compiled();
2576
- __dirname3 = dirname3(fileURLToPath3(import.meta.url));
3004
+ __dirname3 = dirname5(fileURLToPath4(import.meta.url));
2577
3005
  WIDGET_ENTRY_POINTS = {
2578
3006
  explorer: "explorer/index.tsx",
2579
3007
  changes: "changes/index.tsx",
@@ -2582,7 +3010,7 @@ var init_resolve = __esm({
2582
3010
  config: "config/index.tsx",
2583
3011
  sidebar: "sidebar/index.tsx"
2584
3012
  };
2585
- REPO_ROOT = existsSync3(resolve6(__dirname3, "explorer/index.tsx")) ? resolve6(__dirname3, "../../../..") : resolve6(__dirname3, "..");
3013
+ REPO_ROOT = existsSync5(resolve6(__dirname3, "explorer/index.tsx")) ? resolve6(__dirname3, "../../../..") : resolve6(__dirname3, "..");
2586
3014
  WIDGET_TYPES = Object.keys(WIDGET_ENTRY_POINTS);
2587
3015
  }
2588
3016
  });
@@ -2597,11 +3025,14 @@ __export(app_config_exports, {
2597
3025
  appConfigPath: () => appConfigPath,
2598
3026
  getAppConfig: () => getAppConfig,
2599
3027
  loadAppConfig: () => loadAppConfig,
2600
- parseAppConfig: () => parseAppConfig
3028
+ loadRawAppConfig: () => loadRawAppConfig,
3029
+ mergeConfigPatch: () => mergeConfigPatch,
3030
+ parseAppConfig: () => parseAppConfig,
3031
+ updateAppConfig: () => updateAppConfig
2601
3032
  });
2602
- import { existsSync as existsSync4, readFileSync as readFileSync3 } from "node:fs";
2603
- import { homedir as homedir2 } from "node:os";
2604
- import { join as join2 } from "node:path";
3033
+ import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync4, renameSync as renameSync2, writeFileSync as writeFileSync4 } from "node:fs";
3034
+ import { homedir as homedir4 } from "node:os";
3035
+ import { dirname as dirname6, join as join4 } from "node:path";
2605
3036
  function asObject(value) {
2606
3037
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
2607
3038
  }
@@ -2629,6 +3060,7 @@ function parseAppConfig(input) {
2629
3060
  const welcome = asObject(root.welcome);
2630
3061
  const integrations = asObject(root.integrations);
2631
3062
  const worktrees = asObject(root.worktrees);
3063
+ const app = asObject(root.app);
2632
3064
  return {
2633
3065
  keys: {
2634
3066
  popup: pickString(keys.popup, D.keys.popup),
@@ -2670,17 +3102,18 @@ function parseAppConfig(input) {
2670
3102
  updates: { check: pickBool(updates.check, D.updates.check) },
2671
3103
  welcome: { show: pickBool(welcome.show, D.welcome.show) },
2672
3104
  integrations: { offer: pickBool(integrations.offer, D.integrations.offer) },
2673
- worktrees: { dir: pickString(worktrees.dir, D.worktrees.dir) }
3105
+ worktrees: { dir: pickString(worktrees.dir, D.worktrees.dir) },
3106
+ app: { frontDoor: pickBool(app.frontDoor, D.app.frontDoor) }
2674
3107
  };
2675
3108
  }
2676
3109
  function appConfigPath() {
2677
- return process.env.TMUX_IDE_CONFIG ?? join2(homedir2(), ".tmux-ide", "config.json");
3110
+ return process.env.TMUX_IDE_CONFIG ?? join4(homedir4(), ".tmux-ide", "config.json");
2678
3111
  }
2679
3112
  function loadAppConfig() {
2680
3113
  const path2 = appConfigPath();
2681
- if (!existsSync4(path2)) return parseAppConfig(void 0);
3114
+ if (!existsSync6(path2)) return parseAppConfig(void 0);
2682
3115
  try {
2683
- return parseAppConfig(JSON.parse(readFileSync3(path2, "utf-8")));
3116
+ return parseAppConfig(JSON.parse(readFileSync4(path2, "utf-8")));
2684
3117
  } catch {
2685
3118
  return parseAppConfig(void 0);
2686
3119
  }
@@ -2692,6 +3125,45 @@ function getAppConfig() {
2692
3125
  function _resetForTests() {
2693
3126
  cached = null;
2694
3127
  }
3128
+ function loadRawAppConfig() {
3129
+ const path2 = appConfigPath();
3130
+ if (!existsSync6(path2)) return {};
3131
+ try {
3132
+ const parsed = JSON.parse(readFileSync4(path2, "utf-8"));
3133
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
3134
+ } catch {
3135
+ return {};
3136
+ }
3137
+ }
3138
+ function isPlainObject(value) {
3139
+ return !!value && typeof value === "object" && !Array.isArray(value);
3140
+ }
3141
+ function mergeConfigPatch(raw, patch) {
3142
+ const out = { ...raw };
3143
+ for (const [key, value] of Object.entries(patch)) {
3144
+ if (value === void 0) {
3145
+ delete out[key];
3146
+ } else if (isPlainObject(value) && isPlainObject(out[key])) {
3147
+ out[key] = mergeConfigPatch(out[key], value);
3148
+ } else if (isPlainObject(value)) {
3149
+ out[key] = mergeConfigPatch({}, value);
3150
+ } else {
3151
+ out[key] = value;
3152
+ }
3153
+ }
3154
+ return out;
3155
+ }
3156
+ function updateAppConfig(patch) {
3157
+ const path2 = appConfigPath();
3158
+ const merged = mergeConfigPatch(loadRawAppConfig(), patch);
3159
+ mkdirSync3(dirname6(path2), { recursive: true });
3160
+ const tmp = `${path2}.${process.pid}.${Date.now()}.tmp`;
3161
+ writeFileSync4(tmp, `${JSON.stringify(merged, null, 2)}
3162
+ `, "utf-8");
3163
+ renameSync2(tmp, path2);
3164
+ cached = null;
3165
+ return parseAppConfig(merged);
3166
+ }
2695
3167
  var DEFAULT_APP_CONFIG, DEFAULT_THEME, DEFAULT_KEYS, cached;
2696
3168
  var init_app_config = __esm({
2697
3169
  "packages/daemon/src/lib/app-config.ts"() {
@@ -2724,7 +3196,8 @@ var init_app_config = __esm({
2724
3196
  updates: { check: true },
2725
3197
  welcome: { show: true },
2726
3198
  integrations: { offer: true },
2727
- worktrees: { dir: "" }
3199
+ worktrees: { dir: "" },
3200
+ app: { frontDoor: false }
2728
3201
  };
2729
3202
  DEFAULT_THEME = DEFAULT_APP_CONFIG.theme;
2730
3203
  DEFAULT_KEYS = DEFAULT_APP_CONFIG.keys;
@@ -2733,9 +3206,9 @@ var init_app_config = __esm({
2733
3206
  });
2734
3207
 
2735
3208
  // packages/daemon/src/tui/team/keymap.ts
2736
- import { existsSync as existsSync5, readFileSync as readFileSync4 } from "node:fs";
2737
- import { homedir as homedir3 } from "node:os";
2738
- import { join as join3 } from "node:path";
3209
+ import { existsSync as existsSync7, readFileSync as readFileSync5 } from "node:fs";
3210
+ import { homedir as homedir5 } from "node:os";
3211
+ import { join as join5 } from "node:path";
2739
3212
  var ACTION_ORDER, DEFAULT_KEYMAP;
2740
3213
  var init_keymap = __esm({
2741
3214
  "packages/daemon/src/tui/team/keymap.ts"() {
@@ -3164,24 +3637,24 @@ __export(welcome_exports, {
3164
3637
  welcomeMarkerPath: () => welcomeMarkerPath
3165
3638
  });
3166
3639
  import { spawn as spawn2 } from "node:child_process";
3167
- import { existsSync as existsSync6, mkdirSync, writeFileSync as writeFileSync2 } from "node:fs";
3168
- import { homedir as homedir4 } from "node:os";
3169
- import { dirname as dirname4, join as join4 } from "node:path";
3640
+ import { existsSync as existsSync8, mkdirSync as mkdirSync4, writeFileSync as writeFileSync5 } from "node:fs";
3641
+ import { homedir as homedir6 } from "node:os";
3642
+ import { dirname as dirname7, join as join6 } from "node:path";
3170
3643
  function renderKey2(tmuxKey) {
3171
3644
  return tmuxKey.replace(/M-/g, "\u2325").replace(/C-/g, "^").replace(/S-/g, "\u21E7");
3172
3645
  }
3173
3646
  function welcomeMarkerPath() {
3174
- const home = process.env.TMUX_IDE_HOME ?? join4(homedir4(), ".tmux-ide");
3175
- return join4(home, "welcomed");
3647
+ const home = process.env.TMUX_IDE_HOME ?? join6(homedir6(), ".tmux-ide");
3648
+ return join6(home, "welcomed");
3176
3649
  }
3177
3650
  function shouldShowWelcome() {
3178
- return !existsSync6(welcomeMarkerPath()) && getAppConfig().welcome.show;
3651
+ return !existsSync8(welcomeMarkerPath()) && getAppConfig().welcome.show;
3179
3652
  }
3180
3653
  function markWelcomed() {
3181
3654
  const path2 = welcomeMarkerPath();
3182
3655
  try {
3183
- mkdirSync(dirname4(path2), { recursive: true });
3184
- writeFileSync2(path2, (/* @__PURE__ */ new Date()).toISOString());
3656
+ mkdirSync4(dirname7(path2), { recursive: true });
3657
+ writeFileSync5(path2, (/* @__PURE__ */ new Date()).toISOString());
3185
3658
  } catch {
3186
3659
  }
3187
3660
  }
@@ -3241,20 +3714,20 @@ __export(claude_exports, {
3241
3714
  uninstallClaudeIntegration: () => uninstallClaudeIntegration
3242
3715
  });
3243
3716
  import {
3244
- chmodSync,
3717
+ chmodSync as chmodSync2,
3245
3718
  copyFileSync,
3246
- existsSync as existsSync7,
3247
- mkdirSync as mkdirSync2,
3248
- readFileSync as readFileSync5,
3249
- writeFileSync as writeFileSync3
3719
+ existsSync as existsSync9,
3720
+ mkdirSync as mkdirSync5,
3721
+ readFileSync as readFileSync6,
3722
+ writeFileSync as writeFileSync6
3250
3723
  } from "node:fs";
3251
- import { homedir as homedir5 } from "node:os";
3252
- import { dirname as dirname5, join as join5 } from "node:path";
3724
+ import { homedir as homedir7 } from "node:os";
3725
+ import { dirname as dirname8, join as join7 } from "node:path";
3253
3726
  function hookScriptPath() {
3254
- return join5(homedir5(), HOOK_SCRIPT_RELPATH);
3727
+ return join7(homedir7(), HOOK_SCRIPT_RELPATH);
3255
3728
  }
3256
3729
  function claudeSettingsPath() {
3257
- return process.env.TMUX_IDE_CLAUDE_SETTINGS ?? join5(homedir5(), ".claude", "settings.json");
3730
+ return process.env.TMUX_IDE_CLAUDE_SETTINGS ?? join7(homedir7(), ".claude", "settings.json");
3258
3731
  }
3259
3732
  function isOurs(group) {
3260
3733
  return group.hooks?.some((h) => h.command?.includes(HOOK_SCRIPT_RELPATH)) ?? false;
@@ -3287,24 +3760,24 @@ function isInstalled(settings) {
3287
3760
  return Object.values(settings.hooks ?? {}).some((groups) => groups.some(isOurs));
3288
3761
  }
3289
3762
  function readSettings(path2) {
3290
- if (!existsSync7(path2)) return {};
3763
+ if (!existsSync9(path2)) return {};
3291
3764
  try {
3292
- return JSON.parse(readFileSync5(path2, "utf8"));
3765
+ return JSON.parse(readFileSync6(path2, "utf8"));
3293
3766
  } catch {
3294
3767
  throw new Error(`${path2} is not valid JSON \u2014 fix or move it, then retry`);
3295
3768
  }
3296
3769
  }
3297
3770
  function installClaudeIntegration() {
3298
3771
  const script = hookScriptPath();
3299
- mkdirSync2(dirname5(script), { recursive: true });
3300
- writeFileSync3(script, HOOK_SCRIPT, "utf8");
3301
- chmodSync(script, 493);
3772
+ mkdirSync5(dirname8(script), { recursive: true });
3773
+ writeFileSync6(script, HOOK_SCRIPT, "utf8");
3774
+ chmodSync2(script, 493);
3302
3775
  const settingsPath = claudeSettingsPath();
3303
- mkdirSync2(dirname5(settingsPath), { recursive: true });
3776
+ mkdirSync5(dirname8(settingsPath), { recursive: true });
3304
3777
  const settings = readSettings(settingsPath);
3305
3778
  const backup = `${settingsPath}.tmux-ide.bak`;
3306
- if (existsSync7(settingsPath) && !existsSync7(backup)) copyFileSync(settingsPath, backup);
3307
- writeFileSync3(settingsPath, `${JSON.stringify(mergeHooks(settings, script), null, 2)}
3779
+ if (existsSync9(settingsPath) && !existsSync9(backup)) copyFileSync(settingsPath, backup);
3780
+ writeFileSync6(settingsPath, `${JSON.stringify(mergeHooks(settings, script), null, 2)}
3308
3781
  `, "utf8");
3309
3782
  return { scriptPath: script, settingsPath };
3310
3783
  }
@@ -3313,7 +3786,7 @@ function uninstallClaudeIntegration() {
3313
3786
  const settings = readSettings(settingsPath);
3314
3787
  const wasInstalled = isInstalled(settings);
3315
3788
  if (wasInstalled) {
3316
- writeFileSync3(settingsPath, `${JSON.stringify(removeHooks(settings), null, 2)}
3789
+ writeFileSync6(settingsPath, `${JSON.stringify(removeHooks(settings), null, 2)}
3317
3790
  `, "utf8");
3318
3791
  }
3319
3792
  return { settingsPath, wasInstalled };
@@ -3321,7 +3794,7 @@ function uninstallClaudeIntegration() {
3321
3794
  function claudeIntegrationStatus() {
3322
3795
  return {
3323
3796
  installed: isInstalled(readSettings(claudeSettingsPath())),
3324
- scriptExists: existsSync7(hookScriptPath())
3797
+ scriptExists: existsSync9(hookScriptPath())
3325
3798
  };
3326
3799
  }
3327
3800
  var HOOK_SCRIPT_RELPATH, HOOK_SCRIPT, EVENT_STATES;
@@ -3360,12 +3833,12 @@ __export(offer_exports, {
3360
3833
  shouldOfferIntegration: () => shouldOfferIntegration
3361
3834
  });
3362
3835
  import { execFileSync as execFileSync5, spawn as spawn3 } from "node:child_process";
3363
- import { existsSync as existsSync8, mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "node:fs";
3364
- import { homedir as homedir6 } from "node:os";
3365
- import { dirname as dirname6, join as join6 } from "node:path";
3836
+ import { existsSync as existsSync10, mkdirSync as mkdirSync6, writeFileSync as writeFileSync7 } from "node:fs";
3837
+ import { homedir as homedir8 } from "node:os";
3838
+ import { dirname as dirname9, join as join8 } from "node:path";
3366
3839
  function integrationOfferMarkerPath() {
3367
- const home = process.env.TMUX_IDE_HOME ?? join6(homedir6(), ".tmux-ide");
3368
- return join6(home, "integration-offered");
3840
+ const home = process.env.TMUX_IDE_HOME ?? join8(homedir8(), ".tmux-ide");
3841
+ return join8(home, "integration-offered");
3369
3842
  }
3370
3843
  function shouldOfferIntegration(input) {
3371
3844
  return input.claudeOnPath && !input.integrationInstalled && !input.markerPresent && input.offerEnabled;
@@ -3373,8 +3846,8 @@ function shouldOfferIntegration(input) {
3373
3846
  function markIntegrationOffered() {
3374
3847
  const path2 = integrationOfferMarkerPath();
3375
3848
  try {
3376
- mkdirSync3(dirname6(path2), { recursive: true });
3377
- writeFileSync4(path2, (/* @__PURE__ */ new Date()).toISOString());
3849
+ mkdirSync6(dirname9(path2), { recursive: true });
3850
+ writeFileSync7(path2, (/* @__PURE__ */ new Date()).toISOString());
3378
3851
  } catch {
3379
3852
  }
3380
3853
  }
@@ -3399,7 +3872,7 @@ function maybeOfferIntegrationPopup() {
3399
3872
  offer = shouldOfferIntegration({
3400
3873
  claudeOnPath: claudeOnPath(),
3401
3874
  integrationInstalled: status2.installed,
3402
- markerPresent: existsSync8(integrationOfferMarkerPath()),
3875
+ markerPresent: existsSync10(integrationOfferMarkerPath()),
3403
3876
  offerEnabled: getAppConfig().integrations.offer
3404
3877
  });
3405
3878
  } catch {
@@ -3455,176 +3928,6 @@ var init_kitty_keys = __esm({
3455
3928
  }
3456
3929
  });
3457
3930
 
3458
- // packages/daemon/src/lib/update-check.ts
3459
- var update_check_exports = {};
3460
- __export(update_check_exports, {
3461
- CHECK_INTERVAL_MS: () => CHECK_INTERVAL_MS,
3462
- REGISTRY_URL: () => REGISTRY_URL,
3463
- compareSemver: () => compareSemver,
3464
- deriveStatus: () => deriveStatus,
3465
- fetchLatestVersion: () => fetchLatestVersion,
3466
- getCurrentVersion: () => getCurrentVersion,
3467
- getUpdateStatus: () => getUpdateStatus,
3468
- isNewer: () => isNewer,
3469
- markUpdateNotified: () => markUpdateNotified,
3470
- maybeCheckForUpdate: () => maybeCheckForUpdate,
3471
- parseRegistryResponse: () => parseRegistryResponse,
3472
- readUpdateCache: () => readUpdateCache,
3473
- runUpdateCheck: () => runUpdateCheck,
3474
- shouldCheck: () => shouldCheck,
3475
- updateCachePath: () => updateCachePath,
3476
- writeUpdateCache: () => writeUpdateCache
3477
- });
3478
- import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "node:fs";
3479
- import { homedir as homedir7 } from "node:os";
3480
- import { dirname as dirname7, join as join7 } from "node:path";
3481
- import { fileURLToPath as fileURLToPath4 } from "node:url";
3482
- function parseSemver(version) {
3483
- const core = version.trim().replace(/^v/i, "").split("+")[0] ?? "";
3484
- const dash = core.indexOf("-");
3485
- const main = dash === -1 ? core : core.slice(0, dash);
3486
- const pre = dash === -1 ? "" : core.slice(dash + 1);
3487
- const parts = main.split(".");
3488
- const num = (i) => {
3489
- const n = Number.parseInt(parts[i] ?? "", 10);
3490
- return Number.isFinite(n) && n >= 0 ? n : 0;
3491
- };
3492
- return { nums: [num(0), num(1), num(2)], pre };
3493
- }
3494
- function compareSemver(a, b) {
3495
- const pa = parseSemver(a);
3496
- const pb = parseSemver(b);
3497
- for (let i = 0; i < 3; i++) {
3498
- if (pa.nums[i] !== pb.nums[i]) return pa.nums[i] < pb.nums[i] ? -1 : 1;
3499
- }
3500
- if (pa.pre === pb.pre) return 0;
3501
- if (pa.pre === "") return 1;
3502
- if (pb.pre === "") return -1;
3503
- return pa.pre < pb.pre ? -1 : 1;
3504
- }
3505
- function isNewer(latest, current) {
3506
- return compareSemver(latest, current) === 1;
3507
- }
3508
- function shouldCheck(lastCheckedAt, nowMs) {
3509
- if (lastCheckedAt === null) return true;
3510
- return nowMs - lastCheckedAt >= CHECK_INTERVAL_MS;
3511
- }
3512
- function parseRegistryResponse(json2) {
3513
- try {
3514
- const parsed = JSON.parse(json2);
3515
- if (!parsed || typeof parsed !== "object") return null;
3516
- const version = parsed.version;
3517
- return typeof version === "string" && version.length > 0 ? version : null;
3518
- } catch {
3519
- return null;
3520
- }
3521
- }
3522
- function deriveStatus(latest, currentVersion) {
3523
- return {
3524
- latest,
3525
- updateAvailable: latest !== null && isNewer(latest, currentVersion)
3526
- };
3527
- }
3528
- function updateCachePath() {
3529
- const home = process.env.TMUX_IDE_HOME ?? join7(homedir7(), ".tmux-ide");
3530
- return join7(home, "update-check.json");
3531
- }
3532
- function readUpdateCache() {
3533
- const path2 = updateCachePath();
3534
- if (!existsSync9(path2)) return null;
3535
- try {
3536
- const parsed = JSON.parse(readFileSync6(path2, "utf-8"));
3537
- if (!parsed || typeof parsed !== "object") return null;
3538
- const obj = parsed;
3539
- const lastCheckedAt = typeof obj.lastCheckedAt === "number" ? obj.lastCheckedAt : null;
3540
- const latest = typeof obj.latest === "string" && obj.latest.length > 0 ? obj.latest : null;
3541
- const notified = Array.isArray(obj.notified) ? obj.notified.filter((v) => typeof v === "string") : void 0;
3542
- return { lastCheckedAt, latest, ...notified ? { notified } : {} };
3543
- } catch {
3544
- return null;
3545
- }
3546
- }
3547
- function writeUpdateCache(cache3) {
3548
- const path2 = updateCachePath();
3549
- try {
3550
- mkdirSync4(dirname7(path2), { recursive: true });
3551
- writeFileSync5(path2, JSON.stringify(cache3));
3552
- } catch {
3553
- }
3554
- }
3555
- async function fetchLatestVersion(timeoutMs = 3e3) {
3556
- const controller = new AbortController();
3557
- const timer = setTimeout(() => controller.abort(), timeoutMs);
3558
- try {
3559
- const res = await fetch(REGISTRY_URL, { signal: controller.signal });
3560
- if (!res.ok) return null;
3561
- return parseRegistryResponse(await res.text());
3562
- } catch {
3563
- return null;
3564
- } finally {
3565
- clearTimeout(timer);
3566
- }
3567
- }
3568
- function getCurrentVersion() {
3569
- const here = dirname7(fileURLToPath4(import.meta.url));
3570
- const candidates = [
3571
- join7(here, "../package.json"),
3572
- // bundled bin/cli.js → repo root
3573
- join7(here, "../../../../package.json")
3574
- // dev src/lib → repo root
3575
- ];
3576
- for (const candidate of candidates) {
3577
- try {
3578
- const parsed = JSON.parse(readFileSync6(candidate, "utf-8"));
3579
- if (typeof parsed.version === "string" && parsed.version.length > 0) return parsed.version;
3580
- } catch {
3581
- }
3582
- }
3583
- return "0.0.0";
3584
- }
3585
- function getUpdateStatus({
3586
- currentVersion = getCurrentVersion()
3587
- } = {}) {
3588
- const cache3 = readUpdateCache();
3589
- return deriveStatus(cache3?.latest ?? null, currentVersion);
3590
- }
3591
- async function runUpdateCheck({ now = Date.now() } = {}) {
3592
- const cache3 = readUpdateCache();
3593
- if (!shouldCheck(cache3?.lastCheckedAt ?? null, now)) return;
3594
- const fetched = await fetchLatestVersion();
3595
- writeUpdateCache({
3596
- lastCheckedAt: now,
3597
- latest: fetched ?? cache3?.latest ?? null,
3598
- ...cache3?.notified ? { notified: cache3.notified } : {}
3599
- });
3600
- }
3601
- function maybeCheckForUpdate({
3602
- enabled,
3603
- now = Date.now(),
3604
- currentVersion = getCurrentVersion()
3605
- }) {
3606
- if (!enabled) return { latest: null, updateAvailable: false };
3607
- const status2 = getUpdateStatus({ now, currentVersion });
3608
- void runUpdateCheck({ now }).catch(() => {
3609
- });
3610
- return status2;
3611
- }
3612
- function markUpdateNotified(version) {
3613
- const cache3 = readUpdateCache() ?? { lastCheckedAt: null, latest: null };
3614
- const notified = cache3.notified ?? [];
3615
- if (notified.includes(version)) return false;
3616
- writeUpdateCache({ ...cache3, notified: [...notified, version] });
3617
- return true;
3618
- }
3619
- var REGISTRY_URL, CHECK_INTERVAL_MS;
3620
- var init_update_check = __esm({
3621
- "packages/daemon/src/lib/update-check.ts"() {
3622
- "use strict";
3623
- REGISTRY_URL = "https://registry.npmjs.org/tmux-ide/latest";
3624
- CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
3625
- }
3626
- });
3627
-
3628
3931
  // packages/daemon/src/schemas/registry.ts
3629
3932
  import { z as z9 } from "zod";
3630
3933
  var RegisteredProjectSchemaZ, RegisterProjectRequestSchemaZ, InitProjectRequestSchemaZ, ProjectTemplateSchemaZ;
@@ -3663,7 +3966,7 @@ var init_registry = __esm({
3663
3966
 
3664
3967
  // packages/daemon/src/lib/project-probe.ts
3665
3968
  import { execFile } from "node:child_process";
3666
- import { existsSync as existsSync10 } from "node:fs";
3969
+ import { existsSync as existsSync11 } from "node:fs";
3667
3970
  import { basename as basename3, isAbsolute, resolve as resolve7 } from "node:path";
3668
3971
  function sanitizeName(raw) {
3669
3972
  return raw.trim().replace(/\s+/g, "-").replace(/[^A-Za-z0-9._-]/g, "").replace(/^-+|-+$/g, "");
@@ -3694,7 +3997,7 @@ var init_project_probe = __esm({
3694
3997
  "use strict";
3695
3998
  GIT_TIMEOUT_MS = 2e3;
3696
3999
  realIo = {
3697
- exists: existsSync10,
4000
+ exists: existsSync11,
3698
4001
  runGit: (args, cwd) => new Promise((resolveResult) => {
3699
4002
  execFile(
3700
4003
  "git",
@@ -3715,9 +4018,9 @@ var init_project_probe = __esm({
3715
4018
 
3716
4019
  // packages/daemon/src/lib/project-registry.ts
3717
4020
  import { EventEmitter } from "node:events";
3718
- import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync7, renameSync, writeFileSync as writeFileSync6 } from "node:fs";
3719
- import { homedir as homedir8 } from "node:os";
3720
- import { dirname as dirname8, isAbsolute as isAbsolute2, join as join8, resolve as resolve8 } from "node:path";
4021
+ import { existsSync as existsSync12, mkdirSync as mkdirSync7, readFileSync as readFileSync7, renameSync as renameSync3, writeFileSync as writeFileSync8 } from "node:fs";
4022
+ import { homedir as homedir9 } from "node:os";
4023
+ import { dirname as dirname10, isAbsolute as isAbsolute2, join as join9, resolve as resolve8 } from "node:path";
3721
4024
  import { z as z10 } from "zod";
3722
4025
  function applyAction(state, action) {
3723
4026
  switch (action.type) {
@@ -3749,14 +4052,14 @@ function buildRegisteredProject(probe, name, registeredAt) {
3749
4052
  function registryDir() {
3750
4053
  const override = process.env[REGISTRY_DIR_ENV];
3751
4054
  if (override && override.length > 0) return override;
3752
- return join8(homedir8(), ".tmux-ide");
4055
+ return join9(homedir9(), ".tmux-ide");
3753
4056
  }
3754
4057
  function registryPath() {
3755
- return join8(registryDir(), "projects.json");
4058
+ return join9(registryDir(), "projects.json");
3756
4059
  }
3757
4060
  function readDisk() {
3758
4061
  const path2 = registryPath();
3759
- if (!existsSync11(path2)) return [];
4062
+ if (!existsSync12(path2)) return [];
3760
4063
  const raw = readFileSync7(path2, "utf-8");
3761
4064
  if (raw.trim().length === 0) return [];
3762
4065
  let parsed;
@@ -3779,12 +4082,12 @@ function readDisk() {
3779
4082
  }
3780
4083
  function writeDisk(projects) {
3781
4084
  const path2 = registryPath();
3782
- const dir = dirname8(path2);
3783
- mkdirSync5(dir, { recursive: true });
4085
+ const dir = dirname10(path2);
4086
+ mkdirSync7(dir, { recursive: true });
3784
4087
  const file = { version: 1, projects };
3785
4088
  const tmpPath = `${path2}.tmp`;
3786
- writeFileSync6(tmpPath, JSON.stringify(file, null, 2) + "\n");
3787
- renameSync(tmpPath, path2);
4089
+ writeFileSync8(tmpPath, JSON.stringify(file, null, 2) + "\n");
4090
+ renameSync3(tmpPath, path2);
3788
4091
  }
3789
4092
  function ensureCache() {
3790
4093
  if (cache2 !== null) return cache2;
@@ -3803,7 +4106,7 @@ function getProject(name) {
3803
4106
  return ensureCache().find((p) => p.name === name) ?? null;
3804
4107
  }
3805
4108
  async function registerProject(input) {
3806
- const exists = input.exists ?? existsSync11;
4109
+ const exists = input.exists ?? existsSync12;
3807
4110
  const absoluteDir = isAbsolute2(input.dir) ? input.dir : resolve8(input.dir);
3808
4111
  if (!exists(absoluteDir)) {
3809
4112
  throw new ProjectDirNotFoundError(absoluteDir);
@@ -4013,9 +4316,9 @@ __export(events_exports, {
4013
4316
  formatEventLine: () => formatEventLine,
4014
4317
  shouldRotate: () => shouldRotate
4015
4318
  });
4016
- import { appendFileSync, existsSync as existsSync12, mkdirSync as mkdirSync6, renameSync as renameSync2, statSync } from "node:fs";
4017
- import { homedir as homedir9 } from "node:os";
4018
- import { join as join9 } from "node:path";
4319
+ import { appendFileSync, existsSync as existsSync13, mkdirSync as mkdirSync8, renameSync as renameSync4, statSync } from "node:fs";
4320
+ import { homedir as homedir10 } from "node:os";
4321
+ import { join as join10 } from "node:path";
4019
4322
  function diffFleet(prev, next) {
4020
4323
  const state = /* @__PURE__ */ new Map();
4021
4324
  const events = [];
@@ -4042,15 +4345,15 @@ function formatEventLine(ev, paint = (_s, t) => t) {
4042
4345
  return `${isoTime(ev.ts)} ${ev.session} ${from} \u2192 ${paint(ev.to, ev.to)}`;
4043
4346
  }
4044
4347
  function eventsPath() {
4045
- return join9(homedir9(), ".tmux-ide", "events.jsonl");
4348
+ return join10(homedir10(), ".tmux-ide", "events.jsonl");
4046
4349
  }
4047
4350
  function appendEvents(events, now = () => (/* @__PURE__ */ new Date()).toISOString()) {
4048
4351
  if (events.length === 0) return;
4049
4352
  const path2 = eventsPath();
4050
4353
  try {
4051
- mkdirSync6(join9(homedir9(), ".tmux-ide"), { recursive: true });
4052
- if (existsSync12(path2) && shouldRotate(statSync(path2).size)) {
4053
- renameSync2(path2, `${path2}.1`);
4354
+ mkdirSync8(join10(homedir10(), ".tmux-ide"), { recursive: true });
4355
+ if (existsSync13(path2) && shouldRotate(statSync(path2).size)) {
4356
+ renameSync4(path2, `${path2}.1`);
4054
4357
  }
4055
4358
  const ts = now();
4056
4359
  const lines = events.map((e) => `${JSON.stringify({ ts, ...e })}
@@ -4069,25 +4372,38 @@ var init_events = __esm({
4069
4372
 
4070
4373
  // packages/daemon/src/tui/chrome/notify.ts
4071
4374
  import { execFileSync as execFileSync6 } from "node:child_process";
4072
- function notifyMessage(session, to) {
4073
- return to === "blocked" ? `\u26A0 ${session} needs you (blocked)` : `\u2713 ${session} finished (done)`;
4375
+ import { existsSync as existsSync14, readFileSync as readFileSync8 } from "node:fs";
4376
+ function statusPhrase(to) {
4377
+ return to === "blocked" ? "needs input" : "finished";
4378
+ }
4379
+ function notifyMessage(ev) {
4380
+ const agent = ev.agent && ev.agent.length > 0 ? ev.agent : "agent";
4381
+ const where = ev.location && ev.location.length > 0 ? ev.location : ev.session;
4382
+ const text = `${agent} ${ev.to} \xB7 ${where} \u2014 ${statusPhrase(ev.to)}`;
4383
+ return text.length > NOTIFY_MAX_LEN ? `${text.slice(0, NOTIFY_MAX_LEN - 1)}\u2026` : text;
4384
+ }
4385
+ function enabledStates(prefs) {
4386
+ const states = /* @__PURE__ */ new Set();
4387
+ if (prefs.onBlocked) states.add("blocked");
4388
+ if (prefs.onDone) states.add("done");
4389
+ return states;
4074
4390
  }
4075
- function decideNotifications(events, clients, lastNotified, nowMs) {
4391
+ function decideNotifications(events, clients, lastNotified, nowMs, states = NOTIFY_STATES) {
4076
4392
  const nextLastNotified = new Map(lastNotified);
4077
4393
  const toasts = [];
4078
4394
  const system = [];
4079
4395
  for (const ev of events) {
4080
- if (!NOTIFY_STATES.has(ev.to)) continue;
4396
+ if (!states.has(ev.to)) continue;
4081
4397
  const key = `${ev.session}:${ev.to}`;
4082
4398
  const last = nextLastNotified.get(key);
4083
4399
  if (last !== void 0 && nowMs - last < NOTIFY_DEBOUNCE_MS) continue;
4084
4400
  nextLastNotified.set(key, nowMs);
4085
- const message = notifyMessage(ev.session, ev.to);
4401
+ const message = notifyMessage(ev);
4086
4402
  for (const c of clients) {
4087
4403
  if (c.session === ev.session) continue;
4088
4404
  toasts.push({ client: c.client, message });
4089
4405
  }
4090
- system.push({ message });
4406
+ system.push({ message, session: ev.session });
4091
4407
  }
4092
4408
  return { toasts, system, nextLastNotified };
4093
4409
  }
@@ -4115,23 +4431,100 @@ function sendToasts(toasts) {
4115
4431
  }
4116
4432
  }
4117
4433
  }
4118
- function sendSystemNotification(message) {
4434
+ function hasTerminalNotifier() {
4435
+ try {
4436
+ execFileSync6("which", ["terminal-notifier"], { stdio: "ignore" });
4437
+ return true;
4438
+ } catch {
4439
+ return false;
4440
+ }
4441
+ }
4442
+ function shellSingleQuote(value) {
4443
+ return `'${value.replace(/'/g, `'\\''`)}'`;
4444
+ }
4445
+ function terminalNotifierArgs(n) {
4446
+ return [
4447
+ "-title",
4448
+ "tmux-ide",
4449
+ "-message",
4450
+ n.message,
4451
+ "-execute",
4452
+ `tmux switch-client -t ${shellSingleQuote(n.session)}`
4453
+ ];
4454
+ }
4455
+ function sendSystemNotification(n) {
4119
4456
  if (process.platform !== "darwin") return;
4120
- const escaped = message.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
4121
4457
  try {
4458
+ if (hasTerminalNotifier()) {
4459
+ execFileSync6("terminal-notifier", terminalNotifierArgs(n), { stdio: "ignore" });
4460
+ return;
4461
+ }
4462
+ const escaped = n.message.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
4122
4463
  execFileSync6("osascript", ["-e", `display notification "${escaped}" with title "tmux-ide"`], {
4123
4464
  stdio: "ignore"
4124
4465
  });
4125
4466
  } catch {
4126
4467
  }
4127
4468
  }
4469
+ function asObject2(value) {
4470
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
4471
+ }
4472
+ function pickBool2(value, fallback) {
4473
+ return typeof value === "boolean" ? value : fallback;
4474
+ }
4475
+ function parseHHMM(value) {
4476
+ if (typeof value !== "string") return null;
4477
+ const m = /^(\d{2}):(\d{2})$/.exec(value.trim());
4478
+ if (!m) return null;
4479
+ const hours = Number(m[1]);
4480
+ const minutes = Number(m[2]);
4481
+ if (hours > 23 || minutes > 59) return null;
4482
+ return hours * 60 + minutes;
4483
+ }
4484
+ function inQuietHours(now, quiet) {
4485
+ if (!quiet) return false;
4486
+ const start2 = parseHHMM(quiet.start);
4487
+ const end = parseHHMM(quiet.end);
4488
+ if (start2 === null || end === null || start2 === end) return false;
4489
+ const nowMin = now.getHours() * 60 + now.getMinutes();
4490
+ return start2 < end ? nowMin >= start2 && nowMin < end : nowMin >= start2 || nowMin < end;
4491
+ }
4492
+ function parseQuietHours(value) {
4493
+ const o = asObject2(value);
4494
+ const start2 = typeof o.start === "string" ? o.start : null;
4495
+ const end = typeof o.end === "string" ? o.end : null;
4496
+ if (start2 === null || end === null) return null;
4497
+ if (parseHHMM(start2) === null || parseHHMM(end) === null) return null;
4498
+ return { start: start2, end };
4499
+ }
4500
+ function parseNotificationPrefs(rawConfig) {
4501
+ const base = parseAppConfig(rawConfig).notifications;
4502
+ const n = asObject2(asObject2(rawConfig).notifications);
4503
+ return {
4504
+ enabled: pickBool2(n.enabled, DEFAULT_NOTIFICATION_PREFS.enabled),
4505
+ toast: base.toast,
4506
+ macos: base.macos,
4507
+ onBlocked: pickBool2(n.onBlocked, DEFAULT_NOTIFICATION_PREFS.onBlocked),
4508
+ onDone: pickBool2(n.onDone, DEFAULT_NOTIFICATION_PREFS.onDone),
4509
+ quietHours: parseQuietHours(n.quietHours)
4510
+ };
4511
+ }
4128
4512
  function applyKillSwitch(prefs, envValue) {
4129
- return envValue === "0" ? { toast: false, macos: false } : prefs;
4513
+ return envValue === "0" ? { ...prefs, enabled: false, toast: false, macos: false } : prefs;
4514
+ }
4515
+ function readRawConfig() {
4516
+ const path2 = appConfigPath();
4517
+ if (!existsSync14(path2)) return void 0;
4518
+ try {
4519
+ return JSON.parse(readFileSync8(path2, "utf-8"));
4520
+ } catch {
4521
+ return void 0;
4522
+ }
4130
4523
  }
4131
4524
  function readNotificationPrefs() {
4132
- return applyKillSwitch(loadAppConfig().notifications, process.env.TMUX_IDE_NOTIFY);
4525
+ return applyKillSwitch(parseNotificationPrefs(readRawConfig()), process.env.TMUX_IDE_NOTIFY);
4133
4526
  }
4134
- var NOTIFY_STATES, NOTIFY_DEBOUNCE_MS;
4527
+ var NOTIFY_STATES, NOTIFY_DEBOUNCE_MS, NOTIFY_MAX_LEN, DEFAULT_NOTIFICATION_PREFS;
4135
4528
  var init_notify = __esm({
4136
4529
  "packages/daemon/src/tui/chrome/notify.ts"() {
4137
4530
  "use strict";
@@ -4139,13 +4532,22 @@ var init_notify = __esm({
4139
4532
  init_app_config();
4140
4533
  NOTIFY_STATES = /* @__PURE__ */ new Set(["blocked", "done"]);
4141
4534
  NOTIFY_DEBOUNCE_MS = 3e4;
4535
+ NOTIFY_MAX_LEN = 120;
4536
+ DEFAULT_NOTIFICATION_PREFS = {
4537
+ enabled: true,
4538
+ toast: true,
4539
+ macos: false,
4540
+ onBlocked: true,
4541
+ onDone: true,
4542
+ quietHours: null
4543
+ };
4142
4544
  }
4143
4545
  });
4144
4546
 
4145
4547
  // packages/daemon/src/tui/chrome/snapshot.ts
4146
- import { existsSync as existsSync13, mkdirSync as mkdirSync7, readFileSync as readFileSync8, renameSync as renameSync3, writeFileSync as writeFileSync7 } from "node:fs";
4147
- import { homedir as homedir10 } from "node:os";
4148
- import { dirname as dirname9, join as join10 } from "node:path";
4548
+ import { existsSync as existsSync15, mkdirSync as mkdirSync9, readFileSync as readFileSync9, renameSync as renameSync5, writeFileSync as writeFileSync9 } from "node:fs";
4549
+ import { homedir as homedir11 } from "node:os";
4550
+ import { dirname as dirname11, join as join11 } from "node:path";
4149
4551
  import { z as z11 } from "zod";
4150
4552
  function isBareShell(cmd) {
4151
4553
  return /^-?(zsh|bash|sh|fish|dash|ksh|tcsh|csh|nu)$/.test(cmd.trim());
@@ -4260,29 +4662,29 @@ function collectFleetSnapshot(io = defaultIo) {
4260
4662
  return buildSnapshot(rawPanes, rawSessions, io.processTable());
4261
4663
  }
4262
4664
  function snapshotPath() {
4263
- return join10(homedir10(), ".tmux-ide", "snapshot.json");
4665
+ return join11(homedir11(), ".tmux-ide", "snapshot.json");
4264
4666
  }
4265
4667
  function writeSnapshot(snapshot) {
4266
4668
  const path2 = snapshotPath();
4267
4669
  try {
4268
- mkdirSync7(dirname9(path2), { recursive: true });
4670
+ mkdirSync9(dirname11(path2), { recursive: true });
4269
4671
  const tmp = `${path2}.tmp`;
4270
- writeFileSync7(tmp, JSON.stringify(snapshot, null, 2) + "\n");
4271
- if (existsSync13(path2)) {
4672
+ writeFileSync9(tmp, JSON.stringify(snapshot, null, 2) + "\n");
4673
+ if (existsSync15(path2)) {
4272
4674
  try {
4273
- renameSync3(path2, `${path2}.1`);
4675
+ renameSync5(path2, `${path2}.1`);
4274
4676
  } catch {
4275
4677
  }
4276
4678
  }
4277
- renameSync3(tmp, path2);
4679
+ renameSync5(tmp, path2);
4278
4680
  } catch {
4279
4681
  }
4280
4682
  }
4281
4683
  function readSnapshot() {
4282
4684
  const path2 = snapshotPath();
4283
4685
  try {
4284
- if (!existsSync13(path2)) return null;
4285
- const raw = readFileSync8(path2, "utf-8");
4686
+ if (!existsSync15(path2)) return null;
4687
+ const raw = readFileSync9(path2, "utf-8");
4286
4688
  if (raw.trim().length === 0) return null;
4287
4689
  const result = FleetSnapshotSchemaZ.safeParse(JSON.parse(raw));
4288
4690
  return result.success ? result.data : null;
@@ -4379,7 +4781,10 @@ __export(updater_exports, {
4379
4781
  UPDATER_PID_OPTION: () => UPDATER_PID_OPTION,
4380
4782
  UPDATER_SESSION: () => UPDATER_SESSION,
4381
4783
  adoptedSessionsFrom: () => adoptedSessionsFrom,
4784
+ enrichEvents: () => enrichEvents,
4382
4785
  listAdoptedSessions: () => listAdoptedSessions,
4786
+ paneLocation: () => paneLocation,
4787
+ pickRepresentativePane: () => pickRepresentativePane,
4383
4788
  runUpdaterLoop: () => runUpdaterLoop,
4384
4789
  runUpdaterTick: () => runUpdaterTick,
4385
4790
  seedSessionStatus: () => seedSessionStatus,
@@ -4436,7 +4841,7 @@ function runUpdaterTick(deps2) {
4436
4841
  for (const [name, status2] of state) deps2.prevState.set(name, status2);
4437
4842
  if (events.length > 0) {
4438
4843
  deps2.appendEvents(events);
4439
- dispatchNotifications(deps2, events);
4844
+ dispatchNotifications(deps2, enrichEvents(events, panes, deps2.locatePane));
4440
4845
  }
4441
4846
  }
4442
4847
  }
@@ -4452,16 +4857,54 @@ function writeChips(deps2, adopted, panes, theme) {
4452
4857
  writeChip(pane.paneId, chip);
4453
4858
  }
4454
4859
  }
4860
+ function pickRepresentativePane(session, to, panes) {
4861
+ const matching = panes.filter((p) => p.sessionName === session && p.status === to);
4862
+ if (matching.length === 0) return null;
4863
+ return matching.find((p) => p.agent !== null) ?? matching[0];
4864
+ }
4865
+ function enrichEvents(events, panes, locate) {
4866
+ return events.map((ev) => {
4867
+ const notifiable = ev.to === "blocked" || ev.to === "done";
4868
+ const rep = notifiable ? pickRepresentativePane(ev.session, ev.to, panes) : null;
4869
+ return {
4870
+ ...ev,
4871
+ agent: rep?.agent ?? null,
4872
+ location: rep && locate ? locate(rep.paneId) : ev.session
4873
+ };
4874
+ });
4875
+ }
4455
4876
  function dispatchNotifications(deps2, events) {
4456
4877
  const { listClients, lastNotified, now, prefs, sendToasts: toast, sendSystem } = deps2;
4457
4878
  if (!listClients || !lastNotified || !now || !prefs) return;
4879
+ if (!prefs.enabled) return;
4458
4880
  if (!prefs.toast && !prefs.macos) return;
4459
- const decision = decideNotifications(events, listClients(), lastNotified, now());
4881
+ const nowMs = now();
4882
+ const decision = decideNotifications(
4883
+ events,
4884
+ listClients(),
4885
+ lastNotified,
4886
+ nowMs,
4887
+ enabledStates(prefs)
4888
+ );
4460
4889
  lastNotified.clear();
4461
4890
  for (const [key, ts] of decision.nextLastNotified) lastNotified.set(key, ts);
4462
4891
  if (prefs.toast && toast) toast(decision.toasts);
4463
- if (prefs.macos && sendSystem) {
4464
- for (const { message } of decision.system) sendSystem(message);
4892
+ if (prefs.macos && sendSystem && !inQuietHours(new Date(nowMs), prefs.quietHours)) {
4893
+ for (const n of decision.system) sendSystem(n);
4894
+ }
4895
+ }
4896
+ function paneLocation(paneId) {
4897
+ try {
4898
+ const raw = runTmux([
4899
+ "display-message",
4900
+ "-p",
4901
+ "-t",
4902
+ paneId,
4903
+ "#{session_name}:#{window_index}.#{pane_index}"
4904
+ ]).toString().trim();
4905
+ return raw || paneId;
4906
+ } catch {
4907
+ return paneId;
4465
4908
  }
4466
4909
  }
4467
4910
  function dispatchUpdateToast(deps2, version) {
@@ -4553,6 +4996,7 @@ function runUpdaterLoop() {
4553
4996
  prefs: readNotificationPrefs(),
4554
4997
  sendToasts,
4555
4998
  sendSystem: sendSystemNotification,
4999
+ locatePane: paneLocation,
4556
5000
  maybeCheckForUpdate: () => maybeCheckForUpdate({ enabled: config2.updates.check }),
4557
5001
  markUpdateNotified
4558
5002
  });
@@ -4872,12 +5316,12 @@ __export(canonical_daemon_exports, {
4872
5316
  warnOnDaemonVersionSkew: () => warnOnDaemonVersionSkew,
4873
5317
  writeCanonicalDaemonInfo: () => writeCanonicalDaemonInfo
4874
5318
  });
4875
- import { existsSync as existsSync14, mkdirSync as mkdirSync8, readFileSync as readFileSync9, renameSync as renameSync4, rmSync, writeFileSync as writeFileSync8 } from "node:fs";
4876
- import { homedir as homedir11 } from "node:os";
4877
- import { dirname as dirname10, join as join11 } from "node:path";
5319
+ import { existsSync as existsSync16, mkdirSync as mkdirSync10, readFileSync as readFileSync10, renameSync as renameSync6, rmSync, writeFileSync as writeFileSync10 } from "node:fs";
5320
+ import { homedir as homedir12 } from "node:os";
5321
+ import { dirname as dirname12, join as join12 } from "node:path";
4878
5322
  function getCanonicalDaemonInfoPath() {
4879
- const dir = process.env[DAEMON_INFO_DIR_ENV] ?? process.env[REGISTRY_DIR_ENV2] ?? join11(homedir11(), ".tmux-ide");
4880
- return join11(dir, DAEMON_INFO_FILE);
5323
+ const dir = process.env[DAEMON_INFO_DIR_ENV] ?? process.env[REGISTRY_DIR_ENV2] ?? join12(homedir12(), ".tmux-ide");
5324
+ return join12(dir, DAEMON_INFO_FILE);
4881
5325
  }
4882
5326
  function parseCanonicalDaemonInfo(raw) {
4883
5327
  if (!raw || typeof raw !== "object") return null;
@@ -4900,7 +5344,7 @@ function parseCanonicalDaemonInfo(raw) {
4900
5344
  }
4901
5345
  function writeCanonicalDaemonInfo(info) {
4902
5346
  const path2 = getCanonicalDaemonInfoPath();
4903
- mkdirSync8(dirname10(path2), { recursive: true });
5347
+ mkdirSync10(dirname12(path2), { recursive: true });
4904
5348
  const tmpPath = `${path2}.${process.pid}.${Date.now()}.tmp`;
4905
5349
  const persisted = {
4906
5350
  pid: info.pid,
@@ -4910,14 +5354,14 @@ function writeCanonicalDaemonInfo(info) {
4910
5354
  bindHostname: info.bindHostname,
4911
5355
  authToken: info.authToken
4912
5356
  };
4913
- writeFileSync8(tmpPath, JSON.stringify(persisted, null, 2) + "\n", "utf-8");
4914
- renameSync4(tmpPath, path2);
5357
+ writeFileSync10(tmpPath, JSON.stringify(persisted, null, 2) + "\n", "utf-8");
5358
+ renameSync6(tmpPath, path2);
4915
5359
  }
4916
5360
  function readCanonicalDaemonInfo() {
4917
5361
  const path2 = getCanonicalDaemonInfoPath();
4918
- if (!existsSync14(path2)) return null;
5362
+ if (!existsSync16(path2)) return null;
4919
5363
  try {
4920
- return parseCanonicalDaemonInfo(JSON.parse(readFileSync9(path2, "utf-8")));
5364
+ return parseCanonicalDaemonInfo(JSON.parse(readFileSync10(path2, "utf-8")));
4921
5365
  } catch {
4922
5366
  return null;
4923
5367
  }
@@ -5207,13 +5651,13 @@ var init_launch = __esm({
5207
5651
 
5208
5652
  // packages/daemon/src/detect.ts
5209
5653
  import { resolve as resolve10, basename as basename4 } from "node:path";
5210
- import { readFileSync as readFileSync10, existsSync as existsSync15 } from "node:fs";
5654
+ import { readFileSync as readFileSync11, existsSync as existsSync17 } from "node:fs";
5211
5655
  function fileExists(dir, name) {
5212
- return existsSync15(resolve10(dir, name));
5656
+ return existsSync17(resolve10(dir, name));
5213
5657
  }
5214
5658
  function readJson(dir, name) {
5215
5659
  try {
5216
- return JSON.parse(readFileSync10(resolve10(dir, name), "utf-8"));
5660
+ return JSON.parse(readFileSync11(resolve10(dir, name), "utf-8"));
5217
5661
  } catch {
5218
5662
  return null;
5219
5663
  }
@@ -5275,7 +5719,7 @@ function detectStack(dir) {
5275
5719
  detected.language = detected.language ?? "python";
5276
5720
  detected.reasons.push('Detected Python from "pyproject.toml" or "requirements.txt".');
5277
5721
  try {
5278
- const pyproject = readFileSync10(resolve10(dir, "pyproject.toml"), "utf-8");
5722
+ const pyproject = readFileSync11(resolve10(dir, "pyproject.toml"), "utf-8");
5279
5723
  if (pyproject.includes("fastapi"))
5280
5724
  pushFramework(detected, "fastapi", 'Found "fastapi" in pyproject.toml.');
5281
5725
  else if (pyproject.includes("django"))
@@ -5422,28 +5866,28 @@ __export(skill_sync_exports, {
5422
5866
  syncSkill: () => syncSkill,
5423
5867
  versionMarker: () => versionMarker
5424
5868
  });
5425
- import { existsSync as existsSync17, mkdirSync as mkdirSync10, readFileSync as readFileSync12, writeFileSync as writeFileSync10 } from "node:fs";
5426
- import { homedir as homedir12 } from "node:os";
5427
- import { dirname as dirname12, join as join13 } from "node:path";
5869
+ import { existsSync as existsSync19, mkdirSync as mkdirSync12, readFileSync as readFileSync13, writeFileSync as writeFileSync12 } from "node:fs";
5870
+ import { homedir as homedir13 } from "node:os";
5871
+ import { dirname as dirname14, join as join14 } from "node:path";
5428
5872
  import { fileURLToPath as fileURLToPath6 } from "node:url";
5429
5873
  function claudeDir() {
5430
- return process.env.TMUX_IDE_CLAUDE_DIR ?? join13(homedir12(), ".claude");
5874
+ return process.env.TMUX_IDE_CLAUDE_DIR ?? join14(homedir13(), ".claude");
5431
5875
  }
5432
5876
  function skillTargetDir() {
5433
- return join13(claudeDir(), "skills", "tmux-ide");
5877
+ return join14(claudeDir(), "skills", "tmux-ide");
5434
5878
  }
5435
5879
  function skillTargetFile() {
5436
- return join13(skillTargetDir(), "SKILL.md");
5880
+ return join14(skillTargetDir(), "SKILL.md");
5437
5881
  }
5438
5882
  function defaultSkillSource() {
5439
- const here = dirname12(fileURLToPath6(import.meta.url));
5883
+ const here = dirname14(fileURLToPath6(import.meta.url));
5440
5884
  const candidates = [
5441
- join13(here, "../skill/SKILL.md"),
5885
+ join14(here, "../skill/SKILL.md"),
5442
5886
  // bundled bin/cli.js → repo root
5443
- join13(here, "../../../../skill/SKILL.md")
5887
+ join14(here, "../../../../skill/SKILL.md")
5444
5888
  // dev src/lib → repo root
5445
5889
  ];
5446
- return candidates.find((c) => existsSync17(c)) ?? candidates[0];
5890
+ return candidates.find((c) => existsSync19(c)) ?? candidates[0];
5447
5891
  }
5448
5892
  function versionMarker(version) {
5449
5893
  return `<!-- tmux-ide-skill-version: ${version} -->`;
@@ -5457,10 +5901,10 @@ function rewriteVersionMarker(content, version) {
5457
5901
  return content.replace(VERSION_MARKER_RE, versionMarker(version));
5458
5902
  }
5459
5903
  function installedSkillVersion(dir = skillTargetDir()) {
5460
- const file = join13(dir, "SKILL.md");
5461
- if (!existsSync17(file)) return null;
5904
+ const file = join14(dir, "SKILL.md");
5905
+ if (!existsSync19(file)) return null;
5462
5906
  try {
5463
- return parseSkillVersion(readFileSync12(file, "utf-8"));
5907
+ return parseSkillVersion(readFileSync13(file, "utf-8"));
5464
5908
  } catch {
5465
5909
  return null;
5466
5910
  }
@@ -5469,15 +5913,15 @@ function syncSkill({
5469
5913
  source = defaultSkillSource(),
5470
5914
  version = getCurrentVersion()
5471
5915
  } = {}) {
5472
- const rendered = rewriteVersionMarker(readFileSync12(source, "utf-8"), version);
5916
+ const rendered = rewriteVersionMarker(readFileSync13(source, "utf-8"), version);
5473
5917
  const dir = skillTargetDir();
5474
- const target = join13(dir, "SKILL.md");
5475
- const existing = existsSync17(target) ? readFileSync12(target, "utf-8") : null;
5918
+ const target = join14(dir, "SKILL.md");
5919
+ const existing = existsSync19(target) ? readFileSync13(target, "utf-8") : null;
5476
5920
  if (existing === rendered) {
5477
5921
  return { action: "unchanged", path: target, to: version };
5478
5922
  }
5479
- mkdirSync10(dir, { recursive: true });
5480
- writeFileSync10(target, rendered, "utf-8");
5923
+ mkdirSync12(dir, { recursive: true });
5924
+ writeFileSync12(target, rendered, "utf-8");
5481
5925
  if (existing === null) return { action: "installed", path: target, to: version };
5482
5926
  return { action: "updated", path: target, from: parseSkillVersion(existing), to: version };
5483
5927
  }
@@ -5678,8 +6122,8 @@ var init_PtyAdapter = __esm({
5678
6122
  });
5679
6123
 
5680
6124
  // packages/daemon/src/terminal/NodePtyAdapter.ts
5681
- import { chmodSync as chmodSync2, existsSync as existsSync20, statSync as statSync2 } from "node:fs";
5682
- import { dirname as dirname14, join as join14 } from "node:path";
6125
+ import { chmodSync as chmodSync3, existsSync as existsSync22, statSync as statSync2 } from "node:fs";
6126
+ import { dirname as dirname16, join as join15 } from "node:path";
5683
6127
  import { createRequire } from "node:module";
5684
6128
  import * as pty from "node-pty";
5685
6129
  function candidateSpawnHelperPaths() {
@@ -5690,11 +6134,11 @@ function candidateSpawnHelperPaths() {
5690
6134
  } catch {
5691
6135
  return [];
5692
6136
  }
5693
- const pkgDir = dirname14(pkgJsonPath);
6137
+ const pkgDir = dirname16(pkgJsonPath);
5694
6138
  return [
5695
- join14(pkgDir, "build", "Release", "spawn-helper"),
5696
- join14(pkgDir, "build", "Debug", "spawn-helper"),
5697
- join14(pkgDir, "prebuilds", `${process.platform}-${process.arch}`, "spawn-helper")
6139
+ join15(pkgDir, "build", "Release", "spawn-helper"),
6140
+ join15(pkgDir, "build", "Debug", "spawn-helper"),
6141
+ join15(pkgDir, "prebuilds", `${process.platform}-${process.arch}`, "spawn-helper")
5698
6142
  ];
5699
6143
  }
5700
6144
  function ensureNodePtySpawnHelperExecutable(options = {}) {
@@ -5702,9 +6146,9 @@ function ensureNodePtySpawnHelperExecutable(options = {}) {
5702
6146
  if (!options.force && !options.explicitPath && helperEnsured) return;
5703
6147
  const candidates = options.explicitPath ? [options.explicitPath] : candidateSpawnHelperPaths();
5704
6148
  for (const candidate of candidates) {
5705
- if (!existsSync20(candidate)) continue;
6149
+ if (!existsSync22(candidate)) continue;
5706
6150
  try {
5707
- chmodSync2(candidate, 493);
6151
+ chmodSync3(candidate, 493);
5708
6152
  } catch {
5709
6153
  }
5710
6154
  }
@@ -6691,9 +7135,9 @@ var init_pane_comms = __esm({
6691
7135
 
6692
7136
  // packages/daemon/src/lib/workspace-registry.ts
6693
7137
  import { EventEmitter as EventEmitter3 } from "node:events";
6694
- import { existsSync as existsSync21, mkdirSync as mkdirSync11, readFileSync as readFileSync13, renameSync as renameSync6, writeFileSync as writeFileSync11 } from "node:fs";
6695
- import { homedir as homedir13 } from "node:os";
6696
- import { dirname as dirname15, join as join15 } from "node:path";
7138
+ import { existsSync as existsSync23, mkdirSync as mkdirSync13, readFileSync as readFileSync14, renameSync as renameSync8, writeFileSync as writeFileSync13 } from "node:fs";
7139
+ import { homedir as homedir14 } from "node:os";
7140
+ import { dirname as dirname17, join as join16 } from "node:path";
6697
7141
  import { z as z12 } from "zod";
6698
7142
  function getDefaultWorkspaceRegistry() {
6699
7143
  if (!_default) _default = new WorkspaceRegistry();
@@ -6742,7 +7186,7 @@ var init_workspace_registry = __esm({
6742
7186
  workspaces = [];
6743
7187
  loaded = false;
6744
7188
  constructor(options = {}) {
6745
- this.dir = options.dir ?? process.env[REGISTRY_DIR_ENV3] ?? join15(homedir13(), ".tmux-ide");
7189
+ this.dir = options.dir ?? process.env[REGISTRY_DIR_ENV3] ?? join16(homedir14(), ".tmux-ide");
6746
7190
  this.listSessions = options.listSessions ?? defaultListSessions;
6747
7191
  this.emitter.setMaxListeners(0);
6748
7192
  }
@@ -6809,14 +7253,14 @@ var init_workspace_registry = __esm({
6809
7253
  }
6810
7254
  // ----------------- io -----------------
6811
7255
  filePath() {
6812
- return join15(this.dir, "workspaces.json");
7256
+ return join16(this.dir, "workspaces.json");
6813
7257
  }
6814
7258
  readDisk() {
6815
7259
  const path2 = this.filePath();
6816
- if (!existsSync21(path2)) return [];
7260
+ if (!existsSync23(path2)) return [];
6817
7261
  let parsed;
6818
7262
  try {
6819
- parsed = JSON.parse(readFileSync13(path2, "utf-8"));
7263
+ parsed = JSON.parse(readFileSync14(path2, "utf-8"));
6820
7264
  } catch {
6821
7265
  return [];
6822
7266
  }
@@ -6826,11 +7270,11 @@ var init_workspace_registry = __esm({
6826
7270
  }
6827
7271
  writeDisk() {
6828
7272
  const path2 = this.filePath();
6829
- mkdirSync11(dirname15(path2), { recursive: true });
7273
+ mkdirSync13(dirname17(path2), { recursive: true });
6830
7274
  const file = { version: 1, workspaces: this.workspaces };
6831
7275
  const tmp = `${path2}.tmp`;
6832
- writeFileSync11(tmp, JSON.stringify(file, null, 2) + "\n");
6833
- renameSync6(tmp, path2);
7276
+ writeFileSync13(tmp, JSON.stringify(file, null, 2) + "\n");
7277
+ renameSync8(tmp, path2);
6834
7278
  }
6835
7279
  /** @internal Test-only: assert the registry is loaded. */
6836
7280
  _isLoaded() {
@@ -7111,14 +7555,14 @@ var init_auth_token = __esm({
7111
7555
  });
7112
7556
 
7113
7557
  // packages/daemon/src/lib/app-settings.ts
7114
- import { existsSync as existsSync22, mkdirSync as mkdirSync12, readFileSync as readFileSync14, renameSync as renameSync7, writeFileSync as writeFileSync12 } from "node:fs";
7115
- import { dirname as dirname16, join as join16 } from "node:path";
7116
- import { homedir as homedir14 } from "node:os";
7558
+ import { existsSync as existsSync24, mkdirSync as mkdirSync14, readFileSync as readFileSync15, renameSync as renameSync9, writeFileSync as writeFileSync14 } from "node:fs";
7559
+ import { dirname as dirname18, join as join17 } from "node:path";
7560
+ import { homedir as homedir15 } from "node:os";
7117
7561
  function settingsDir() {
7118
- return process.env.TMUX_IDE_SETTINGS_DIR ?? join16(homedir14(), ".tmux-ide");
7562
+ return process.env.TMUX_IDE_SETTINGS_DIR ?? join17(homedir15(), ".tmux-ide");
7119
7563
  }
7120
7564
  function appSettingsPath() {
7121
- return join16(settingsDir(), "app-settings.json");
7565
+ return join17(settingsDir(), "app-settings.json");
7122
7566
  }
7123
7567
  function normalizeSettings(value) {
7124
7568
  if (!value || typeof value !== "object") return structuredClone(DEFAULT_SETTINGS);
@@ -7131,20 +7575,20 @@ function normalizeSettings(value) {
7131
7575
  }
7132
7576
  function readAppSettings() {
7133
7577
  const path2 = appSettingsPath();
7134
- if (!existsSync22(path2)) return structuredClone(DEFAULT_SETTINGS);
7578
+ if (!existsSync24(path2)) return structuredClone(DEFAULT_SETTINGS);
7135
7579
  try {
7136
- return normalizeSettings(JSON.parse(readFileSync14(path2, "utf-8")));
7580
+ return normalizeSettings(JSON.parse(readFileSync15(path2, "utf-8")));
7137
7581
  } catch {
7138
7582
  return structuredClone(DEFAULT_SETTINGS);
7139
7583
  }
7140
7584
  }
7141
7585
  function writeAppSettings(next) {
7142
7586
  const path2 = appSettingsPath();
7143
- mkdirSync12(dirname16(path2), { recursive: true });
7587
+ mkdirSync14(dirname18(path2), { recursive: true });
7144
7588
  const tmp = `${path2}.${process.pid}.${Date.now()}.tmp`;
7145
- writeFileSync12(tmp, `${JSON.stringify(normalizeSettings(next), null, 2)}
7589
+ writeFileSync14(tmp, `${JSON.stringify(normalizeSettings(next), null, 2)}
7146
7590
  `, "utf-8");
7147
- renameSync7(tmp, path2);
7591
+ renameSync9(tmp, path2);
7148
7592
  }
7149
7593
  var DEFAULT_SETTINGS;
7150
7594
  var init_app_settings = __esm({
@@ -7332,16 +7776,16 @@ var init_active_projects = __esm({
7332
7776
 
7333
7777
  // packages/daemon/src/send.ts
7334
7778
  import { randomUUID } from "node:crypto";
7335
- import { resolve as resolve17, join as join17 } from "node:path";
7336
- import { existsSync as existsSync23, mkdirSync as mkdirSync13, writeFileSync as writeFileSync13 } from "node:fs";
7779
+ import { resolve as resolve17, join as join18 } from "node:path";
7780
+ import { existsSync as existsSync25, mkdirSync as mkdirSync15, writeFileSync as writeFileSync15 } from "node:fs";
7337
7781
  function writeDispatchFile(dir, paneId, message) {
7338
7782
  if (message.length <= LONG_MESSAGE_THRESHOLD) return null;
7339
- const dispatchDir = join17(dir, ".tasks", "dispatch");
7340
- if (!existsSync23(dispatchDir)) mkdirSync13(dispatchDir, { recursive: true });
7783
+ const dispatchDir = join18(dir, ".tasks", "dispatch");
7784
+ if (!existsSync25(dispatchDir)) mkdirSync15(dispatchDir, { recursive: true });
7341
7785
  const paneSlug = paneId.replace("%", "");
7342
7786
  const filename = `send-${paneSlug}-${Date.now()}-${randomUUID().slice(0, 8)}.md`;
7343
- const filePath = join17(dispatchDir, filename);
7344
- writeFileSync13(filePath, message);
7787
+ const filePath = join18(dispatchDir, filename);
7788
+ writeFileSync15(filePath, message);
7345
7789
  return { filePath, triggerCmd: `Read and execute: .tasks/dispatch/${filename}` };
7346
7790
  }
7347
7791
  function resolvePane(panes, target) {
@@ -7585,19 +8029,19 @@ var init_schemas = __esm({
7585
8029
  });
7586
8030
 
7587
8031
  // packages/daemon/src/lib/terminals-store.ts
7588
- import { existsSync as existsSync24, mkdirSync as mkdirSync14, readFileSync as readFileSync15, renameSync as renameSync8, writeFileSync as writeFileSync14 } from "node:fs";
7589
- import { dirname as dirname17, join as join18 } from "node:path";
8032
+ import { existsSync as existsSync26, mkdirSync as mkdirSync16, readFileSync as readFileSync16, renameSync as renameSync10, writeFileSync as writeFileSync16 } from "node:fs";
8033
+ import { dirname as dirname19, join as join19 } from "node:path";
7590
8034
  function path(dir) {
7591
- return join18(dir, TERMINALS_FILE);
8035
+ return join19(dir, TERMINALS_FILE);
7592
8036
  }
7593
8037
  function ensureDir(dir) {
7594
- mkdirSync14(dirname17(path(dir)), { recursive: true });
8038
+ mkdirSync16(dirname19(path(dir)), { recursive: true });
7595
8039
  }
7596
8040
  function loadTerminals(dir) {
7597
8041
  const file = path(dir);
7598
- if (!existsSync24(file)) return [];
8042
+ if (!existsSync26(file)) return [];
7599
8043
  try {
7600
- const body = readFileSync15(file, "utf-8");
8044
+ const body = readFileSync16(file, "utf-8");
7601
8045
  const parsed = JSON.parse(body);
7602
8046
  if (!parsed.terminals || !Array.isArray(parsed.terminals)) return [];
7603
8047
  return parsed.terminals.filter((t) => isTerminal(t)).map((t) => ({ ...t }));
@@ -7614,8 +8058,8 @@ function writeAtomic(dir, terminals) {
7614
8058
  ensureDir(dir);
7615
8059
  const file = path(dir);
7616
8060
  const tmp = `${file}.tmp`;
7617
- writeFileSync14(tmp, JSON.stringify({ terminals }, null, 2) + "\n");
7618
- renameSync8(tmp, file);
8061
+ writeFileSync16(tmp, JSON.stringify({ terminals }, null, 2) + "\n");
8062
+ renameSync10(tmp, file);
7619
8063
  }
7620
8064
  function upsertTerminal(dir, input) {
7621
8065
  if (!SAFE_ID.test(input.id)) {
@@ -7676,9 +8120,9 @@ __export(auth_service_exports, {
7676
8120
  AuthService: () => AuthService
7677
8121
  });
7678
8122
  import * as crypto2 from "node:crypto";
7679
- import { readFileSync as readFileSync16, existsSync as existsSync25 } from "node:fs";
7680
- import { join as join19 } from "node:path";
7681
- import { homedir as homedir15 } from "node:os";
8123
+ import { readFileSync as readFileSync17, existsSync as existsSync27 } from "node:fs";
8124
+ import { join as join20 } from "node:path";
8125
+ import { homedir as homedir16 } from "node:os";
7682
8126
  function base64url(buf) {
7683
8127
  const b = typeof buf === "string" ? Buffer.from(buf) : buf;
7684
8128
  return b.toString("base64url");
@@ -7820,10 +8264,10 @@ var init_auth_service = __esm({
7820
8264
  }
7821
8265
  checkSSHKeyAuthorization(userId, publicKey) {
7822
8266
  try {
7823
- const home = userId === process.env.USER ? homedir15() : `/home/${userId}`;
7824
- const authKeysPath = join19(home, ".ssh", "authorized_keys");
7825
- if (!existsSync25(authKeysPath)) return false;
7826
- const authorizedKeys = readFileSync16(authKeysPath, "utf-8");
8267
+ const home = userId === process.env.USER ? homedir16() : `/home/${userId}`;
8268
+ const authKeysPath = join20(home, ".ssh", "authorized_keys");
8269
+ if (!existsSync27(authKeysPath)) return false;
8270
+ const authorizedKeys = readFileSync17(authKeysPath, "utf-8");
7827
8271
  const parts = publicKey.trim().split(" ");
7828
8272
  const keyData = parts.length > 1 ? parts[1] : parts[0];
7829
8273
  return authorizedKeys.includes(keyData);
@@ -8221,11 +8665,11 @@ var init_project_context = __esm({
8221
8665
  });
8222
8666
 
8223
8667
  // packages/daemon/src/command-center/actions/handlers/config-actions.ts
8224
- import { existsSync as existsSync26 } from "node:fs";
8225
- import { join as join20 } from "node:path";
8668
+ import { existsSync as existsSync28 } from "node:fs";
8669
+ import { join as join21 } from "node:path";
8226
8670
  function mutateConfigAction(input, deps2, fn) {
8227
8671
  const context = resolveProjectContext(input, deps2);
8228
- if (!existsSync26(join20(context.dir, "ide.yml"))) {
8672
+ if (!existsSync28(join21(context.dir, "ide.yml"))) {
8229
8673
  throw new ActionError({
8230
8674
  code: "ide_yml_missing",
8231
8675
  message: "ide.yml was not found",
@@ -8648,8 +9092,8 @@ var init_inspect = __esm({
8648
9092
 
8649
9093
  // packages/daemon/src/lib/filesystem-browser.ts
8650
9094
  import { realpathSync, readdirSync as readdirSync3, statSync as statSync4 } from "node:fs";
8651
- import { homedir as homedir16 } from "node:os";
8652
- import { isAbsolute as isAbsolute3, join as join21, resolve as resolve19, sep } from "node:path";
9095
+ import { homedir as homedir17 } from "node:os";
9096
+ import { isAbsolute as isAbsolute3, join as join22, resolve as resolve19, sep } from "node:path";
8653
9097
  function isUnderRoot(canonical, root) {
8654
9098
  if (canonical === root) return true;
8655
9099
  const prefix = root.endsWith(sep) ? root : root + sep;
@@ -8678,7 +9122,7 @@ var init_filesystem_browser = __esm({
8678
9122
  });
8679
9123
 
8680
9124
  // packages/daemon/src/lib/project-inspect.ts
8681
- import { existsSync as existsSync27 } from "node:fs";
9125
+ import { existsSync as existsSync29 } from "node:fs";
8682
9126
  import { isAbsolute as isAbsolute4, resolve as resolve20 } from "node:path";
8683
9127
  function narrowPackageManager(raw) {
8684
9128
  if (!raw) return null;
@@ -8689,7 +9133,7 @@ function inferTestCommand(packageManager) {
8689
9133
  return packageManager === "npm" ? "npm test" : `${packageManager} test`;
8690
9134
  }
8691
9135
  async function inspectProject(dir, io = {}) {
8692
- const exists = io.exists ?? existsSync27;
9136
+ const exists = io.exists ?? existsSync29;
8693
9137
  const absoluteDir = isAbsolute4(dir) ? dir : resolve20(dir);
8694
9138
  if (!exists(absoluteDir)) {
8695
9139
  throw new InspectDirNotFoundError(absoluteDir);
@@ -8730,8 +9174,8 @@ var init_project_inspect = __esm({
8730
9174
 
8731
9175
  // packages/daemon/src/lib/project-onboard.ts
8732
9176
  import yaml2 from "js-yaml";
8733
- import { existsSync as existsSync28 } from "node:fs";
8734
- import { join as join22 } from "node:path";
9177
+ import { existsSync as existsSync30 } from "node:fs";
9178
+ import { join as join23 } from "node:path";
8735
9179
  function composeIdeYmlConfig(input) {
8736
9180
  if (!Number.isInteger(input.agents) || input.agents < 1 || input.agents > 3) {
8737
9181
  throw new OnboardInvalidInputError(
@@ -8789,8 +9233,8 @@ function composeIdeYmlConfig(input) {
8789
9233
  }
8790
9234
  return config2;
8791
9235
  }
8792
- function assertNoExistingIdeYml(dir, exists = existsSync28) {
8793
- const path2 = join22(dir, "ide.yml");
9236
+ function assertNoExistingIdeYml(dir, exists = existsSync30) {
9237
+ const path2 = join23(dir, "ide.yml");
8794
9238
  if (exists(path2)) {
8795
9239
  throw new OnboardConflictError(path2);
8796
9240
  }
@@ -8825,23 +9269,23 @@ __export(server_exports, {
8825
9269
  });
8826
9270
  import { execFile as execFile2 } from "node:child_process";
8827
9271
  import { promisify } from "node:util";
8828
- import { existsSync as existsSync29, readFileSync as readFileSync17, readdirSync as readdirSync4 } from "node:fs";
8829
- import { join as join23, dirname as dirname18, basename as basename8 } from "node:path";
9272
+ import { existsSync as existsSync31, readFileSync as readFileSync18, readdirSync as readdirSync4 } from "node:fs";
9273
+ import { join as join24, dirname as dirname20, basename as basename8 } from "node:path";
8830
9274
  import { fileURLToPath as fileURLToPath8 } from "node:url";
8831
9275
  import { Hono } from "hono";
8832
9276
  import { streamSSE } from "hono/streaming";
8833
9277
  import { cors } from "hono/cors";
8834
9278
  import { zValidator } from "@hono/zod-validator";
8835
9279
  import { realpathSync as realpathSync2 } from "node:fs";
8836
- import { homedir as homedir17 } from "node:os";
9280
+ import { homedir as homedir18 } from "node:os";
8837
9281
  import { isAbsolute as isAbsolute5, resolve as pathResolve } from "node:path";
8838
9282
  import { randomUUID as randomUUID3 } from "node:crypto";
8839
9283
  import { WebSocketServer } from "ws";
8840
9284
  function resolvePackageVersion() {
8841
- const candidates = [join23(__dirname5, "../../package.json"), join23(__dirname5, "../package.json")];
9285
+ const candidates = [join24(__dirname5, "../../package.json"), join24(__dirname5, "../package.json")];
8842
9286
  for (const candidate of candidates) {
8843
9287
  try {
8844
- const parsed = JSON.parse(readFileSync17(candidate, "utf-8"));
9288
+ const parsed = JSON.parse(readFileSync18(candidate, "utf-8"));
8845
9289
  if (typeof parsed.version === "string") return parsed.version;
8846
9290
  } catch {
8847
9291
  }
@@ -8905,7 +9349,7 @@ function sandboxResolveDir(rawDir) {
8905
9349
  if (trimmed.includes("\0")) {
8906
9350
  return { error: "invalid-path", message: "Path contains a null byte", status: 400 };
8907
9351
  }
8908
- const home = process.env.TMUX_IDE_HOME_OVERRIDE && process.env.TMUX_IDE_HOME_OVERRIDE.trim().length > 0 ? process.env.TMUX_IDE_HOME_OVERRIDE : homedir17();
9352
+ const home = process.env.TMUX_IDE_HOME_OVERRIDE && process.env.TMUX_IDE_HOME_OVERRIDE.trim().length > 0 ? process.env.TMUX_IDE_HOME_OVERRIDE : homedir18();
8909
9353
  let candidate = trimmed;
8910
9354
  if (candidate === "~") {
8911
9355
  candidate = home;
@@ -9575,7 +10019,7 @@ function createApp(options = {}) {
9575
10019
  if (!parsed.success) {
9576
10020
  return c.json({ error: "Invalid request", details: parsed.error.issues }, 400);
9577
10021
  }
9578
- if (!existsSync29(parsed.data.dir)) {
10022
+ if (!existsSync31(parsed.data.dir)) {
9579
10023
  return c.json({ error: `Directory "${parsed.data.dir}" does not exist` }, 400);
9580
10024
  }
9581
10025
  const jobId = randomUUID3();
@@ -9681,9 +10125,9 @@ function createApp(options = {}) {
9681
10125
  }
9682
10126
  function listAvailableTemplates() {
9683
10127
  const __filename = fileURLToPath8(import.meta.url);
9684
- const __dir = dirname18(__filename);
9685
- const templatesDir = join23(__dir, "..", "..", "..", "..", "templates");
9686
- if (!existsSync29(templatesDir)) return [];
10128
+ const __dir = dirname20(__filename);
10129
+ const templatesDir = join24(__dir, "..", "..", "..", "..", "templates");
10130
+ if (!existsSync31(templatesDir)) return [];
9687
10131
  const labels = {
9688
10132
  default: { label: "Default", description: "Single Claude pane + dev/shell row" },
9689
10133
  nextjs: {
@@ -9772,7 +10216,7 @@ var init_server = __esm({
9772
10216
  init_filesystem_browser();
9773
10217
  init_project_inspect();
9774
10218
  init_project_onboard();
9775
- __dirname5 = dirname18(fileURLToPath8(import.meta.url));
10219
+ __dirname5 = dirname20(fileURLToPath8(import.meta.url));
9776
10220
  pkgVersion = resolvePackageVersion();
9777
10221
  projectStreamConnections = 0;
9778
10222
  sseMetrics = {
@@ -10975,7 +11419,7 @@ var require_package = __commonJS({
10975
11419
  "package.json"(exports, module) {
10976
11420
  module.exports = {
10977
11421
  name: "tmux-ide",
10978
- version: "2.6.0",
11422
+ version: "2.7.0",
10979
11423
  description: "Turn any project into a tmux-powered terminal IDE with a simple ide.yml",
10980
11424
  type: "module",
10981
11425
  bin: {
@@ -10987,7 +11431,13 @@ var require_package = __commonJS({
10987
11431
  "skill",
10988
11432
  "templates",
10989
11433
  "packages/daemon/dist",
10990
- "!packages/daemon/dist/tui"
11434
+ "!packages/daemon/dist/tui",
11435
+ "packages/daemon/src",
11436
+ "bunfig.toml",
11437
+ "packages/contracts/src",
11438
+ "packages/tmux-bridge/src",
11439
+ "packages/tmux-bridge/package.json",
11440
+ "packages/contracts/package.json"
10991
11441
  ],
10992
11442
  scripts: {
10993
11443
  build: "pnpm build:cli",
@@ -11034,16 +11484,15 @@ var require_package = __commonJS({
11034
11484
  dependencies: {
11035
11485
  "@hono/node-server": "^1.19.11",
11036
11486
  "@hono/zod-validator": "^0.7.6",
11037
- "@opentui/core": "^0.1.88",
11038
- "@opentui/core-darwin-arm64": "^0.1.88",
11039
- "@opentui/solid": "^0.1.88",
11487
+ "@opentui/core": "^0.4.3",
11488
+ "@opentui/solid": "^0.4.3",
11040
11489
  "@parcel/watcher": "^2.5.6",
11041
11490
  "@types/ws": "^8.18.1",
11042
11491
  hono: "^4.12.8",
11043
11492
  ignore: "^7.0.5",
11044
11493
  "js-yaml": "^4.1.1",
11045
11494
  "node-pty": "1.2.0-beta.12",
11046
- "solid-js": "^1.9.11",
11495
+ "solid-js": "1.9.12",
11047
11496
  ws: "^8.20.0",
11048
11497
  zod: "^4.3.6"
11049
11498
  },
@@ -11052,7 +11501,10 @@ var require_package = __commonJS({
11052
11501
  "@parcel/watcher",
11053
11502
  "esbuild",
11054
11503
  "node-pty"
11055
- ]
11504
+ ],
11505
+ overrides: {
11506
+ zod: "^4.3.6"
11507
+ }
11056
11508
  },
11057
11509
  devDependencies: {
11058
11510
  "@eslint/js": "^10.0.1",
@@ -11068,6 +11520,9 @@ var require_package = __commonJS({
11068
11520
  turbo: "^2.3.3",
11069
11521
  typescript: "^5.9.3",
11070
11522
  vitest: "^4.1.0"
11523
+ },
11524
+ optionalDependencies: {
11525
+ "@opentui/core-darwin-arm64": "^0.4.3"
11071
11526
  }
11072
11527
  };
11073
11528
  }
@@ -11098,7 +11553,10 @@ function toFleetJson(projects) {
11098
11553
  active: w.active,
11099
11554
  panes: w.panes,
11100
11555
  status: w.status
11101
- }))
11556
+ })),
11557
+ // A pre-agents TeamSession (older constructor/test) yields `[]` — the
11558
+ // contract always exposes the array.
11559
+ agents: s.agents ?? []
11102
11560
  }))
11103
11561
  }))
11104
11562
  };
@@ -11180,6 +11638,7 @@ function buildReport(target) {
11180
11638
  hint: info.hintRaw
11181
11639
  });
11182
11640
  const manifest = resolved2.manifest;
11641
+ const subtree = manifest ? [] : describeSubtree(table, info.pid);
11183
11642
  const snapshot = { ...readPaneSnapshot(info.id), title: info.title };
11184
11643
  const explained = manifest ? explain(snapshot, manifest) : {
11185
11644
  state: null,
@@ -11201,7 +11660,9 @@ function buildReport(target) {
11201
11660
  resolution: {
11202
11661
  manifestId: manifest?.id ?? null,
11203
11662
  matchedCommand: resolved2.matchedCommand,
11204
- source: resolved2.source
11663
+ source: resolved2.source,
11664
+ confidence: manifest ? manifest.confidence ?? "conservative" : null,
11665
+ subtree
11205
11666
  },
11206
11667
  states: explained.checked,
11207
11668
  winner: explained.state,
@@ -11237,10 +11698,16 @@ function renderReport(r, opts = {}) {
11237
11698
  } else {
11238
11699
  out.push(` ${label("hint")} ${dim4("(unset)")}`);
11239
11700
  }
11240
- const mid = r.resolution.manifestId ?? dim4("none");
11241
- out.push(
11242
- ` ${label("manifest")} ${mid} ${dim4(`via ${r.resolution.source}` + (r.resolution.matchedCommand ? ` "${r.resolution.matchedCommand}"` : ""))}`
11243
- );
11701
+ if (r.resolution.manifestId) {
11702
+ const conf = r.resolution.confidence === "tuned" ? c("\x1B[32m", "tuned") : dim4(r.resolution.confidence ?? "conservative");
11703
+ out.push(
11704
+ ` ${label("manifest")} ${r.resolution.manifestId} ${dim4(`via ${r.resolution.source}` + (r.resolution.matchedCommand ? ` "${r.resolution.matchedCommand}"` : ""))} [${conf}]`
11705
+ );
11706
+ } else {
11707
+ const saw = r.resolution.subtree.length > 0 ? r.resolution.subtree.join(", ") : r.pane.cmd || "(nothing)";
11708
+ out.push(` ${label("manifest")} ${dim4("none matched")} \u2014 ${dim4(`process-tree saw: ${saw}`)}`);
11709
+ out.push(` ${dim4("set `tmux set-option -p @agent_hint <agent>` to force one")}`);
11710
+ }
11244
11711
  out.push("");
11245
11712
  out.push(bold4(" state rules"));
11246
11713
  if (r.states.length === 0) {
@@ -11314,7 +11781,7 @@ __export(worktree_exports, {
11314
11781
  worktreeSessionName: () => worktreeSessionName
11315
11782
  });
11316
11783
  import { execFileSync as execFileSync13 } from "node:child_process";
11317
- import { basename as basename9, dirname as dirname19, isAbsolute as isAbsolute6, join as join24, resolve as resolve22 } from "node:path";
11784
+ import { basename as basename9, dirname as dirname21, isAbsolute as isAbsolute6, join as join25, resolve as resolve22 } from "node:path";
11318
11785
  function sanitizeForTmux(part) {
11319
11786
  return part.replace(/[.:/\s]+/g, "-");
11320
11787
  }
@@ -11323,11 +11790,11 @@ function worktreeSessionName(project, branch) {
11323
11790
  }
11324
11791
  function defaultWorktreeBaseDir(repoDir) {
11325
11792
  const abs = resolve22(repoDir);
11326
- return join24(dirname19(abs), `${basename9(abs)}-worktrees`);
11793
+ return join25(dirname21(abs), `${basename9(abs)}-worktrees`);
11327
11794
  }
11328
11795
  function worktreePath(repoDir, branch, configuredDir) {
11329
11796
  const base = configuredDir && configuredDir.length > 0 ? isAbsolute6(configuredDir) ? configuredDir : resolve22(repoDir, configuredDir) : defaultWorktreeBaseDir(repoDir);
11330
- return join24(base, branch);
11797
+ return join25(base, branch);
11331
11798
  }
11332
11799
  function parseWorktreeList(porcelain) {
11333
11800
  const entries = [];
@@ -11471,8 +11938,8 @@ __export(update_exports, {
11471
11938
  runUpdate: () => runUpdate
11472
11939
  });
11473
11940
  import { execSync as execSync4 } from "node:child_process";
11474
- import { existsSync as existsSync30 } from "node:fs";
11475
- import { dirname as dirname20, join as join25 } from "node:path";
11941
+ import { existsSync as existsSync32 } from "node:fs";
11942
+ import { dirname as dirname22, join as join26 } from "node:path";
11476
11943
  function detectPackageManager(cliPath) {
11477
11944
  const p = cliPath.toLowerCase();
11478
11945
  if (/(^|\/)\.?bun(\/|$)/.test(p)) return "bun";
@@ -11518,8 +11985,8 @@ function renderPlan(plan, { current, latest, dryRun }) {
11518
11985
  function findGitCheckoutRoot(startDir) {
11519
11986
  let dir = startDir;
11520
11987
  for (; ; ) {
11521
- if (existsSync30(join25(dir, ".git"))) return dir;
11522
- const parent = dirname20(dir);
11988
+ if (existsSync32(join26(dir, ".git"))) return dir;
11989
+ const parent = dirname22(dir);
11523
11990
  if (parent === dir) return null;
11524
11991
  dir = parent;
11525
11992
  }
@@ -11651,65 +12118,68 @@ var init_server2 = __esm({
11651
12118
  // bin/cli.ts
11652
12119
  init_launch();
11653
12120
  import { parseArgs } from "node:util";
11654
- import { resolve as resolve23, dirname as dirname21, join as join26 } from "node:path";
12121
+ import { resolve as resolve23, dirname as dirname23, join as join27 } from "node:path";
11655
12122
  import { execFileSync as execFileSync14 } from "node:child_process";
11656
- import { existsSync as existsSync31 } from "node:fs";
12123
+ import { existsSync as existsSync33 } from "node:fs";
11657
12124
  import { fileURLToPath as fileURLToPath9 } from "node:url";
11658
12125
 
11659
12126
  // packages/daemon/src/tui/team/entry.ts
11660
- function shouldOpenCockpit(hasIdeYml, teamFlag) {
11661
- return teamFlag || !hasIdeYml;
12127
+ function resolveEntry(opts) {
12128
+ if (opts.teamFlag) return "cockpit";
12129
+ if (opts.hasIdeYml) return "project";
12130
+ return opts.frontDoor ? "app" : "cockpit";
11662
12131
  }
11663
12132
 
11664
12133
  // bin/cli.ts
12134
+ init_app_config();
11665
12135
  init_compiled();
11666
12136
 
11667
12137
  // packages/daemon/src/init.ts
11668
12138
  init_detect();
11669
12139
  init_output();
11670
12140
  import {
11671
- existsSync as existsSync16,
11672
- readFileSync as readFileSync11,
11673
- writeFileSync as writeFileSync9,
11674
- renameSync as renameSync5,
11675
- mkdirSync as mkdirSync9,
12141
+ existsSync as existsSync18,
12142
+ readFileSync as readFileSync12,
12143
+ writeFileSync as writeFileSync11,
12144
+ renameSync as renameSync7,
12145
+ mkdirSync as mkdirSync11,
11676
12146
  readdirSync as readdirSync2,
11677
12147
  copyFileSync as copyFileSync2
11678
12148
  } from "node:fs";
11679
- import { resolve as resolve11, join as join12, basename as basename5, dirname as dirname11 } from "node:path";
12149
+ import { resolve as resolve11, join as join13, basename as basename5, dirname as dirname13 } from "node:path";
11680
12150
  import { fileURLToPath as fileURLToPath5 } from "node:url";
11681
- var __dirname4 = dirname11(fileURLToPath5(import.meta.url));
12151
+ var __dirname4 = dirname13(fileURLToPath5(import.meta.url));
11682
12152
  function copyTemplateSkills(targetDir) {
11683
12153
  const created = [];
11684
12154
  const templateSkillsDir = resolve11(__dirname4, "..", "..", "..", "templates", "skills");
11685
- if (!existsSync16(templateSkillsDir)) return created;
11686
- mkdirSync9(targetDir, { recursive: true });
12155
+ if (!existsSync18(templateSkillsDir)) return created;
12156
+ mkdirSync11(targetDir, { recursive: true });
11687
12157
  for (const file of readdirSync2(templateSkillsDir)) {
11688
12158
  if (!file.endsWith(".md")) continue;
11689
- const destination = join12(targetDir, file);
11690
- copyFileSync2(join12(templateSkillsDir, file), destination);
12159
+ const destination = join13(targetDir, file);
12160
+ copyFileSync2(join13(templateSkillsDir, file), destination);
11691
12161
  created.push(destination);
11692
12162
  }
11693
12163
  return created;
11694
12164
  }
11695
12165
  function scaffoldLibraryStubs(dir) {
11696
12166
  const created = [];
11697
- const libraryDir = join12(dir, ".tmux-ide", "library");
11698
- if (!existsSync16(libraryDir)) {
11699
- mkdirSync9(libraryDir, { recursive: true });
12167
+ const libraryDir = join13(dir, ".tmux-ide", "library");
12168
+ if (!existsSync18(libraryDir)) {
12169
+ mkdirSync11(libraryDir, { recursive: true });
11700
12170
  created.push(libraryDir);
11701
12171
  }
11702
- const archPath = join12(libraryDir, "architecture.md");
11703
- if (!existsSync16(archPath)) {
11704
- writeFileSync9(
12172
+ const archPath = join13(libraryDir, "architecture.md");
12173
+ if (!existsSync18(archPath)) {
12174
+ writeFileSync11(
11705
12175
  archPath,
11706
12176
  "# Architecture\n\n<!-- Describe your project's architecture here. This context is injected into agent dispatch prompts. -->\n"
11707
12177
  );
11708
12178
  created.push(archPath);
11709
12179
  }
11710
- const learningsPath = join12(libraryDir, "learnings.md");
11711
- if (!existsSync16(learningsPath)) {
11712
- writeFileSync9(
12180
+ const learningsPath = join13(libraryDir, "learnings.md");
12181
+ if (!existsSync18(learningsPath)) {
12182
+ writeFileSync11(
11713
12183
  learningsPath,
11714
12184
  "# Learnings\n\n<!-- Task summaries are automatically appended here by the orchestrator. -->\n"
11715
12185
  );
@@ -11719,13 +12189,13 @@ function scaffoldLibraryStubs(dir) {
11719
12189
  }
11720
12190
  function scaffoldValidationContract(dir) {
11721
12191
  const created = [];
11722
- const tasksDir = join12(dir, ".tasks");
11723
- if (!existsSync16(tasksDir)) {
11724
- mkdirSync9(tasksDir, { recursive: true });
12192
+ const tasksDir = join13(dir, ".tasks");
12193
+ if (!existsSync18(tasksDir)) {
12194
+ mkdirSync11(tasksDir, { recursive: true });
11725
12195
  }
11726
- const contractPath = join12(tasksDir, "validation-contract.md");
11727
- if (!existsSync16(contractPath)) {
11728
- writeFileSync9(
12196
+ const contractPath = join13(tasksDir, "validation-contract.md");
12197
+ if (!existsSync18(contractPath)) {
12198
+ writeFileSync11(
11729
12199
  contractPath,
11730
12200
  "# Validation Contract\n\n<!-- Define assertions that the validator agent will verify. Example: -->\n<!-- - VAL-001: All tests pass -->\n<!-- - VAL-002: No TypeScript errors -->\n<!-- - VAL-003: Lint passes with zero warnings -->\n"
11731
12201
  );
@@ -11736,11 +12206,11 @@ function scaffoldValidationContract(dir) {
11736
12206
  function scaffoldAgentsMd(dir, name) {
11737
12207
  const created = [];
11738
12208
  const agentsTemplatePath = resolve11(__dirname4, "..", "..", "..", "templates", "AGENTS.md");
11739
- if (existsSync16(agentsTemplatePath)) {
11740
- const agentsPath = join12(dir, "AGENTS.md");
11741
- if (!existsSync16(agentsPath)) {
11742
- const content = readFileSync11(agentsTemplatePath, "utf-8").replace(/{{name}}/g, name);
11743
- writeFileSync9(agentsPath, content);
12209
+ if (existsSync18(agentsTemplatePath)) {
12210
+ const agentsPath = join13(dir, "AGENTS.md");
12211
+ if (!existsSync18(agentsPath)) {
12212
+ const content = readFileSync12(agentsTemplatePath, "utf-8").replace(/{{name}}/g, name);
12213
+ writeFileSync11(agentsPath, content);
11744
12214
  created.push(agentsPath);
11745
12215
  }
11746
12216
  }
@@ -11758,7 +12228,7 @@ function scaffoldTeamWorkspace(dir, name) {
11758
12228
  }
11759
12229
  function scaffoldMissionsWorkspace(dir, name) {
11760
12230
  const created = [];
11761
- const skillsDir = join12(dir, ".tmux-ide", "skills");
12231
+ const skillsDir = join13(dir, ".tmux-ide", "skills");
11762
12232
  created.push(...copyTemplateSkills(skillsDir));
11763
12233
  created.push(...scaffoldTeamWorkspace(dir, name));
11764
12234
  return created;
@@ -11769,30 +12239,30 @@ async function init({
11769
12239
  } = {}) {
11770
12240
  const dir = process.cwd();
11771
12241
  const configPath = resolve11(dir, "ide.yml");
11772
- if (existsSync16(configPath)) {
12242
+ if (existsSync18(configPath)) {
11773
12243
  outputError("ide.yml already exists in this directory", "EXISTS");
11774
12244
  }
11775
12245
  if (template) {
11776
12246
  const templatePath = resolve11(__dirname4, "..", "..", "..", "templates", `${template}.yml`);
11777
- if (!existsSync16(templatePath)) {
12247
+ if (!existsSync18(templatePath)) {
11778
12248
  outputError(`Template "${template}" not found`, "NOT_FOUND");
11779
12249
  }
11780
- let content = readFileSync11(templatePath, "utf-8");
12250
+ let content = readFileSync12(templatePath, "utf-8");
11781
12251
  const name2 = basename5(dir);
11782
12252
  content = content.replace(/^name: .+/m, `name: ${name2}`);
11783
12253
  const tmpPath = configPath + ".tmp";
11784
- writeFileSync9(tmpPath, content);
11785
- renameSync5(tmpPath, configPath);
12254
+ writeFileSync11(tmpPath, content);
12255
+ renameSync7(tmpPath, configPath);
11786
12256
  let created;
11787
12257
  if (template === "missions") {
11788
12258
  created = scaffoldMissionsWorkspace(dir, name2);
11789
12259
  } else if (isTeamTemplate(template)) {
11790
12260
  created = [
11791
- ...copyTemplateSkills(join12(dir, ".tmux-ide", "skills")),
12261
+ ...copyTemplateSkills(join13(dir, ".tmux-ide", "skills")),
11792
12262
  ...scaffoldTeamWorkspace(dir, name2)
11793
12263
  ];
11794
12264
  } else {
11795
- created = copyTemplateSkills(join12(dir, ".tmux-ide", "skills"));
12265
+ created = copyTemplateSkills(join13(dir, ".tmux-ide", "skills"));
11796
12266
  }
11797
12267
  if (json2) {
11798
12268
  console.log(JSON.stringify({ created: true, template, name: name2, paths: created }));
@@ -11812,8 +12282,8 @@ async function init({
11812
12282
  const config2 = suggestConfig(dir, detected);
11813
12283
  const yaml3 = (await import("js-yaml")).default;
11814
12284
  const tmpPath2 = configPath + ".tmp";
11815
- writeFileSync9(tmpPath2, yaml3.dump(config2, { lineWidth: -1, noRefs: true, quotingType: '"' }));
11816
- renameSync5(tmpPath2, configPath);
12285
+ writeFileSync11(tmpPath2, yaml3.dump(config2, { lineWidth: -1, noRefs: true, quotingType: '"' }));
12286
+ renameSync7(tmpPath2, configPath);
11817
12287
  const desc = detected.frameworks.join(" + ");
11818
12288
  if (json2) {
11819
12289
  console.log(JSON.stringify({ created: true, detected: detected.frameworks, name }));
@@ -11824,11 +12294,11 @@ async function init({
11824
12294
  }
11825
12295
  } else {
11826
12296
  const templatePath = resolve11(__dirname4, "..", "..", "..", "templates", "default.yml");
11827
- let content = readFileSync11(templatePath, "utf-8");
12297
+ let content = readFileSync12(templatePath, "utf-8");
11828
12298
  content = content.replace(/^name: .+/m, `name: ${name}`);
11829
12299
  const tmpPath3 = configPath + ".tmp";
11830
- writeFileSync9(tmpPath3, content);
11831
- renameSync5(tmpPath3, configPath);
12300
+ writeFileSync11(tmpPath3, content);
12301
+ renameSync7(tmpPath3, configPath);
11832
12302
  if (json2) {
11833
12303
  console.log(JSON.stringify({ created: true, template: "default", name }));
11834
12304
  } else {
@@ -11838,8 +12308,8 @@ async function init({
11838
12308
  console.log("Edit it to configure your workspace, then run: tmux-ide");
11839
12309
  }
11840
12310
  }
11841
- const skillsDir = join12(dir, ".tmux-ide", "skills");
11842
- if (!existsSync16(skillsDir)) {
12311
+ const skillsDir = join13(dir, ".tmux-ide", "skills");
12312
+ if (!existsSync18(skillsDir)) {
11843
12313
  const created = copyTemplateSkills(skillsDir);
11844
12314
  if (created.length > 0 && !json2) {
11845
12315
  console.log("Copied built-in skill templates to .tmux-ide/skills/");
@@ -11927,8 +12397,8 @@ init_skill_sync();
11927
12397
  init_agent_discovery();
11928
12398
  init_compiled();
11929
12399
  import { execSync as execSync3 } from "node:child_process";
11930
- import { existsSync as existsSync18 } from "node:fs";
11931
- import { resolve as resolve14, dirname as dirname13 } from "node:path";
12400
+ import { existsSync as existsSync20 } from "node:fs";
12401
+ import { resolve as resolve14, dirname as dirname15 } from "node:path";
11932
12402
  import { fileURLToPath as fileURLToPath7 } from "node:url";
11933
12403
  function agentIntegrationRows(agents) {
11934
12404
  return presentAgents(agents).map((agent) => {
@@ -11998,7 +12468,7 @@ async function doctor({
11998
12468
  checks.push(
11999
12469
  check("ide.yml exists", () => {
12000
12470
  const path2 = resolve14(".", "ide.yml");
12001
- if (!existsSync18(path2)) throw new Error("not found in current directory");
12471
+ if (!existsSync20(path2)) throw new Error("not found in current directory");
12002
12472
  return "found";
12003
12473
  })
12004
12474
  );
@@ -12006,11 +12476,11 @@ async function doctor({
12006
12476
  check(
12007
12477
  "TUI surfaces (cockpit / widgets)",
12008
12478
  () => {
12009
- const here = dirname13(fileURLToPath7(import.meta.url));
12479
+ const here = dirname15(fileURLToPath7(import.meta.url));
12010
12480
  const checkoutEntry = [
12011
12481
  resolve14(here, "../packages/daemon/src/tui/team/index.tsx"),
12012
12482
  resolve14(here, "tui/team/index.tsx")
12013
- ].find(existsSync18);
12483
+ ].find(existsSync20);
12014
12484
  const binary = findCompiledTui();
12015
12485
  if (checkoutEntry && isBunAvailable()) return "dev checkout (bun)";
12016
12486
  if (binary) return `compiled binary (${binary})`;
@@ -12113,11 +12583,11 @@ init_yaml_io();
12113
12583
  init_src();
12114
12584
  init_canonical_daemon();
12115
12585
  import { resolve as resolve15 } from "node:path";
12116
- import { existsSync as existsSync19 } from "node:fs";
12586
+ import { existsSync as existsSync21 } from "node:fs";
12117
12587
  async function status(targetDir, { json: json2 } = {}) {
12118
12588
  const dir = resolve15(targetDir ?? ".");
12119
12589
  const { name: session } = getSessionName(dir);
12120
- const configExists = existsSync19(resolve15(dir, "ide.yml"));
12590
+ const configExists = existsSync21(resolve15(dir, "ide.yml"));
12121
12591
  const state = getSessionState(session);
12122
12592
  const running = state.running;
12123
12593
  let panes = [];
@@ -12569,7 +13039,9 @@ function reportPlan(plan, snapshot, { json: json2, dryRun, restored, launched, r
12569
13039
  init_send();
12570
13040
  init_errors2();
12571
13041
  init_output();
12572
- var __dirname6 = dirname21(fileURLToPath9(import.meta.url));
13042
+ var __dirname6 = dirname23(fileURLToPath9(import.meta.url));
13043
+ var selfPath = fileURLToPath9(import.meta.url);
13044
+ var nodeCliPath = selfPath.endsWith(".js") ? selfPath : resolve23(__dirname6, "cli.js");
12573
13045
  var { positionals, values } = parseArgs({
12574
13046
  allowPositionals: true,
12575
13047
  strict: false,
@@ -12647,6 +13119,7 @@ var knownCommands = /* @__PURE__ */ new Set([
12647
13119
  "send",
12648
13120
  "settings",
12649
13121
  "team",
13122
+ "app",
12650
13123
  "switcher",
12651
13124
  "wait",
12652
13125
  "events",
@@ -12707,6 +13180,7 @@ ${bold3("Usage:")}
12707
13180
  ${dim3("(--resume-agents revives claude conversations via claude --resume)")}
12708
13181
  ${cyan2("tmux-ide attach")} ${dim3("Reattach to a running session")}
12709
13182
  ${cyan2("tmux-ide team")} [--json] ${dim3("TUI over all tmux sessions (--json prints fleet state)")}
13183
+ ${cyan2("tmux-ide app")} [session] ${dim3("Unified app: fleet home + live session mirror (bare = home)")}
12710
13184
  ${cyan2("tmux-ide switcher")} ${dim3("Compact session picker (opens in the M-p popup on adopted sessions)")}
12711
13185
  ${cyan2("tmux-ide wait agent-status")} <session> --status <s> [--timeout <ms>]
12712
13186
  ${dim3("Block until a session reaches a status (exit 0 match / 1 timeout)")}
@@ -12774,18 +13248,23 @@ function execBunWidget(surface, scriptPath, args, commandLabel, extraEnv = {}) {
12774
13248
  surface,
12775
13249
  scriptPath,
12776
13250
  args,
12777
- checkoutExists: existsSync31(scriptPath),
13251
+ checkoutExists: existsSync33(scriptPath),
12778
13252
  bunAvailable: isBunAvailable(),
12779
13253
  compiledBinary: findCompiledTui()
12780
13254
  });
12781
13255
  if (launch2.mode === "unavailable") {
12782
13256
  throw new IdeError(
12783
13257
  `\`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.`,
13258
+ Install bun (https://bun.sh) \u2014 the TUI surfaces run on it. Sources ship with the npm package since v2.6.1.`,
12785
13259
  { code: "USAGE", exitCode: 1 }
12786
13260
  );
12787
13261
  }
12788
- const env = { ...process.env, TMUX_IDE_CWD: process.cwd(), ...extraEnv };
13262
+ const env = {
13263
+ ...process.env,
13264
+ TMUX_IDE_CWD: process.cwd(),
13265
+ TMUX_IDE_CLI: nodeCliPath,
13266
+ ...extraEnv
13267
+ };
12789
13268
  if (launch2.mode === "bun") {
12790
13269
  execFileSync14(launch2.bin, launch2.argv, {
12791
13270
  stdio: "inherit",
@@ -12803,9 +13282,13 @@ async function printFleetJson() {
12803
13282
  console.log(JSON.stringify(toFleetJson2(listTeamProjects2(createStatusTracker2())), null, 2));
12804
13283
  }
12805
13284
  var teamScriptPath = resolve23(__dirname6, "../packages/daemon/src/tui/team/index.tsx");
13285
+ var appScriptPath = resolve23(__dirname6, "../packages/daemon/src/tui/mirror/app.tsx");
12806
13286
  function launchTeamCockpit() {
12807
13287
  execBunWidget("team", teamScriptPath, [], "team");
12808
13288
  }
13289
+ function launchApp() {
13290
+ execBunWidget("app", appScriptPath, [], "app");
13291
+ }
12809
13292
  try {
12810
13293
  switch (command) {
12811
13294
  case "start": {
@@ -12823,13 +13306,19 @@ try {
12823
13306
  }
12824
13307
  }
12825
13308
  const targetDir = resolve23(startTargetDir || ".");
12826
- const hasIdeYml = existsSync31(join26(targetDir, "ide.yml"));
12827
- if (shouldOpenCockpit(hasIdeYml, values.team === true)) {
13309
+ const hasIdeYml = existsSync33(join27(targetDir, "ide.yml"));
13310
+ const entry = resolveEntry({
13311
+ hasIdeYml,
13312
+ teamFlag: values.team === true,
13313
+ frontDoor: loadAppConfig().app.frontDoor
13314
+ });
13315
+ if (entry !== "project") {
12828
13316
  if (json) {
12829
13317
  await printFleetJson();
12830
13318
  break;
12831
13319
  }
12832
- launchTeamCockpit();
13320
+ if (entry === "app") launchApp();
13321
+ else launchTeamCockpit();
12833
13322
  break;
12834
13323
  }
12835
13324
  await launch(startTargetDir, { json });
@@ -12929,8 +13418,8 @@ try {
12929
13418
  const messageStart = values.to ? 1 : 2;
12930
13419
  let message = positionals.slice(messageStart).join(" ");
12931
13420
  if (!message && !process.stdin.isTTY) {
12932
- const { readFileSync: readFileSync18 } = await import("node:fs");
12933
- message = readFileSync18(0, "utf-8").trim();
13421
+ const { readFileSync: readFileSync19 } = await import("node:fs");
13422
+ message = readFileSync19(0, "utf-8").trim();
12934
13423
  }
12935
13424
  await send(null, { json, to: target, message, noEnter: values["no-enter"] });
12936
13425
  break;
@@ -12955,6 +13444,12 @@ try {
12955
13444
  launchTeamCockpit();
12956
13445
  break;
12957
13446
  }
13447
+ case "app": {
13448
+ const session = positionals[1];
13449
+ const appArgs = session ? [`--target=${session}`] : [];
13450
+ execBunWidget("app", appScriptPath, appArgs, "app");
13451
+ break;
13452
+ }
12958
13453
  case "switcher": {
12959
13454
  const clientArg = typeof values.client === "string" ? values.client : "";
12960
13455
  execBunWidget("team", teamScriptPath, [], "switcher", { TMUX_IDE_PICKER_CLIENT: clientArg });
@@ -13047,10 +13542,10 @@ try {
13047
13542
  }
13048
13543
  }
13049
13544
  case "events": {
13050
- const { readFileSync: readFileSync18, existsSync: existsSync32, statSync: statSync5, openSync, readSync, closeSync } = await import("node:fs");
13545
+ const { readFileSync: readFileSync19, existsSync: existsSync34, statSync: statSync5, openSync, readSync, closeSync } = await import("node:fs");
13051
13546
  const { eventsPath: eventsPath2, formatEventLine: formatEventLine2 } = await Promise.resolve().then(() => (init_events(), events_exports));
13052
13547
  const path2 = eventsPath2();
13053
- if (!existsSync32(path2)) {
13548
+ if (!existsSync34(path2)) {
13054
13549
  console.log("no events yet \u2014 is a session adopted? (the chrome updater writes events)");
13055
13550
  break;
13056
13551
  }
@@ -13070,7 +13565,7 @@ try {
13070
13565
  } catch {
13071
13566
  }
13072
13567
  };
13073
- const allLines = readFileSync18(path2, "utf8").split("\n").filter((l) => l.trim().length > 0);
13568
+ const allLines = readFileSync19(path2, "utf8").split("\n").filter((l) => l.trim().length > 0);
13074
13569
  for (const line of allLines.slice(-50)) printLine(line);
13075
13570
  if (!values.follow) break;
13076
13571
  let offset = statSync5(path2).size;
@@ -13477,7 +13972,7 @@ Known panels: ${POPUP_WIDGETS2.join(", ")}.`,
13477
13972
  const mainPath = worktrees[0]?.path ?? repoDir;
13478
13973
  const projectName = getSessionName2(mainPath).name;
13479
13974
  async function openWorktreeSession(wtPath, name) {
13480
- if (existsSync31(join26(wtPath, "ide.yml"))) {
13975
+ if (existsSync33(join27(wtPath, "ide.yml"))) {
13481
13976
  await launch(wtPath, { attach: false, sessionName: name });
13482
13977
  } else {
13483
13978
  if (!hasSession2(name)) createDetachedSession2(name, wtPath);
@@ -13614,6 +14109,16 @@ Known panels: ${POPUP_WIDGETS2.join(", ")}.`,
13614
14109
  break;
13615
14110
  }
13616
14111
  case "update": {
14112
+ if (values["tui-binary"] === true) {
14113
+ const { downloadTuiBinary: downloadTuiBinary2 } = await Promise.resolve().then(() => (init_tui_binary(), tui_binary_exports));
14114
+ const { path: path2 } = await downloadTuiBinary2({ log: (m) => console.error(m) });
14115
+ if (json) {
14116
+ console.log(JSON.stringify({ ok: true, path: path2 }, null, 2));
14117
+ } else {
14118
+ console.log(`TUI binary ready: ${path2}`);
14119
+ }
14120
+ break;
14121
+ }
13617
14122
  const { runUpdate: runUpdate2 } = await Promise.resolve().then(() => (init_update(), update_exports));
13618
14123
  const dryRun = values["dry-run"] === true;
13619
14124
  const plan = runUpdate2({ cliDir: __dirname6, dryRun });