tmux-ide 2.6.0 → 2.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/bin/cli.js +13 -5
  2. package/bin/cli.ts +1 -1
  3. package/bunfig.toml +4 -0
  4. package/package.json +11 -3
  5. package/packages/contracts/package.json +22 -0
  6. package/packages/contracts/src/__tests__/ide-config.test.ts +46 -0
  7. package/packages/contracts/src/__tests__/terminals.test.ts +87 -0
  8. package/packages/contracts/src/actions-contract.ts +310 -0
  9. package/packages/contracts/src/actions-errors.ts +41 -0
  10. package/packages/contracts/src/domain.ts +36 -0
  11. package/packages/contracts/src/ide-config.ts +170 -0
  12. package/packages/contracts/src/index.ts +24 -0
  13. package/packages/contracts/src/lib-internal/auth.ts +13 -0
  14. package/packages/contracts/src/lib-internal/hq.ts +38 -0
  15. package/packages/contracts/src/terminals.ts +116 -0
  16. package/packages/contracts/src/tmux.ts +60 -0
  17. package/packages/contracts/src/workspace.ts +67 -0
  18. package/packages/daemon/src/agent-explain.ts +270 -0
  19. package/packages/daemon/src/attach.ts +20 -0
  20. package/packages/daemon/src/bin.ts +4 -0
  21. package/packages/daemon/src/canonical.ts +7 -0
  22. package/packages/daemon/src/cli.ts +499 -0
  23. package/packages/daemon/src/command-center/actions/contract.ts +2 -0
  24. package/packages/daemon/src/command-center/actions/dispatcher.ts +137 -0
  25. package/packages/daemon/src/command-center/actions/errors.ts +105 -0
  26. package/packages/daemon/src/command-center/actions/handlers/_project-context.ts +30 -0
  27. package/packages/daemon/src/command-center/actions/handlers/_resolve-project.ts +78 -0
  28. package/packages/daemon/src/command-center/actions/handlers/app-set-remote-access.ts +118 -0
  29. package/packages/daemon/src/command-center/actions/handlers/config-actions.ts +113 -0
  30. package/packages/daemon/src/command-center/actions/handlers/daemon-shutdown.ts +38 -0
  31. package/packages/daemon/src/command-center/actions/handlers/project-activate.ts +30 -0
  32. package/packages/daemon/src/command-center/actions/handlers/project-launch.ts +70 -0
  33. package/packages/daemon/src/command-center/actions/handlers/project-open-terminal.ts +87 -0
  34. package/packages/daemon/src/command-center/actions/handlers/project-restart.ts +38 -0
  35. package/packages/daemon/src/command-center/actions/handlers/project-stop.ts +62 -0
  36. package/packages/daemon/src/command-center/actions/handlers/terminal-respawn.ts +119 -0
  37. package/packages/daemon/src/command-center/actions/handlers/terminal-stop.ts +35 -0
  38. package/packages/daemon/src/command-center/actions/registry.ts +149 -0
  39. package/packages/daemon/src/command-center/discovery.ts +96 -0
  40. package/packages/daemon/src/command-center/index.ts +31 -0
  41. package/packages/daemon/src/command-center/schemas.ts +85 -0
  42. package/packages/daemon/src/command-center/server.ts +1260 -0
  43. package/packages/daemon/src/command-center/ws-events.ts +316 -0
  44. package/packages/daemon/src/config.ts +549 -0
  45. package/packages/daemon/src/detect.ts +248 -0
  46. package/packages/daemon/src/doctor.ts +242 -0
  47. package/packages/daemon/src/embed.ts +5 -0
  48. package/packages/daemon/src/index.ts +12 -0
  49. package/packages/daemon/src/init.ts +211 -0
  50. package/packages/daemon/src/inspect.ts +178 -0
  51. package/packages/daemon/src/js-yaml.d.ts +10 -0
  52. package/packages/daemon/src/launch.ts +349 -0
  53. package/packages/daemon/src/lib/active-projects.ts +49 -0
  54. package/packages/daemon/src/lib/agent-discovery.ts +121 -0
  55. package/packages/daemon/src/lib/app-config.ts +336 -0
  56. package/packages/daemon/src/lib/app-settings.ts +53 -0
  57. package/packages/daemon/src/lib/auth/auth-service.ts +227 -0
  58. package/packages/daemon/src/lib/auth/middleware.ts +56 -0
  59. package/packages/daemon/src/lib/auth/types.ts +2 -0
  60. package/packages/daemon/src/lib/auth-token.ts +5 -0
  61. package/packages/daemon/src/lib/authorship.ts +280 -0
  62. package/packages/daemon/src/lib/canonical-daemon.ts +122 -0
  63. package/packages/daemon/src/lib/cli-action-bridge.ts +216 -0
  64. package/packages/daemon/src/lib/daemon-embed.ts +782 -0
  65. package/packages/daemon/src/lib/daemon-watchdog.ts +111 -0
  66. package/packages/daemon/src/lib/daemon.ts +79 -0
  67. package/packages/daemon/src/lib/dot-path.ts +17 -0
  68. package/packages/daemon/src/lib/errors.ts +67 -0
  69. package/packages/daemon/src/lib/filesystem-browser.ts +292 -0
  70. package/packages/daemon/src/lib/launch-plan.ts +90 -0
  71. package/packages/daemon/src/lib/log.ts +134 -0
  72. package/packages/daemon/src/lib/output.ts +76 -0
  73. package/packages/daemon/src/lib/project-init-runner.ts +150 -0
  74. package/packages/daemon/src/lib/project-inspect.ts +80 -0
  75. package/packages/daemon/src/lib/project-onboard.ts +149 -0
  76. package/packages/daemon/src/lib/project-probe.ts +92 -0
  77. package/packages/daemon/src/lib/project-registry.ts +296 -0
  78. package/packages/daemon/src/lib/session-monitor.ts +122 -0
  79. package/packages/daemon/src/lib/session-options.ts +100 -0
  80. package/packages/daemon/src/lib/shell.ts +8 -0
  81. package/packages/daemon/src/lib/sizes.ts +36 -0
  82. package/packages/daemon/src/lib/skill-sync.ts +155 -0
  83. package/packages/daemon/src/lib/slugify.ts +10 -0
  84. package/packages/daemon/src/lib/terminals-store.ts +125 -0
  85. package/packages/daemon/src/lib/update-check.ts +298 -0
  86. package/packages/daemon/src/lib/update.ts +158 -0
  87. package/packages/daemon/src/lib/workspace-registry.ts +229 -0
  88. package/packages/daemon/src/lib/worktree.ts +289 -0
  89. package/packages/daemon/src/lib/yaml-io.ts +27 -0
  90. package/packages/daemon/src/ls.ts +40 -0
  91. package/packages/daemon/src/restart.ts +24 -0
  92. package/packages/daemon/src/restore.ts +514 -0
  93. package/packages/daemon/src/schemas/domain.ts +2 -0
  94. package/packages/daemon/src/schemas/filesystem.ts +34 -0
  95. package/packages/daemon/src/schemas/ide-config.ts +2 -0
  96. package/packages/daemon/src/schemas/index.ts +59 -0
  97. package/packages/daemon/src/schemas/inspect.ts +67 -0
  98. package/packages/daemon/src/schemas/registry.ts +55 -0
  99. package/packages/daemon/src/schemas/ws-events.ts +135 -0
  100. package/packages/daemon/src/send.ts +171 -0
  101. package/packages/daemon/src/server/README.md +15 -0
  102. package/packages/daemon/src/server/index.ts +74 -0
  103. package/packages/daemon/src/server/pty-bridge.ts +532 -0
  104. package/packages/daemon/src/server/standalone.ts +18 -0
  105. package/packages/daemon/src/server/ws-route.ts +483 -0
  106. package/packages/daemon/src/status.ts +59 -0
  107. package/packages/daemon/src/stop.ts +28 -0
  108. package/packages/daemon/src/terminal/NodePtyAdapter.ts +271 -0
  109. package/packages/daemon/src/terminal/PtyAdapter.ts +140 -0
  110. package/packages/daemon/src/terminal/README.md +92 -0
  111. package/packages/daemon/src/tui/chrome/cheatsheet.ts +260 -0
  112. package/packages/daemon/src/tui/chrome/chip.ts +30 -0
  113. package/packages/daemon/src/tui/chrome/events.ts +119 -0
  114. package/packages/daemon/src/tui/chrome/kitty-keys.ts +55 -0
  115. package/packages/daemon/src/tui/chrome/menu.ts +289 -0
  116. package/packages/daemon/src/tui/chrome/notify.ts +195 -0
  117. package/packages/daemon/src/tui/chrome/panels.ts +111 -0
  118. package/packages/daemon/src/tui/chrome/sidebar.ts +222 -0
  119. package/packages/daemon/src/tui/chrome/snapshot.ts +425 -0
  120. package/packages/daemon/src/tui/chrome/statusline.ts +595 -0
  121. package/packages/daemon/src/tui/chrome/updater.ts +428 -0
  122. package/packages/daemon/src/tui/chrome/welcome.ts +124 -0
  123. package/packages/daemon/src/tui/compiled.ts +113 -0
  124. package/packages/daemon/src/tui/detect/classify.ts +193 -0
  125. package/packages/daemon/src/tui/detect/manifest-loader.ts +192 -0
  126. package/packages/daemon/src/tui/detect/manifest.ts +184 -0
  127. package/packages/daemon/src/tui/detect/manifests.ts +226 -0
  128. package/packages/daemon/src/tui/detect/process-tree.ts +197 -0
  129. package/packages/daemon/src/tui/detect/snapshot.ts +70 -0
  130. package/packages/daemon/src/tui/integrations/claude.ts +176 -0
  131. package/packages/daemon/src/tui/integrations/offer.ts +145 -0
  132. package/packages/daemon/src/tui/main.ts +70 -0
  133. package/packages/daemon/src/tui/mirror/control-client.ts +143 -0
  134. package/packages/daemon/src/tui/mirror/control.ts +97 -0
  135. package/packages/daemon/src/tui/mirror/pane-mirror.ts +81 -0
  136. package/packages/daemon/src/tui/mirror/viewer.tsx +166 -0
  137. package/packages/daemon/src/tui/team/CONTROL.md +50 -0
  138. package/packages/daemon/src/tui/team/entry.ts +11 -0
  139. package/packages/daemon/src/tui/team/fuzzy.ts +133 -0
  140. package/packages/daemon/src/tui/team/home.ts +170 -0
  141. package/packages/daemon/src/tui/team/index.tsx +1521 -0
  142. package/packages/daemon/src/tui/team/input.ts +34 -0
  143. package/packages/daemon/src/tui/team/keymap.ts +127 -0
  144. package/packages/daemon/src/tui/team/mouse.ts +29 -0
  145. package/packages/daemon/src/tui/team/nav.ts +31 -0
  146. package/packages/daemon/src/tui/team/preview.ts +34 -0
  147. package/packages/daemon/src/tui/team/projects.ts +191 -0
  148. package/packages/daemon/src/tui/team/report.ts +73 -0
  149. package/packages/daemon/src/tui/team/sessions.ts +344 -0
  150. package/packages/daemon/src/tui/team/tree.ts +62 -0
  151. package/packages/daemon/src/types.ts +13 -0
  152. package/packages/daemon/src/ui/index.ts +32 -0
  153. package/packages/daemon/src/ui/terminal/index.ts +9 -0
  154. package/packages/daemon/src/ui/types.ts +91 -0
  155. package/packages/daemon/src/ui/web/base.css +80 -0
  156. package/packages/daemon/src/ui/web/components/Box.tsx +59 -0
  157. package/packages/daemon/src/ui/web/components/Input.tsx +32 -0
  158. package/packages/daemon/src/ui/web/components/ScrollBox.tsx +60 -0
  159. package/packages/daemon/src/ui/web/components/Text.tsx +28 -0
  160. package/packages/daemon/src/ui/web/hooks.ts +106 -0
  161. package/packages/daemon/src/ui/web/index.ts +27 -0
  162. package/packages/daemon/src/ui/web/render.ts +77 -0
  163. package/packages/daemon/src/ui/web/utils/color.ts +27 -0
  164. package/packages/daemon/src/validate.ts +217 -0
  165. package/packages/daemon/src/widgets/changes/README.md +3 -0
  166. package/packages/daemon/src/widgets/changes/index.tsx +691 -0
  167. package/packages/daemon/src/widgets/config/README.md +3 -0
  168. package/packages/daemon/src/widgets/config/index.tsx +481 -0
  169. package/packages/daemon/src/widgets/explorer/README.md +3 -0
  170. package/packages/daemon/src/widgets/explorer/breadcrumbs.tsx +77 -0
  171. package/packages/daemon/src/widgets/explorer/footer.tsx +20 -0
  172. package/packages/daemon/src/widgets/explorer/header.tsx +23 -0
  173. package/packages/daemon/src/widgets/explorer/index.tsx +456 -0
  174. package/packages/daemon/src/widgets/explorer/tree-model.ts +103 -0
  175. package/packages/daemon/src/widgets/explorer/tree.tsx +165 -0
  176. package/packages/daemon/src/widgets/lib/config-model.ts +116 -0
  177. package/packages/daemon/src/widgets/lib/files.ts +88 -0
  178. package/packages/daemon/src/widgets/lib/git.ts +88 -0
  179. package/packages/daemon/src/widgets/lib/grammar.ts +126 -0
  180. package/packages/daemon/src/widgets/lib/help-overlay.tsx +101 -0
  181. package/packages/daemon/src/widgets/lib/pane-comms.ts +209 -0
  182. package/packages/daemon/src/widgets/lib/theme.ts +194 -0
  183. package/packages/daemon/src/widgets/lib/watcher.ts +132 -0
  184. package/packages/daemon/src/widgets/preview/README.md +3 -0
  185. package/packages/daemon/src/widgets/preview/index.tsx +416 -0
  186. package/packages/daemon/src/widgets/resolve.ts +121 -0
  187. package/packages/daemon/src/widgets/setup/README.md +3 -0
  188. package/packages/daemon/src/widgets/setup/agent-naming.tsx +112 -0
  189. package/packages/daemon/src/widgets/setup/config-tree.tsx +246 -0
  190. package/packages/daemon/src/widgets/setup/detect-panel.tsx +72 -0
  191. package/packages/daemon/src/widgets/setup/field-editor.tsx +265 -0
  192. package/packages/daemon/src/widgets/setup/footer.tsx +107 -0
  193. package/packages/daemon/src/widgets/setup/index.tsx +341 -0
  194. package/packages/daemon/src/widgets/setup/layout-picker.tsx +96 -0
  195. package/packages/daemon/src/widgets/setup/orchestrator-panel.tsx +200 -0
  196. package/packages/daemon/src/widgets/setup/review-panel.tsx +140 -0
  197. package/packages/daemon/src/widgets/setup/setup-model.ts +188 -0
  198. package/packages/daemon/src/widgets/sidebar/index.tsx +527 -0
  199. package/packages/tmux-bridge/package.json +22 -0
  200. package/packages/tmux-bridge/src/errors.ts +28 -0
  201. package/packages/tmux-bridge/src/index.ts +31 -0
  202. package/packages/tmux-bridge/src/monitor.ts +77 -0
  203. package/packages/tmux-bridge/src/panes.ts +136 -0
  204. package/packages/tmux-bridge/src/runner.test.ts +501 -0
  205. package/packages/tmux-bridge/src/runner.ts +91 -0
  206. package/packages/tmux-bridge/src/sessions.ts +126 -0
  207. package/packages/tmux-bridge/src/targeting.test.ts +107 -0
  208. package/packages/tmux-bridge/src/targeting.ts +90 -0
  209. package/scripts/postinstall.js +26 -2
