chorus-codes 0.7.0 → 0.7.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 (291) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +3 -3
  3. package/.next/cache/.previewinfo +1 -1
  4. package/.next/cache/.rscinfo +1 -1
  5. package/.next/fallback-build-manifest.json +3 -3
  6. package/.next/prerender-manifest.json +3 -3
  7. package/.next/server/app/_global-error.html +1 -1
  8. package/.next/server/app/_global-error.rsc +1 -1
  9. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  12. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/.next/server/app/_not-found.html +1 -1
  15. package/.next/server/app/_not-found.rsc +1 -1
  16. package/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  17. package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  18. package/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  19. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  20. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  21. package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  22. package/.next/server/app/new.html +1 -1
  23. package/.next/server/app/new.rsc +1 -1
  24. package/.next/server/app/new.segments/_full.segment.rsc +1 -1
  25. package/.next/server/app/new.segments/_head.segment.rsc +1 -1
  26. package/.next/server/app/new.segments/_index.segment.rsc +1 -1
  27. package/.next/server/app/new.segments/_tree.segment.rsc +1 -1
  28. package/.next/server/app/new.segments/new/__PAGE__.segment.rsc +1 -1
  29. package/.next/server/app/new.segments/new.segment.rsc +1 -1
  30. package/.next/server/app/onboarding.html +1 -1
  31. package/.next/server/app/onboarding.rsc +1 -1
  32. package/.next/server/app/onboarding.segments/_full.segment.rsc +1 -1
  33. package/.next/server/app/onboarding.segments/_head.segment.rsc +1 -1
  34. package/.next/server/app/onboarding.segments/_index.segment.rsc +1 -1
  35. package/.next/server/app/onboarding.segments/_tree.segment.rsc +1 -1
  36. package/.next/server/app/onboarding.segments/onboarding/__PAGE__.segment.rsc +1 -1
  37. package/.next/server/app/onboarding.segments/onboarding.segment.rsc +1 -1
  38. package/.next/server/app/personas.html +1 -1
  39. package/.next/server/app/personas.rsc +1 -1
  40. package/.next/server/app/personas.segments/_full.segment.rsc +1 -1
  41. package/.next/server/app/personas.segments/_head.segment.rsc +1 -1
  42. package/.next/server/app/personas.segments/_index.segment.rsc +1 -1
  43. package/.next/server/app/personas.segments/_tree.segment.rsc +1 -1
  44. package/.next/server/app/personas.segments/personas/__PAGE__.segment.rsc +1 -1
  45. package/.next/server/app/personas.segments/personas.segment.rsc +1 -1
  46. package/.next/server/app/settings.html +1 -1
  47. package/.next/server/app/settings.rsc +1 -1
  48. package/.next/server/app/settings.segments/_full.segment.rsc +1 -1
  49. package/.next/server/app/settings.segments/_head.segment.rsc +1 -1
  50. package/.next/server/app/settings.segments/_index.segment.rsc +1 -1
  51. package/.next/server/app/settings.segments/_tree.segment.rsc +1 -1
  52. package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +1 -1
  53. package/.next/server/app/settings.segments/settings.segment.rsc +1 -1
  54. package/.next/server/app/templates.html +1 -1
  55. package/.next/server/app/templates.rsc +1 -1
  56. package/.next/server/app/templates.segments/_full.segment.rsc +1 -1
  57. package/.next/server/app/templates.segments/_head.segment.rsc +1 -1
  58. package/.next/server/app/templates.segments/_index.segment.rsc +1 -1
  59. package/.next/server/app/templates.segments/_tree.segment.rsc +1 -1
  60. package/.next/server/app/templates.segments/templates/__PAGE__.segment.rsc +1 -1
  61. package/.next/server/app/templates.segments/templates.segment.rsc +1 -1
  62. package/.next/server/middleware-build-manifest.js +3 -3
  63. package/.next/server/pages/404.html +1 -1
  64. package/.next/server/pages/500.html +1 -1
  65. package/.next/server/server-reference-manifest.js +1 -1
  66. package/.next/server/server-reference-manifest.json +1 -1
  67. package/.next/trace +1 -1
  68. package/.next/trace-build +1 -1
  69. package/dist/cli/commands/doctor.js +116 -0
  70. package/dist/cli/commands/doctor.js.map +1 -0
  71. package/dist/cli/commands/init.js +211 -0
  72. package/dist/cli/commands/init.js.map +1 -0
  73. package/dist/cli/commands/start.js +240 -0
  74. package/dist/cli/commands/start.js.map +1 -0
  75. package/dist/cli/commands/status.js +54 -0
  76. package/dist/cli/commands/status.js.map +1 -0
  77. package/dist/cli/commands/stop.js +97 -0
  78. package/dist/cli/commands/stop.js.map +1 -0
  79. package/dist/cli/connect.js +108 -0
  80. package/dist/cli/connect.js.map +1 -0
  81. package/dist/cli/index.js +99 -0
  82. package/dist/cli/index.js.map +1 -0
  83. package/dist/cli/port-utils.js +260 -0
  84. package/dist/cli/port-utils.js.map +1 -0
  85. package/dist/cli/runtime-env.js +60 -0
  86. package/dist/cli/runtime-env.js.map +1 -0
  87. package/dist/cli/shared.js +54 -0
  88. package/dist/cli/shared.js.map +1 -0
  89. package/dist/cli/ui.js +60 -0
  90. package/dist/cli/ui.js.map +1 -0
  91. package/dist/daemon/agents/claude.js +98 -0
  92. package/dist/daemon/agents/claude.js.map +1 -0
  93. package/dist/daemon/agents/codex.js +160 -0
  94. package/dist/daemon/agents/codex.js.map +1 -0
  95. package/dist/daemon/agents/gemini.js +111 -0
  96. package/dist/daemon/agents/gemini.js.map +1 -0
  97. package/dist/daemon/agents/index.js +59 -0
  98. package/dist/daemon/agents/index.js.map +1 -0
  99. package/dist/daemon/agents/kimi.js +206 -0
  100. package/dist/daemon/agents/kimi.js.map +1 -0
  101. package/dist/daemon/agents/opencode.js +228 -0
  102. package/dist/daemon/agents/opencode.js.map +1 -0
  103. package/dist/daemon/agents/openrouter.js +274 -0
  104. package/dist/daemon/agents/openrouter.js.map +1 -0
  105. package/dist/daemon/agents/parsers/claude.js +63 -0
  106. package/dist/daemon/agents/parsers/claude.js.map +1 -0
  107. package/dist/daemon/agents/parsers/codex.js +51 -0
  108. package/dist/daemon/agents/parsers/codex.js.map +1 -0
  109. package/dist/daemon/agents/parsers/gemini.js +144 -0
  110. package/dist/daemon/agents/parsers/gemini.js.map +1 -0
  111. package/dist/daemon/agents/parsers/index.js +31 -0
  112. package/dist/daemon/agents/parsers/index.js.map +1 -0
  113. package/dist/daemon/agents/parsers/kimi.js +8 -0
  114. package/dist/daemon/agents/parsers/kimi.js.map +1 -0
  115. package/dist/daemon/agents/parsers/opencode.js +105 -0
  116. package/dist/daemon/agents/parsers/opencode.js.map +1 -0
  117. package/dist/daemon/agents/parsers/openrouter.js +69 -0
  118. package/dist/daemon/agents/parsers/openrouter.js.map +1 -0
  119. package/dist/daemon/agents/parsers/shared.js +17 -0
  120. package/dist/daemon/agents/parsers/shared.js.map +1 -0
  121. package/dist/daemon/agents/preflight.js +83 -0
  122. package/dist/daemon/agents/preflight.js.map +1 -0
  123. package/dist/daemon/agents/quote.js +45 -0
  124. package/dist/daemon/agents/quote.js.map +1 -0
  125. package/dist/daemon/agents/sandbox-guard.js +69 -0
  126. package/dist/daemon/agents/sandbox-guard.js.map +1 -0
  127. package/dist/daemon/agents/types.js +6 -0
  128. package/dist/daemon/agents/types.js.map +1 -0
  129. package/dist/daemon/api-response.js +65 -0
  130. package/dist/daemon/api-response.js.map +1 -0
  131. package/dist/daemon/error-detector.js +329 -0
  132. package/dist/daemon/error-detector.js.map +1 -0
  133. package/dist/daemon/headless.js +533 -0
  134. package/dist/daemon/headless.js.map +1 -0
  135. package/dist/daemon/index.js +333 -0
  136. package/dist/daemon/index.js.map +1 -0
  137. package/dist/daemon/openrouter.js +192 -0
  138. package/dist/daemon/openrouter.js.map +1 -0
  139. package/dist/daemon/orchestrators/claude.js +163 -0
  140. package/dist/daemon/orchestrators/claude.js.map +1 -0
  141. package/dist/daemon/orchestrators/codex.js +101 -0
  142. package/dist/daemon/orchestrators/codex.js.map +1 -0
  143. package/dist/daemon/orchestrators/cursor-windsurf.js +118 -0
  144. package/dist/daemon/orchestrators/cursor-windsurf.js.map +1 -0
  145. package/dist/daemon/orchestrators/gemini.js +108 -0
  146. package/dist/daemon/orchestrators/gemini.js.map +1 -0
  147. package/dist/daemon/orchestrators/index.js +90 -0
  148. package/dist/daemon/orchestrators/index.js.map +1 -0
  149. package/dist/daemon/orchestrators/kimi.js +108 -0
  150. package/dist/daemon/orchestrators/kimi.js.map +1 -0
  151. package/dist/daemon/orchestrators/opencode.js +152 -0
  152. package/dist/daemon/orchestrators/opencode.js.map +1 -0
  153. package/dist/daemon/orchestrators/shared.js +60 -0
  154. package/dist/daemon/orchestrators/shared.js.map +1 -0
  155. package/dist/daemon/output-watcher.js +131 -0
  156. package/dist/daemon/output-watcher.js.map +1 -0
  157. package/dist/daemon/participant-aborts.js +123 -0
  158. package/dist/daemon/participant-aborts.js.map +1 -0
  159. package/dist/daemon/reaper.js +46 -0
  160. package/dist/daemon/reaper.js.map +1 -0
  161. package/dist/daemon/routes/chats-events.js +62 -0
  162. package/dist/daemon/routes/chats-events.js.map +1 -0
  163. package/dist/daemon/routes/chats-stream.js +241 -0
  164. package/dist/daemon/routes/chats-stream.js.map +1 -0
  165. package/dist/daemon/routes/chats-validation.js +13 -0
  166. package/dist/daemon/routes/chats-validation.js.map +1 -0
  167. package/dist/daemon/routes/chats.js +545 -0
  168. package/dist/daemon/routes/chats.js.map +1 -0
  169. package/dist/daemon/routes/openrouter.js +103 -0
  170. package/dist/daemon/routes/openrouter.js.map +1 -0
  171. package/dist/daemon/routes/settings.js +199 -0
  172. package/dist/daemon/routes/settings.js.map +1 -0
  173. package/dist/daemon/routes/stats.js +155 -0
  174. package/dist/daemon/routes/stats.js.map +1 -0
  175. package/dist/daemon/routes/system.js +208 -0
  176. package/dist/daemon/routes/system.js.map +1 -0
  177. package/dist/daemon/routes/templates-personas.js +254 -0
  178. package/dist/daemon/routes/templates-personas.js.map +1 -0
  179. package/dist/daemon/routes/voices.js +139 -0
  180. package/dist/daemon/routes/voices.js.map +1 -0
  181. package/dist/daemon/runner/doer-driver.js +346 -0
  182. package/dist/daemon/runner/doer-driver.js.map +1 -0
  183. package/dist/daemon/runner/doer.js +336 -0
  184. package/dist/daemon/runner/doer.js.map +1 -0
  185. package/dist/daemon/runner/prior-round.js +140 -0
  186. package/dist/daemon/runner/prior-round.js.map +1 -0
  187. package/dist/daemon/runner/prompt-builder.js +292 -0
  188. package/dist/daemon/runner/prompt-builder.js.map +1 -0
  189. package/dist/daemon/runner/review-only-phase.js +103 -0
  190. package/dist/daemon/runner/review-only-phase.js.map +1 -0
  191. package/dist/daemon/runner/reviewer-driver.js +410 -0
  192. package/dist/daemon/runner/reviewer-driver.js.map +1 -0
  193. package/dist/daemon/runner/reviewer.js +384 -0
  194. package/dist/daemon/runner/reviewer.js.map +1 -0
  195. package/dist/daemon/runner/run-with-fallback.js +56 -0
  196. package/dist/daemon/runner/run-with-fallback.js.map +1 -0
  197. package/dist/daemon/runner/sanitize-name.js +8 -0
  198. package/dist/daemon/runner/sanitize-name.js.map +1 -0
  199. package/dist/daemon/runner/stream-file-writer.js +116 -0
  200. package/dist/daemon/runner/stream-file-writer.js.map +1 -0
  201. package/dist/daemon/runner/swap-sidecar.js +102 -0
  202. package/dist/daemon/runner/swap-sidecar.js.map +1 -0
  203. package/dist/daemon/runner/template-fallback.js +119 -0
  204. package/dist/daemon/runner/template-fallback.js.map +1 -0
  205. package/dist/daemon/runner/types.js +3 -0
  206. package/dist/daemon/runner/types.js.map +1 -0
  207. package/dist/daemon/runner/verdict.js +51 -0
  208. package/dist/daemon/runner/verdict.js.map +1 -0
  209. package/dist/daemon/runner-multiplex.js +364 -0
  210. package/dist/daemon/runner-multiplex.js.map +1 -0
  211. package/dist/daemon/runner.js +427 -0
  212. package/dist/daemon/runner.js.map +1 -0
  213. package/dist/daemon/ship.js +340 -0
  214. package/dist/daemon/ship.js.map +1 -0
  215. package/dist/daemon/template-cache.js +37 -0
  216. package/dist/daemon/template-cache.js.map +1 -0
  217. package/dist/daemon/tmux-types.js +9 -0
  218. package/dist/daemon/tmux-types.js.map +1 -0
  219. package/dist/daemon/tmux.js +341 -0
  220. package/dist/daemon/tmux.js.map +1 -0
  221. package/dist/lib/atomic-write.js +55 -0
  222. package/dist/lib/atomic-write.js.map +1 -0
  223. package/dist/lib/chat-events-bus.js +27 -0
  224. package/dist/lib/chat-events-bus.js.map +1 -0
  225. package/dist/lib/chat-slug.js +105 -0
  226. package/dist/lib/chat-slug.js.map +1 -0
  227. package/dist/lib/cli-detect.js +388 -0
  228. package/dist/lib/cli-detect.js.map +1 -0
  229. package/dist/lib/cli-health.js +156 -0
  230. package/dist/lib/cli-health.js.map +1 -0
  231. package/dist/lib/cli-paths.js +113 -0
  232. package/dist/lib/cli-paths.js.map +1 -0
  233. package/dist/lib/cli-precheck.js +141 -0
  234. package/dist/lib/cli-precheck.js.map +1 -0
  235. package/dist/lib/db/chats.js +244 -0
  236. package/dist/lib/db/chats.js.map +1 -0
  237. package/dist/lib/db/connection.js +254 -0
  238. package/dist/lib/db/connection.js.map +1 -0
  239. package/dist/lib/db/index.js +34 -0
  240. package/dist/lib/db/index.js.map +1 -0
  241. package/dist/lib/db/personas.js +65 -0
  242. package/dist/lib/db/personas.js.map +1 -0
  243. package/dist/lib/db/phase-events.js +172 -0
  244. package/dist/lib/db/phase-events.js.map +1 -0
  245. package/dist/lib/db/secrets.js +53 -0
  246. package/dist/lib/db/secrets.js.map +1 -0
  247. package/dist/lib/db/settings.js +47 -0
  248. package/dist/lib/db/settings.js.map +1 -0
  249. package/dist/lib/db/templates.js +75 -0
  250. package/dist/lib/db/templates.js.map +1 -0
  251. package/dist/lib/db/voices.js +184 -0
  252. package/dist/lib/db/voices.js.map +1 -0
  253. package/dist/lib/lineage-maps.js +200 -0
  254. package/dist/lib/lineage-maps.js.map +1 -0
  255. package/dist/lib/logger.js +186 -0
  256. package/dist/lib/logger.js.map +1 -0
  257. package/dist/lib/personas.js +117 -0
  258. package/dist/lib/personas.js.map +1 -0
  259. package/dist/lib/runtime-path.js +222 -0
  260. package/dist/lib/runtime-path.js.map +1 -0
  261. package/dist/lib/settings/billing.js +58 -0
  262. package/dist/lib/settings/billing.js.map +1 -0
  263. package/dist/lib/settings/permissions.js +81 -0
  264. package/dist/lib/settings/permissions.js.map +1 -0
  265. package/dist/lib/settings/transport.js +113 -0
  266. package/dist/lib/settings/transport.js.map +1 -0
  267. package/dist/lib/telemetry.js +290 -0
  268. package/dist/lib/telemetry.js.map +1 -0
  269. package/dist/lib/template-schema.js +319 -0
  270. package/dist/lib/template-schema.js.map +1 -0
  271. package/dist/lib/template-validation.js +82 -0
  272. package/dist/lib/template-validation.js.map +1 -0
  273. package/dist/lib/voices.js +533 -0
  274. package/dist/lib/voices.js.map +1 -0
  275. package/dist/mcp/client.js +138 -0
  276. package/dist/mcp/client.js.map +1 -0
  277. package/dist/mcp/index.js +178 -0
  278. package/dist/mcp/index.js.map +1 -0
  279. package/dist/mcp/tools.js +355 -0
  280. package/dist/mcp/tools.js.map +1 -0
  281. package/package.json +2 -1
  282. package/.next/dev/static/chunks/05w9_next_dist_shared_lib_0beh7rg._.js +0 -6077
  283. package/.next/dev/static/chunks/05w9_next_dist_shared_lib_0beh7rg._.js.map +0 -69
  284. package/.next/dev/static/chunks/05w9_next_dist_shared_lib_0pjsj.j._.js +0 -6318
  285. package/.next/dev/static/chunks/05w9_next_dist_shared_lib_0pjsj.j._.js.map +0 -71
  286. package/.next/dev/types/cache-life.d.ts +0 -145
  287. package/.next/dev/types/routes.d.ts +0 -84
  288. package/.next/dev/types/validator.ts +0 -178
  289. /package/.next/static/{dJlbRLlhISA0JGtHKVqgQ → 8H2I8TTPlrKuWCV-SxY5f}/_buildManifest.js +0 -0
  290. /package/.next/static/{dJlbRLlhISA0JGtHKVqgQ → 8H2I8TTPlrKuWCV-SxY5f}/_clientMiddlewareManifest.js +0 -0
  291. /package/.next/static/{dJlbRLlhISA0JGtHKVqgQ → 8H2I8TTPlrKuWCV-SxY5f}/_ssgManifest.js +0 -0