@@ -0,0 +1,248 @@
1
+ import { resolve, basename } from "node:path";
2
+ import { readFileSync, existsSync } from "node:fs";
3
+ import { writeConfig } from "./lib/yaml-io.ts";
4
+ import type { IdeConfig } from "./types.ts";
5
+
6
+ interface DetectedStack {
7
+ packageManager: string | null;
8
+ frameworks: string[];
9
+ devCommand: string | null;
10
+ language: string | null;
11
+ reasons: string[];
12
+ }
13
+
14
+ function fileExists(dir: string, name: string): boolean {
15
+ return existsSync(resolve(dir, name));
16
+ }
17
+
18
+ function readJson(dir: string, name: string): Record<string, unknown> | null {
19
+ try {
20
+ return JSON.parse(readFileSync(resolve(dir, name), "utf-8")) as Record<string, unknown>;
21
+ } catch {
22
+ return null;
23
+ }
24
+ }
25
+
26
+ export function detectStack(dir: string): DetectedStack {
27
+ const detected: DetectedStack = {
28
+ packageManager: null,
29
+ frameworks: [],
30
+ devCommand: null,
31
+ language: null,
32
+ reasons: [],
33
+ };
34
+
35
+ // Detect package manager from lockfile
36
+ if (fileExists(dir, "pnpm-lock.yaml")) {
37
+ detected.packageManager = "pnpm";
38
+ detected.reasons.push('Detected pnpm from "pnpm-lock.yaml".');
39
+ } else if (fileExists(dir, "bun.lockb") || fileExists(dir, "bun.lock")) {
40
+ detected.packageManager = "bun";
41
+ detected.reasons.push('Detected bun from "bun.lockb" or "bun.lock".');
42
+ } else if (fileExists(dir, "yarn.lock")) {
43
+ detected.packageManager = "yarn";
44
+ detected.reasons.push('Detected yarn from "yarn.lock".');
45
+ } else if (fileExists(dir, "package-lock.json")) {
46
+ detected.packageManager = "npm";
47
+ detected.reasons.push('Detected npm from "package-lock.json".');
48
+ }
49
+
50
+ const pkg = readJson(dir, "package.json");
51
+ if (pkg) {
52
+ detected.language = "javascript";
53
+ detected.reasons.push('Detected JavaScript from "package.json".');
54
+ const deps = {
55
+ ...(pkg.dependencies as Record<string, unknown> | undefined),
56
+ ...(pkg.devDependencies as Record<string, unknown> | undefined),
57
+ };
58
+
59
+ if (deps["next"]) pushFramework(detected, "next", 'Found dependency "next".');
60
+ if (deps["convex"]) pushFramework(detected, "convex", 'Found dependency "convex".');
61
+ if (deps["vite"]) pushFramework(detected, "vite", 'Found dependency "vite".');
62
+ if (deps["remix"] || deps["@remix-run/node"])
63
+ pushFramework(detected, "remix", "Found Remix dependency.");
64
+ if (deps["nuxt"]) pushFramework(detected, "nuxt", 'Found dependency "nuxt".');
65
+ if (deps["astro"]) pushFramework(detected, "astro", 'Found dependency "astro".');
66
+ if (deps["svelte"] || deps["@sveltejs/kit"])
67
+ pushFramework(detected, "svelte", "Found Svelte dependency.");
68
+
69
+ // Detect dev command
70
+ const pm = detected.packageManager ?? "npm";
71
+ const run = pm === "npm" ? "npm run" : pm;
72
+ const scripts = pkg.scripts as Record<string, unknown> | undefined;
73
+ if (scripts?.dev) {
74
+ detected.devCommand = `${run} dev`;
75
+ detected.reasons.push(
76
+ `Using dev command "${detected.devCommand}" from package.json scripts.`,
77
+ );
78
+ } else if (scripts?.start) {
79
+ detected.devCommand = `${run} start`;
80
+ detected.reasons.push(
81
+ `Using start command "${detected.devCommand}" from package.json scripts.`,
82
+ );
83
+ }
84
+ }
85
+
86
+ // Python
87
+ if (fileExists(dir, "pyproject.toml") || fileExists(dir, "requirements.txt")) {
88
+ detected.language = detected.language ?? "python";
89
+ detected.reasons.push('Detected Python from "pyproject.toml" or "requirements.txt".');
90
+ try {
91
+ const pyproject = readFileSync(resolve(dir, "pyproject.toml"), "utf-8");
92
+ if (pyproject.includes("fastapi"))
93
+ pushFramework(detected, "fastapi", 'Found "fastapi" in pyproject.toml.');
94
+ else if (pyproject.includes("django"))
95
+ pushFramework(detected, "django", 'Found "django" in pyproject.toml.');
96
+ else if (pyproject.includes("flask"))
97
+ pushFramework(detected, "flask", 'Found "flask" in pyproject.toml.');
98
+ } catch {
99
+ // Ignore missing or unreadable pyproject metadata.
100
+ }
101
+ }
102
+
103
+ // Rust
104
+ if (fileExists(dir, "Cargo.toml")) {
105
+ detected.language = detected.language ?? "rust";
106
+ detected.reasons.push('Detected Rust from "Cargo.toml".');
107
+ pushFramework(detected, "cargo", 'Using Cargo workflow from "Cargo.toml".');
108
+ }
109
+
110
+ // Go
111
+ if (fileExists(dir, "go.mod")) {
112
+ detected.language = detected.language ?? "go";
113
+ detected.reasons.push('Detected Go from "go.mod".');
114
+ pushFramework(detected, "go", 'Using Go workflow from "go.mod".');
115
+ }
116
+
117
+ // Docker
118
+ if (fileExists(dir, "docker-compose.yml") || fileExists(dir, "docker-compose.yaml")) {
119
+ pushFramework(
120
+ detected,
121
+ "docker",
122
+ 'Detected Docker from "docker-compose.yml" or "docker-compose.yaml".',
123
+ );
124
+ }
125
+
126
+ if (detected.reasons.length === 0) {
127
+ detected.reasons.push("No framework-specific signals found; using the generic layout.");
128
+ }
129
+
130
+ return detected;
131
+ }
132
+
133
+ export function suggestConfig(dir: string, detected: DetectedStack): IdeConfig {
134
+ const name = basename(dir);
135
+ const pm = detected.packageManager ?? "npm";
136
+ const run = pm === "npm" ? "npm run" : pm;
137
+
138
+ // Default: 2 claude panes + shell
139
+ const config: IdeConfig = {
140
+ name,
141
+ rows: [
142
+ {
143
+ size: "70%",
144
+ panes: [
145
+ { title: "Claude 1", command: "claude" },
146
+ { title: "Claude 2", command: "claude" },
147
+ ],
148
+ },
149
+ {
150
+ panes: [],
151
+ },
152
+ ],
153
+ };
154
+
155
+ const bottom = config.rows[1]!.panes;
156
+ const frameworks = detected.frameworks;
157
+
158
+ // Add 3rd claude pane for complex stacks
159
+ if (frameworks.length >= 2) {
160
+ config.rows[0]!.panes.push({ title: "Claude 3", command: "claude" });
161
+ }
162
+
163
+ // Add dev servers
164
+ if (frameworks.includes("next")) {
165
+ bottom.push({ title: "Next.js", command: `${run} dev` });
166
+ } else if (frameworks.includes("vite")) {
167
+ bottom.push({ title: "Vite", command: `${run} dev` });
168
+ } else if (frameworks.includes("nuxt")) {
169
+ bottom.push({ title: "Nuxt", command: `${run} dev` });
170
+ } else if (frameworks.includes("remix")) {
171
+ bottom.push({ title: "Remix", command: `${run} dev` });
172
+ } else if (frameworks.includes("astro")) {
173
+ bottom.push({ title: "Astro", command: `${run} dev` });
174
+ } else if (frameworks.includes("svelte")) {
175
+ bottom.push({ title: "SvelteKit", command: `${run} dev` });
176
+ } else if (frameworks.includes("fastapi")) {
177
+ bottom.push({ title: "FastAPI", command: "uvicorn main:app --reload" });
178
+ } else if (frameworks.includes("django")) {
179
+ bottom.push({ title: "Django", command: "python manage.py runserver" });
180
+ } else if (frameworks.includes("flask")) {
181
+ bottom.push({ title: "Flask", command: "flask run --reload" });
182
+ } else if (frameworks.includes("cargo")) {
183
+ bottom.push({ title: "Cargo", command: "cargo watch -x run" });
184
+ } else if (frameworks.includes("go")) {
185
+ bottom.push({ title: "Go", command: "go run ." });
186
+ } else if (detected.devCommand) {
187
+ bottom.push({ title: "Dev Server", command: detected.devCommand });
188
+ }
189
+
190
+ if (frameworks.includes("convex")) {
191
+ bottom.push({ title: "Convex", command: "npx convex dev" });
192
+ }
193
+
194
+ // Always add shell
195
+ bottom.push({ title: "Shell" });
196
+
197
+ return config;
198
+ }
199
+
200
+ export async function detect(
201
+ targetDir: string | undefined,
202
+ { json, write }: { json?: boolean; write?: boolean } = {},
203
+ ): Promise<void> {
204
+ const dir = resolve(targetDir ?? ".");
205
+ const detected = detectStack(dir);
206
+ const suggested = suggestConfig(dir, detected);
207
+
208
+ if (write) {
209
+ writeConfig(dir, suggested);
210
+ if (json) {
211
+ console.log(JSON.stringify({ detected, suggestedConfig: suggested, written: true }, null, 2));
212
+ } else {
213
+ const desc =
214
+ detected.frameworks.length > 0
215
+ ? detected.frameworks.join(" + ")
216
+ : (detected.language ?? "generic project");
217
+ console.log(`Detected ${desc}. Created ide.yml.`);
218
+ console.log("\nWhy this layout:");
219
+ for (const reason of detected.reasons) {
220
+ console.log(` - ${reason}`);
221
+ }
222
+ }
223
+ return;
224
+ }
225
+
226
+ if (json) {
227
+ console.log(JSON.stringify({ detected, suggestedConfig: suggested }, null, 2));
228
+ return;
229
+ }
230
+
231
+ console.log("Detected stack:");
232
+ if (detected.packageManager) console.log(` Package manager: ${detected.packageManager}`);
233
+ if (detected.language) console.log(` Language: ${detected.language}`);
234
+ if (detected.frameworks.length) console.log(` Frameworks: ${detected.frameworks.join(", ")}`);
235
+ if (detected.devCommand) console.log(` Dev command: ${detected.devCommand}`);
236
+ console.log("\nReasoning:");
237
+ for (const reason of detected.reasons) {
238
+ console.log(` - ${reason}`);
239
+ }
240
+ console.log("\nRun with --write to create ide.yml, or --json to see the suggested config.");
241
+ }
242
+
243
+ function pushFramework(detected: DetectedStack, framework: string, reason: string): void {
244
+ if (!detected.frameworks.includes(framework)) {
245
+ detected.frameworks.push(framework);
246
+ }
247
+ detected.reasons.push(reason);
248
+ }
@@ -0,0 +1,242 @@
1
+ import { execSync } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import { resolve, dirname } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { getCurrentVersion, getUpdateStatus } from "./lib/update-check.ts";
6
+ import { installedSkillVersion } from "./lib/skill-sync.ts";
7
+ import { discoverAgents, presentAgents, type DiscoveredAgent } from "./lib/agent-discovery.ts";
8
+ import { findCompiledTui, isBunAvailable } from "./tui/compiled.ts";
9
+
10
+ interface CheckResult {
11
+ label: string;
12
+ pass: boolean;
13
+ detail: string;
14
+ optional: boolean;
15
+ }
16
+
17
+ /**
18
+ * PURE — the "agent integrations" doctor rows, one per DISCOVERED agent (absent
19
+ * agents produce nothing — no noise). All rows are optional (informational, they
20
+ * never fail the overall check):
21
+ * - `claude` + integration installed → a passing ✓ "integration installed ✓".
22
+ * - `claude` on PATH but NOT installed → a ○ hint pointing at the installer.
23
+ * - any other agent on PATH → a passing ✓ noting screen-manifest detection is
24
+ * active but there's no lifecycle integration yet.
25
+ */
26
+ export function agentIntegrationRows(agents: DiscoveredAgent[]): CheckResult[] {
27
+ return presentAgents(agents).map((agent) => {
28
+ const label = `agent: ${agent.id}`;
29
+ if (agent.integration) {
30
+ return agent.installed
31
+ ? { label, pass: true, detail: "integration installed ✓", optional: true }
32
+ : {
33
+ label,
34
+ pass: false,
35
+ detail: `found on PATH — run \`tmux-ide integration install ${agent.id}\` for ground-truth status`,
36
+ optional: true,
37
+ };
38
+ }
39
+ return {
40
+ label,
41
+ pass: true,
42
+ detail: "found — screen-manifest detection active (no lifecycle integration yet)",
43
+ optional: true,
44
+ };
45
+ });
46
+ }
47
+
48
+ function check(
49
+ label: string,
50
+ fn: () => string,
51
+ { optional = false }: { optional?: boolean } = {},
52
+ ): CheckResult {
53
+ try {
54
+ const result = fn();
55
+ return { label, pass: true, detail: result, optional };
56
+ } catch (e) {
57
+ return { label, pass: false, detail: (e as Error).message, optional };
58
+ }
59
+ }
60
+
61
+ export async function doctor({
62
+ json,
63
+ }: {
64
+ json?: boolean;
65
+ } = {}): Promise<void> {
66
+ const checks: CheckResult[] = [];
67
+
68
+ checks.push(
69
+ check("tmux installed", () => {
70
+ execSync("which tmux", { stdio: "ignore" });
71
+ return "found";
72
+ }),
73
+ );
74
+
75
+ checks.push(
76
+ check("tmux version ≥ 3.0", () => {
77
+ const version = execSync("tmux -V", { encoding: "utf-8" }).trim();
78
+ const num = parseFloat(version.replace(/[^0-9.]/g, ""));
79
+ if (num < 3.0) throw new Error(`${version} (need ≥ 3.0)`);
80
+ return version;
81
+ }),
82
+ );
83
+
84
+ checks.push(
85
+ check("Node.js ≥ 18", () => {
86
+ const major = parseInt(process.versions.node.split(".")[0]!);
87
+ if (major < 18) throw new Error(`Node ${process.versions.node} (need ≥ 18)`);
88
+ return `v${process.versions.node}`;
89
+ }),
90
+ );
91
+
92
+ checks.push(
93
+ check(
94
+ "256-color terminal",
95
+ () => {
96
+ const term = process.env.TERM ?? "";
97
+ if (
98
+ !term.includes("256color") &&
99
+ !term.includes("ghostty") &&
100
+ !term.includes("kitty") &&
101
+ term !== "tmux-256color"
102
+ ) {
103
+ throw new Error(`$TERM is "${term}"`);
104
+ }
105
+ return term;
106
+ },
107
+ { optional: true },
108
+ ),
109
+ );
110
+
111
+ checks.push(
112
+ check("ide.yml exists", () => {
113
+ const path = resolve(".", "ide.yml");
114
+ if (!existsSync(path)) throw new Error("not found in current directory");
115
+ return "found";
116
+ }),
117
+ );
118
+
119
+ checks.push(
120
+ check(
121
+ "TUI surfaces (cockpit / widgets)",
122
+ () => {
123
+ // The OpenTUI/Solid surfaces run either from a dev checkout (bun + the
124
+ // `.tsx` sources) or, when installed, from the compiled `tmux-ide-tui`
125
+ // binary. Report which path is live so a "nothing renders" install is
126
+ // diagnosable instead of silent. Mirrors resolveTuiLaunch's order.
127
+ const here = dirname(fileURLToPath(import.meta.url));
128
+ const checkoutEntry = [
129
+ resolve(here, "../packages/daemon/src/tui/team/index.tsx"),
130
+ resolve(here, "tui/team/index.tsx"),
131
+ ].find(existsSync);
132
+ const binary = findCompiledTui();
133
+ if (checkoutEntry && isBunAvailable()) return "dev checkout (bun)";
134
+ if (binary) return `compiled binary (${binary})`;
135
+ throw new Error(
136
+ "no dev checkout+bun and no compiled binary — build one with `pnpm build:tui` or install a release that ships it",
137
+ );
138
+ },
139
+ { optional: true },
140
+ ),
141
+ );
142
+
143
+ checks.push(
144
+ check(
145
+ "Claude Code agent teams",
146
+ () => {
147
+ if (process.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS !== "1") {
148
+ throw new Error("not set (enable with CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1)");
149
+ }
150
+ return "enabled";
151
+ },
152
+ { optional: true },
153
+ ),
154
+ );
155
+
156
+ checks.push(
157
+ check(
158
+ "tailscale CLI",
159
+ () => {
160
+ const version = execSync("tailscale version", { encoding: "utf-8" }).trim().split("\n")[0]!;
161
+ return version;
162
+ },
163
+ { optional: true },
164
+ ),
165
+ );
166
+
167
+ checks.push(
168
+ check(
169
+ "ngrok CLI",
170
+ () => {
171
+ const version = execSync("ngrok version", { encoding: "utf-8" }).trim();
172
+ return version;
173
+ },
174
+ { optional: true },
175
+ ),
176
+ );
177
+
178
+ checks.push(
179
+ check(
180
+ "cloudflared CLI",
181
+ () => {
182
+ const version = execSync("cloudflared --version", { encoding: "utf-8" }).trim();
183
+ return version;
184
+ },
185
+ { optional: true },
186
+ ),
187
+ );
188
+
189
+ checks.push(
190
+ check(
191
+ "tmux-ide up to date",
192
+ () => {
193
+ const current = getCurrentVersion();
194
+ const { latest, updateAvailable } = getUpdateStatus({ currentVersion: current });
195
+ if (updateAvailable) {
196
+ throw new Error(`v${current} — v${latest} available (run \`tmux-ide update\`)`);
197
+ }
198
+ return latest ? `v${current} (latest)` : `v${current} (latest unknown)`;
199
+ },
200
+ { optional: true },
201
+ ),
202
+ );
203
+
204
+ checks.push(
205
+ check(
206
+ "Claude Code skill",
207
+ () => {
208
+ // The managed skill copy under ~/.claude/skills/tmux-ide should track the
209
+ // CLI version. Stale/missing → point at the one-command fix; installs and
210
+ // `tmux-ide update` refresh it automatically, so this is only a nudge.
211
+ const installed = installedSkillVersion();
212
+ const current = getCurrentVersion();
213
+ if (installed === null) {
214
+ throw new Error("not installed — run `tmux-ide skill-sync`");
215
+ }
216
+ if (installed !== current) {
217
+ throw new Error(`v${installed} (CLI v${current}) — run \`tmux-ide skill-sync\``);
218
+ }
219
+ return `in sync (v${installed})`;
220
+ },
221
+ { optional: true },
222
+ ),
223
+ );
224
+
225
+ // Agent integrations: one row per agent discovered on PATH (absent → no row).
226
+ checks.push(...agentIntegrationRows(discoverAgents()));
227
+
228
+ const allPass = checks.every((c) => c.pass || c.optional);
229
+
230
+ if (json) {
231
+ console.log(JSON.stringify({ ok: allPass, checks }, null, 2));
232
+ return;
233
+ }
234
+
235
+ for (const c of checks) {
236
+ const icon = c.pass ? "✓" : c.optional ? "○" : "✗";
237
+ const color = c.pass ? "\x1b[32m" : c.optional ? "\x1b[33m" : "\x1b[31m";
238
+ console.log(`${color}${icon}\x1b[0m ${c.label} — ${c.detail}`);
239
+ }
240
+
241
+ if (!allPass) process.exitCode = 1;
242
+ }
@@ -0,0 +1,5 @@
1
+ export {
2
+ startEmbeddedDaemon,
3
+ type EmbeddedDaemonHandle,
4
+ type EmbeddedDaemonOptions,
5
+ } from "./lib/daemon-embed.ts";
@@ -0,0 +1,12 @@
1
+ export {
2
+ startEmbeddedDaemon,
3
+ type EmbeddedDaemonHandle,
4
+ type EmbeddedDaemonOptions,
5
+ } from "./embed.ts";
6
+ export {
7
+ clearCanonicalDaemonInfo,
8
+ isCanonicalDaemonAlive,
9
+ readCanonicalDaemonInfo,
10
+ writeCanonicalDaemonInfo,
11
+ } from "./canonical.ts";
12
+ export type { CanonicalDaemonInfo } from "./canonical.ts";