@@ -0,0 +1,388 @@
1
+ "use strict";
2
+ /**
3
+ * Detect whether each supported CLI is installed and runnable.
4
+ *
5
+ * Layered probe:
6
+ * 1. PATH lookup via `which` (Unix) / `where` (Windows)
7
+ * 2. Fallback known install dirs per CLI (covers OpenCode/Kimi installers
8
+ * that drop binaries in non-PATH locations)
9
+ * 3. Verify with `<bin> --version` (2s timeout) so dead symlinks fail
10
+ *
11
+ * Cursor and Windsurf are IDEs invoked via MCP, not CLIs on PATH, so they're
12
+ * not part of this probe — onboarding leaves their checkboxes for the user.
13
+ */
14
+ var __importDefault = (this && this.__importDefault) || function (mod) {
15
+ return (mod && mod.__esModule) ? mod : { "default": mod };
16
+ };
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.detectAllClis = detectAllClis;
19
+ exports.clearDetectionCache = clearDetectionCache;
20
+ exports.validateCliPath = validateCliPath;
21
+ const child_process_1 = require("child_process");
22
+ const fs_1 = require("fs");
23
+ const os_1 = require("os");
24
+ const path_1 = __importDefault(require("path"));
25
+ const cli_paths_js_1 = require("./cli-paths.js");
26
+ const BINARY_NAME = {
27
+ 'claude-code': 'claude',
28
+ 'codex-cli': 'codex',
29
+ 'gemini-cli': 'gemini',
30
+ 'opencode-cli': 'opencode',
31
+ 'kimi-cli': 'kimi',
32
+ };
33
+ const isWindows = (0, os_1.platform)() === 'win32';
34
+ const HOME = (0, os_1.homedir)();
35
+ /**
36
+ * Discovered npm prefix bin dirs — populated lazily by `discoverNpmPrefixes`.
37
+ * Cached at module scope so we shell out once per daemon lifetime, not once
38
+ * per CLI per probe.
39
+ */
40
+ let cachedNpmDirs = null;
41
+ /**
42
+ * Ask npm where it installs global binaries. Covers users with custom
43
+ * `prefix` configs (e.g. ~/.npm-global, ~/.config/npm), nvm-active
44
+ * versions, or an OS package manager that put npm in a non-default
45
+ * location. Best-effort — a missing/broken npm just yields an empty
46
+ * list so we fall back to the static probes.
47
+ */
48
+ function discoverNpmPrefixes() {
49
+ if (cachedNpmDirs !== null)
50
+ return cachedNpmDirs;
51
+ const dirs = new Set();
52
+ // Try `npm config get prefix` (1s budget — slow npm shouldn't block detect).
53
+ try {
54
+ const result = (0, child_process_1.spawnSync)('npm', ['config', 'get', 'prefix'], {
55
+ encoding: 'utf-8',
56
+ timeout: 1000,
57
+ stdio: ['ignore', 'pipe', 'ignore'],
58
+ });
59
+ if (result.status === 0) {
60
+ const prefix = result.stdout.trim();
61
+ if (prefix) {
62
+ dirs.add(isWindows ? prefix : path_1.default.join(prefix, 'bin'));
63
+ }
64
+ }
65
+ }
66
+ catch {
67
+ /* npm not installed / not on PATH — fall through */
68
+ }
69
+ // NPM_CONFIG_PREFIX env override (common in CI, asdf, custom shells).
70
+ const envPrefix = process.env.NPM_CONFIG_PREFIX || process.env.npm_config_prefix;
71
+ if (envPrefix) {
72
+ dirs.add(isWindows ? envPrefix : path_1.default.join(envPrefix, 'bin'));
73
+ }
74
+ cachedNpmDirs = Array.from(dirs);
75
+ return cachedNpmDirs;
76
+ }
77
+ /**
78
+ * Per-CLI fallback install locations to probe when PATH lookup misses.
79
+ *
80
+ * Covers the common installers in the wild — npm global with various
81
+ * prefixes (default, custom, volta, fnm, nvm), Yarn, PNPM, Bun, Cargo,
82
+ * Homebrew (Intel + Apple Silicon), system package dirs — plus per-CLI
83
+ * installer-script locations (OpenCode + Kimi drop binaries in their
84
+ * own dot-folders by default).
85
+ *
86
+ * Order matters: PATH lookup runs first; this list is the second-chance
87
+ * scan, so cheap/common locations come first to keep the probe fast.
88
+ */
89
+ function fallbackPaths(cli) {
90
+ const bin = BINARY_NAME[cli];
91
+ const exts = isWindows ? ['.cmd', '.exe', ''] : [''];
92
+ const dirs = [];
93
+ if (isWindows) {
94
+ if (process.env.APPDATA)
95
+ dirs.push(path_1.default.join(process.env.APPDATA, 'npm'));
96
+ if (process.env.LOCALAPPDATA) {
97
+ dirs.push(path_1.default.join(process.env.LOCALAPPDATA, 'Programs'),
98
+ // Volta on Windows
99
+ path_1.default.join(process.env.LOCALAPPDATA, 'Volta', 'bin'));
100
+ }
101
+ dirs.push(path_1.default.join(HOME, 'AppData', 'Roaming', 'npm'), path_1.default.join(HOME, '.volta', 'bin'), path_1.default.join(HOME, '.bun', 'bin'));
102
+ }
103
+ else {
104
+ dirs.push(
105
+ // User-local
106
+ path_1.default.join(HOME, '.local', 'bin'), path_1.default.join(HOME, '.npm-global', 'bin'), path_1.default.join(HOME, '.config', 'yarn', 'global', 'node_modules', '.bin'), path_1.default.join(HOME, '.yarn', 'bin'),
107
+ // Node version managers
108
+ path_1.default.join(HOME, '.volta', 'bin'), path_1.default.join(HOME, '.fnm', 'aliases', 'default', 'bin'),
109
+ // Alt package managers
110
+ path_1.default.join(HOME, '.bun', 'bin'), path_1.default.join(HOME, '.cargo', 'bin'), path_1.default.join(HOME, '.local', 'share', 'pnpm'), path_1.default.join(HOME, 'Library', 'pnpm'),
111
+ // System-wide
112
+ '/usr/local/bin', '/opt/homebrew/bin', '/usr/bin',
113
+ // Common npm-global system dirs
114
+ '/usr/local/lib/node_modules/.bin', '/opt/homebrew/lib/node_modules/.bin');
115
+ }
116
+ // CLI-specific installer locations (their own install scripts).
117
+ if (cli === 'opencode-cli') {
118
+ dirs.push(path_1.default.join(HOME, '.opencode', 'bin'));
119
+ }
120
+ if (cli === 'kimi-cli') {
121
+ dirs.push(path_1.default.join(HOME, '.kimi', 'bin'));
122
+ }
123
+ // npm-discovered prefixes — cheapest signal for "where did the user
124
+ // actually install global packages?". Pulled last so the static list
125
+ // wins on cache hits, but still covers exotic setups.
126
+ for (const npmDir of discoverNpmPrefixes()) {
127
+ dirs.push(npmDir);
128
+ }
129
+ const candidates = [];
130
+ // Dedup while preserving order — a user with PNPM_HOME=~/.local/share/pnpm
131
+ // shouldn't pay for two probes of the same dir.
132
+ const seen = new Set();
133
+ for (const dir of dirs) {
134
+ for (const ext of exts) {
135
+ const full = path_1.default.join(dir, `${bin}${ext}`);
136
+ if (seen.has(full))
137
+ continue;
138
+ seen.add(full);
139
+ candidates.push(full);
140
+ }
141
+ }
142
+ return candidates;
143
+ }
144
+ function pathLookup(name) {
145
+ const cmd = isWindows ? 'where' : 'which';
146
+ const result = (0, child_process_1.spawnSync)(cmd, [name], { encoding: 'utf-8' });
147
+ if (result.status !== 0)
148
+ return null;
149
+ // `where` returns one path per line on Windows; take the first.
150
+ const first = result.stdout.split(/\r?\n/).map((s) => s.trim()).find((s) => s.length > 0);
151
+ return first || null;
152
+ }
153
+ /**
154
+ * Per-CLI version-output signatures. The shape varies wildly between
155
+ * CLIs — claude prints "2.1.126 (Claude Code)", codex prints
156
+ * "codex-cli 0.128.0", but gemini / opencode print just a bare
157
+ * version like "0.40.1" with NO CLI name. So each CLI gets its own
158
+ * matcher rather than a uniform name-substring regex.
159
+ *
160
+ * Strategy:
161
+ * - CLIs whose output includes the name → match the name (case-insensitive).
162
+ * - CLIs that print a bare version → accept output that STARTS with a
163
+ * digit-dot-digit (semver-ish). This rules out e.g. `cat --version`
164
+ * (output starts with "cat (GNU…") while accepting any reasonable
165
+ * `<bin> --version` from a real install.
166
+ *
167
+ * False positives here (a runnable binary that happens to print a bare
168
+ * version) are still better than rubber-stamping `cat --version` as a
169
+ * valid Claude install — which is what the original exit-0-only check
170
+ * did.
171
+ */
172
+ const STARTS_WITH_VERSION = /^\s*\d+\.\d+/;
173
+ const CLI_SIGNATURES = {
174
+ 'claude-code': /\bclaude\b/i,
175
+ 'codex-cli': /\bcodex\b/i,
176
+ // Bare version output — "0.40.1" — no CLI name to grep for.
177
+ 'gemini-cli': STARTS_WITH_VERSION,
178
+ // Bare version output — "1.14.30" — same as gemini.
179
+ 'opencode-cli': STARTS_WITH_VERSION,
180
+ 'kimi-cli': /\bkimi\b/i,
181
+ };
182
+ /**
183
+ * Basename allowlist — the binary's filename must match the expected
184
+ * name (case-insensitive, .exe/.cmd suffixes stripped) before we even
185
+ * try to verify the version output. This is the primary guard against
186
+ * the STARTS_WITH_VERSION regex being too permissive: `npm --version`
187
+ * prints "11.7.0" and would otherwise pass as gemini/opencode. The
188
+ * regex stays as a secondary check — a binary that's named correctly
189
+ * but whose --version output is total junk still gets rejected.
190
+ *
191
+ * Validated locally before adding (2026-05-04):
192
+ * npm 11.7.0 → matches version regex (would've false-passed)
193
+ * pip 24.0 → no (starts with "pip ")
194
+ * node v20.19 → no (starts with "v")
195
+ * python 3.12 → no (starts with "Python ")
196
+ */
197
+ function basenameMatches(cli, binPath) {
198
+ const expected = BINARY_NAME[cli].toLowerCase();
199
+ const base = path_1.default.basename(binPath).toLowerCase();
200
+ // Strip Windows extensions so claude.exe / claude.cmd both match "claude".
201
+ const stripped = base.replace(/\.(exe|cmd|bat|ps1)$/i, '');
202
+ return stripped === expected;
203
+ }
204
+ function verifyRunnable(cli, binPath, timeoutMs = 2000) {
205
+ if (!(0, fs_1.existsSync)(binPath)) {
206
+ return { ok: false, reason: 'no file at that path' };
207
+ }
208
+ if (!basenameMatches(cli, binPath)) {
209
+ return {
210
+ ok: false,
211
+ reason: `expected a binary named "${BINARY_NAME[cli]}" — got "${path_1.default.basename(binPath)}"`,
212
+ };
213
+ }
214
+ let result;
215
+ try {
216
+ result = (0, child_process_1.spawnSync)(binPath, ['--version'], {
217
+ encoding: 'utf-8',
218
+ timeout: timeoutMs,
219
+ stdio: ['ignore', 'pipe', 'pipe'],
220
+ });
221
+ }
222
+ catch (err) {
223
+ return { ok: false, reason: `failed to spawn (${err.message})` };
224
+ }
225
+ if (result.status !== 0) {
226
+ return {
227
+ ok: false,
228
+ reason: `${path_1.default.basename(binPath)} --version exited ${result.status}`,
229
+ };
230
+ }
231
+ const output = `${result.stdout ?? ''}\n${result.stderr ?? ''}`;
232
+ const signature = CLI_SIGNATURES[cli];
233
+ if (!signature.test(output)) {
234
+ return {
235
+ ok: false,
236
+ reason: `that binary ran, but its --version output doesn't look like the ${BINARY_NAME[cli]} CLI`,
237
+ };
238
+ }
239
+ return { ok: true };
240
+ }
241
+ function detectOne(cli) {
242
+ // 0. User-supplied manual path wins over PATH/fallback. Without this,
243
+ // a fresh boot's PATH lookup would silently miss a CLI in a custom
244
+ // location even though the user had explicitly told us where to
245
+ // find it via the onboarding "I know where it is" affordance.
246
+ // Read from the sync cache populated at daemon boot — async settings
247
+ // fetch isn't available here without refactoring every detect caller.
248
+ const manual = cli_paths_js_1.cliPaths.getCached(cli);
249
+ if (manual && (0, fs_1.existsSync)(manual) && verifyRunnable(cli, manual).ok) {
250
+ return { id: cli, found: true, path: manual, source: 'manual' };
251
+ }
252
+ // 1. PATH lookup
253
+ const onPath = pathLookup(BINARY_NAME[cli]);
254
+ if (onPath && verifyRunnable(cli, onPath).ok) {
255
+ return { id: cli, found: true, path: onPath, source: 'path' };
256
+ }
257
+ // 2. Fallback known dirs
258
+ for (const candidate of fallbackPaths(cli)) {
259
+ if ((0, fs_1.existsSync)(candidate) && verifyRunnable(cli, candidate).ok) {
260
+ return { id: cli, found: true, path: candidate, source: 'fallback' };
261
+ }
262
+ }
263
+ return { id: cli, found: false };
264
+ }
265
+ /**
266
+ * Cache for `detectAllClis` — every detect spawns up to 5 child
267
+ * processes (one `--version` per CLI), so when a daemon route resolves
268
+ * a CLI path on every HTTP poll (e.g. /orchestrators/opencode/models
269
+ * during onboarding), we'd fork ~50 processes/second without a cache.
270
+ *
271
+ * 30s TTL is short enough that a `chorus install <cli>` run from a
272
+ * different shell shows up before the user finishes onboarding, but
273
+ * long enough that a polling UI doesn't keep retriggering scans.
274
+ * Tests can call detectAllClis with `force: true` to bypass.
275
+ */
276
+ let detectCache = null;
277
+ const DETECT_CACHE_TTL_MS = 30_000;
278
+ function detectAllClis(force = false) {
279
+ const now = Date.now();
280
+ if (!force && detectCache && detectCache.expiresAt > now) {
281
+ return detectCache.results;
282
+ }
283
+ const results = Object.keys(BINARY_NAME).map(detectOne);
284
+ detectCache = { results, expiresAt: now + DETECT_CACHE_TTL_MS };
285
+ return results;
286
+ }
287
+ /** Clear the detection cache. Used by callers that mutated state we
288
+ * know will change the answer (e.g. user just supplied a manual path,
289
+ * or installed a new CLI via the cockpit). */
290
+ function clearDetectionCache() {
291
+ detectCache = null;
292
+ }
293
+ /**
294
+ * Validate a user-supplied path for a given CLI. Used by the
295
+ * "Set path manually" fallback when auto-detect misses.
296
+ *
297
+ * Layered checks (any failure returns found=false + a reason):
298
+ * 1. Basename matches the expected binary name. Catches the common
299
+ * paste-the-wrong-tool mistake — e.g. `/usr/bin/npm` for gemini-cli
300
+ * passes the bare-version regex but its basename is `npm`, not
301
+ * `gemini`. Both round-1 reviewers (claude + gemini) flagged this
302
+ * gap when smoke-testing the previous version.
303
+ * 2. File exists.
304
+ * 3. `--version` exits 0 with output that matches the CLI's signature.
305
+ *
306
+ * Auto-detect doesn't need step 1 — pathLookup/fallback already bound
307
+ * the search to the expected binary name.
308
+ */
309
+ function validateCliPath(cli, customPath) {
310
+ const trimmed = customPath.trim();
311
+ if (!trimmed)
312
+ return { id: cli, found: false, reason: 'path is empty' };
313
+ // Basename gate — strip extension on Windows so claude.cmd / claude.exe
314
+ // both match `claude`.
315
+ const expectedBin = BINARY_NAME[cli];
316
+ const actualBase = isWindows
317
+ ? path_1.default.basename(trimmed).replace(/\.(cmd|exe)$/i, '')
318
+ : path_1.default.basename(trimmed);
319
+ if (actualBase.toLowerCase() !== expectedBin.toLowerCase()) {
320
+ return {
321
+ id: cli,
322
+ found: false,
323
+ reason: `that file is named "${actualBase}", but the ${cli} binary should be "${expectedBin}". Pasted the wrong path?`,
324
+ };
325
+ }
326
+ // Symlink-aware validation:
327
+ // - Reject sockets / pipes / directories outright (lstat).
328
+ // - For symlinks, realpath the target and re-check it's a regular
329
+ // file. This keeps Homebrew / asdf / mise / npm-global / fnm /
330
+ // volta installs working (most ship the user-facing binary as a
331
+ // symlink) while neutralising the TOCTOU attack from Audit D3:
332
+ // we persist the CANONICAL path, so a later swap of the symlink
333
+ // can't redirect daemon spawns to a malicious binary.
334
+ // - existsSync alone (the pre-fix call) followed symlinks silently
335
+ // and stored the symlink path — that's the attack surface we're
336
+ // closing.
337
+ let canonical = trimmed;
338
+ let lstat;
339
+ try {
340
+ lstat = (0, fs_1.lstatSync)(trimmed);
341
+ }
342
+ catch {
343
+ return { id: cli, found: false, reason: `no file at ${trimmed}` };
344
+ }
345
+ if (lstat.isSymbolicLink()) {
346
+ try {
347
+ canonical = path_1.default.resolve(path_1.default.dirname(trimmed),
348
+ // realpath resolves the chain; spawning the canonical target
349
+ // means a later symlink-swap can't redirect us.
350
+ (0, fs_1.realpathSync)(trimmed));
351
+ const realStat = (0, fs_1.lstatSync)(canonical);
352
+ if (!realStat.isFile()) {
353
+ return {
354
+ id: cli,
355
+ found: false,
356
+ reason: `symlink target is not a regular file: ${canonical}`,
357
+ };
358
+ }
359
+ }
360
+ catch {
361
+ return {
362
+ id: cli,
363
+ found: false,
364
+ reason: `symlink could not be resolved: ${trimmed}`,
365
+ };
366
+ }
367
+ }
368
+ else if (!lstat.isFile()) {
369
+ return {
370
+ id: cli,
371
+ found: false,
372
+ reason: `path is not a regular file: ${trimmed}`,
373
+ };
374
+ }
375
+ // Verify via the user-pasted path so verifyRunnable's internal
376
+ // basename check passes — "claude" symlinked to a versioned target
377
+ // like "2.1.126" is the canonical real-world install (npm/Homebrew
378
+ // both do this). Spawning the symlink path runs the same binary as
379
+ // spawning the resolved target since the kernel follows the link.
380
+ const v = verifyRunnable(cli, trimmed);
381
+ if (!v.ok)
382
+ return { id: cli, found: false, reason: v.reason };
383
+ // Persist the canonical (realpath-resolved) target. Daemon spawns
384
+ // will hit the resolved binary even if the symlink is later swapped
385
+ // by an attacker — closes the TOCTOU window from Audit D3.
386
+ return { id: cli, found: true, path: canonical, source: 'manual' };
387
+ }
388
+ //# sourceMappingURL=cli-detect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-detect.js","sourceRoot":"","sources":["../../src/lib/cli-detect.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,156 @@
1
+ "use strict";
2
+ /**
3
+ * Per-CLI health state, persisted in the settings table.
4
+ *
5
+ * Records the most recent failure mode the error-detector observed per
6
+ * lineage, plus a timestamp and (when available) a reset-at time so the UI
7
+ * can show "Codex resets at 10:05 PM".
8
+ *
9
+ * Recorded by: daemon's runner when it forwards cli_error events.
10
+ * Read by: home-page CLI status panel via GET /cli/health.
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.recordHealth = recordHealth;
14
+ exports.getHealth = getHealth;
15
+ exports.getAllHealth = getAllHealth;
16
+ exports.clearStaleHealth = clearStaleHealth;
17
+ exports.kindToStatus = kindToStatus;
18
+ exports.classifyOpenRouterError = classifyOpenRouterError;
19
+ const db_1 = require("./db");
20
+ const KEY = (l) => `cli_health.${l}`;
21
+ const ALL_LINEAGES = [
22
+ 'anthropic',
23
+ 'openai',
24
+ 'google',
25
+ 'opencode',
26
+ 'moonshot',
27
+ 'openrouter',
28
+ ];
29
+ async function recordHealth(input) {
30
+ const payload = {
31
+ lineage: input.lineage,
32
+ status: input.status,
33
+ message: input.message,
34
+ resetAt: input.resetAt,
35
+ updatedAt: Date.now(),
36
+ };
37
+ await db_1.settings.set(KEY(input.lineage), payload);
38
+ }
39
+ async function getHealth(lineage) {
40
+ const raw = await db_1.settings.get(KEY(lineage));
41
+ if (raw && typeof raw === 'object' && 'status' in raw) {
42
+ return raw;
43
+ }
44
+ return {
45
+ lineage,
46
+ status: 'unknown',
47
+ updatedAt: 0,
48
+ };
49
+ }
50
+ async function getAllHealth() {
51
+ return Promise.all(ALL_LINEAGES.map(getHealth));
52
+ }
53
+ /**
54
+ * Sweep cli-health entries and reset any whose `resetAt` has passed.
55
+ * Called periodically from the reaper so the home-page fleet card flips
56
+ * back to green automatically once a quota window expires — no refresh,
57
+ * no manual intervention. Bounded by ALL_LINEAGES so this is O(7) DB
58
+ * roundtrips per tick (six writes max even in the worst case).
59
+ *
60
+ * Returns the list of lineages whose state was cleared so the caller
61
+ * can log them.
62
+ */
63
+ async function clearStaleHealth() {
64
+ const now = Date.now();
65
+ const cleared = [];
66
+ for (const lineage of ALL_LINEAGES) {
67
+ const h = await getHealth(lineage);
68
+ if (h.status !== 'healthy' &&
69
+ h.status !== 'unknown' &&
70
+ typeof h.resetAt === 'number' &&
71
+ h.resetAt > 0 &&
72
+ h.resetAt <= now) {
73
+ await recordHealth({ lineage, status: 'healthy' });
74
+ cleared.push(lineage);
75
+ }
76
+ }
77
+ return cleared;
78
+ }
79
+ /**
80
+ * Translate an error-detector CliErrorKind into a HealthStatus.
81
+ * Used by the runner when forwarding cli_error events.
82
+ */
83
+ function kindToStatus(kind) {
84
+ switch (kind) {
85
+ case 'quota_exhausted':
86
+ return 'quota_exhausted';
87
+ case 'token_refresh_lost':
88
+ case 'mcp_handshake_failed':
89
+ return 'auth_invalid';
90
+ default:
91
+ return 'unknown';
92
+ }
93
+ }
94
+ /**
95
+ * Classify a headless-shim error kind (e.g. `openrouter_402`,
96
+ * `openrouter_401`, `openrouter_429`) into a health status + friendlier
97
+ * one-liner the cockpit can show on the fleet card and run-page error
98
+ * banner. Returns null when the kind isn't a known OpenRouter HTTP code,
99
+ * so callers can fall through to generic handling.
100
+ *
101
+ * Reference: https://openrouter.ai/docs/errors — 402 means "your account
102
+ * is out of credits", 401 a bad/revoked key, 429 a rate cap, 403 a model
103
+ * the key isn't allowed to call (paid tier / BYOK), 5xx an upstream
104
+ * outage. We deliberately collapse 401 + 403 to auth_invalid since the
105
+ * remediation is the same (fix the key).
106
+ */
107
+ function classifyOpenRouterError(kind, message) {
108
+ const m = (message ?? '').trim();
109
+ if (kind === 'auth_missing') {
110
+ return {
111
+ status: 'auth_invalid',
112
+ message: 'No OpenRouter API key saved.',
113
+ cta: 'Add your key on the Connect page.',
114
+ };
115
+ }
116
+ if (!kind.startsWith('openrouter_'))
117
+ return null;
118
+ const statusCode = Number(kind.slice('openrouter_'.length));
119
+ if (statusCode === 402) {
120
+ return {
121
+ status: 'quota_exhausted',
122
+ message: m || 'OpenRouter account is out of credits.',
123
+ cta: 'Top up at openrouter.ai/credits.',
124
+ };
125
+ }
126
+ if (statusCode === 401 || statusCode === 403) {
127
+ return {
128
+ status: 'auth_invalid',
129
+ message: m || 'OpenRouter rejected the API key.',
130
+ cta: 'Replace the key on the Connect page.',
131
+ };
132
+ }
133
+ if (statusCode === 429) {
134
+ return {
135
+ status: 'rate_limited',
136
+ message: m || 'OpenRouter rate-limited the request.',
137
+ cta: 'Slow down or pick a higher-tier model.',
138
+ };
139
+ }
140
+ if (statusCode === 404) {
141
+ return {
142
+ status: 'unknown',
143
+ message: m || 'OpenRouter could not find the requested model.',
144
+ cta: 'Pick a different model on the Connect page.',
145
+ };
146
+ }
147
+ if (statusCode >= 500 && statusCode < 600) {
148
+ return {
149
+ status: 'rate_limited',
150
+ message: m || `OpenRouter upstream error (${statusCode}).`,
151
+ cta: 'Try again in a moment.',
152
+ };
153
+ }
154
+ return null;
155
+ }
156
+ //# sourceMappingURL=cli-health.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-health.js","sourceRoot":"","sources":["../../src/lib/cli-health.ts"],"names":[],"mappings":"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"}