@build-astron-co/nimbus 0.2.0 → 0.4.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 (469) hide show
  1. package/bin/nimbus +26 -10
  2. package/bin/nimbus.cmd +41 -0
  3. package/bin/nimbus.mjs +70 -0
  4. package/completions/nimbus.bash +38 -0
  5. package/completions/nimbus.fish +48 -0
  6. package/completions/nimbus.zsh +81 -0
  7. package/dist/src/agent/compaction-agent.js +215 -0
  8. package/dist/src/agent/context-manager.js +385 -0
  9. package/dist/src/agent/context.js +322 -0
  10. package/dist/src/agent/deploy-preview.js +395 -0
  11. package/dist/src/agent/expand-files.js +95 -0
  12. package/dist/src/agent/index.js +18 -0
  13. package/dist/src/agent/loop.js +1535 -0
  14. package/dist/src/agent/modes.js +347 -0
  15. package/dist/src/agent/permissions.js +396 -0
  16. package/dist/src/agent/subagents/base.js +67 -0
  17. package/dist/src/agent/subagents/cost.js +45 -0
  18. package/dist/src/agent/subagents/explore.js +36 -0
  19. package/dist/src/agent/subagents/general.js +41 -0
  20. package/dist/src/agent/subagents/index.js +88 -0
  21. package/dist/src/agent/subagents/infra.js +52 -0
  22. package/dist/src/agent/subagents/security.js +60 -0
  23. package/dist/src/agent/system-prompt.js +860 -0
  24. package/dist/src/app.js +152 -0
  25. package/dist/src/audit/activity-log.js +209 -0
  26. package/dist/src/audit/compliance-checker.js +419 -0
  27. package/dist/src/audit/cost-tracker.js +231 -0
  28. package/dist/src/audit/index.js +10 -0
  29. package/dist/src/audit/security-scanner.js +490 -0
  30. package/dist/src/auth/guard.js +64 -0
  31. package/dist/src/auth/index.js +19 -0
  32. package/dist/src/auth/keychain.js +79 -0
  33. package/dist/src/auth/oauth.js +389 -0
  34. package/dist/src/auth/providers.js +415 -0
  35. package/dist/src/auth/sso.js +87 -0
  36. package/dist/src/auth/store.js +424 -0
  37. package/dist/src/auth/types.js +5 -0
  38. package/dist/src/cli/index.js +8 -0
  39. package/dist/src/cli/init.js +1048 -0
  40. package/dist/src/cli/openapi-spec.js +346 -0
  41. package/dist/src/cli/run.js +505 -0
  42. package/dist/src/cli/serve-auth.js +56 -0
  43. package/dist/src/cli/serve.js +432 -0
  44. package/dist/src/cli/web.js +50 -0
  45. package/dist/src/cli.js +1574 -0
  46. package/dist/src/clients/core-engine-client.js +156 -0
  47. package/dist/src/clients/enterprise-client.js +246 -0
  48. package/dist/src/clients/generator-client.js +219 -0
  49. package/dist/src/clients/git-client.js +367 -0
  50. package/dist/src/clients/github-client.js +229 -0
  51. package/dist/src/clients/helm-client.js +299 -0
  52. package/dist/src/clients/index.js +18 -0
  53. package/dist/src/clients/k8s-client.js +270 -0
  54. package/dist/src/clients/llm-client.js +119 -0
  55. package/dist/src/clients/rest-client.js +104 -0
  56. package/dist/src/clients/service-discovery.js +35 -0
  57. package/dist/src/clients/terraform-client.js +302 -0
  58. package/dist/src/clients/tools-client.js +1227 -0
  59. package/dist/src/clients/ws-client.js +93 -0
  60. package/dist/src/commands/alias.js +91 -0
  61. package/dist/src/commands/analyze/index.js +313 -0
  62. package/dist/src/commands/apply/helm.js +375 -0
  63. package/dist/src/commands/apply/index.js +176 -0
  64. package/dist/src/commands/apply/k8s.js +350 -0
  65. package/dist/src/commands/apply/terraform.js +465 -0
  66. package/dist/src/commands/ask.js +137 -0
  67. package/dist/src/commands/audit/index.js +322 -0
  68. package/dist/src/commands/auth-cloud.js +345 -0
  69. package/dist/src/commands/auth-list.js +112 -0
  70. package/dist/src/commands/auth-profile.js +104 -0
  71. package/dist/src/commands/auth-refresh.js +161 -0
  72. package/dist/src/commands/auth-status.js +122 -0
  73. package/dist/src/commands/aws/ec2.js +402 -0
  74. package/dist/src/commands/aws/iam.js +304 -0
  75. package/dist/src/commands/aws/index.js +108 -0
  76. package/dist/src/commands/aws/lambda.js +317 -0
  77. package/dist/src/commands/aws/rds.js +345 -0
  78. package/dist/src/commands/aws/s3.js +346 -0
  79. package/dist/src/commands/aws/vpc.js +302 -0
  80. package/dist/src/commands/aws-discover.js +413 -0
  81. package/dist/src/commands/aws-terraform.js +618 -0
  82. package/dist/src/commands/azure/aks.js +305 -0
  83. package/dist/src/commands/azure/functions.js +200 -0
  84. package/dist/src/commands/azure/index.js +93 -0
  85. package/dist/src/commands/azure/storage.js +378 -0
  86. package/dist/src/commands/azure/vm.js +291 -0
  87. package/dist/src/commands/billing/index.js +224 -0
  88. package/dist/src/commands/chat.js +259 -0
  89. package/dist/src/commands/completions.js +255 -0
  90. package/dist/src/commands/config.js +291 -0
  91. package/dist/src/commands/cost/cloud-cost-estimator.js +211 -0
  92. package/dist/src/commands/cost/estimator.js +73 -0
  93. package/dist/src/commands/cost/index.js +625 -0
  94. package/dist/src/commands/cost/parsers/terraform.js +234 -0
  95. package/dist/src/commands/cost/parsers/types.js +4 -0
  96. package/dist/src/commands/cost/pricing/aws.js +501 -0
  97. package/dist/src/commands/cost/pricing/azure.js +462 -0
  98. package/dist/src/commands/cost/pricing/gcp.js +359 -0
  99. package/dist/src/commands/cost/pricing/index.js +24 -0
  100. package/dist/src/commands/demo.js +196 -0
  101. package/dist/src/commands/deploy.js +215 -0
  102. package/dist/src/commands/doctor.js +1291 -0
  103. package/dist/src/commands/drift/index.js +674 -0
  104. package/dist/src/commands/explain.js +235 -0
  105. package/dist/src/commands/export.js +120 -0
  106. package/dist/src/commands/feedback.js +319 -0
  107. package/dist/src/commands/fix.js +263 -0
  108. package/dist/src/commands/fs/index.js +338 -0
  109. package/dist/src/commands/gcp/compute.js +266 -0
  110. package/dist/src/commands/gcp/functions.js +221 -0
  111. package/dist/src/commands/gcp/gke.js +357 -0
  112. package/dist/src/commands/gcp/iam.js +295 -0
  113. package/dist/src/commands/gcp/index.js +105 -0
  114. package/dist/src/commands/gcp/storage.js +232 -0
  115. package/dist/src/commands/generate-helm.js +1026 -0
  116. package/dist/src/commands/generate-k8s.js +1263 -0
  117. package/dist/src/commands/generate-terraform.js +1058 -0
  118. package/dist/src/commands/gh/index.js +663 -0
  119. package/dist/src/commands/git/index.js +1208 -0
  120. package/dist/src/commands/helm/index.js +985 -0
  121. package/dist/src/commands/help.js +639 -0
  122. package/dist/src/commands/history.js +120 -0
  123. package/dist/src/commands/import.js +782 -0
  124. package/dist/src/commands/incident.js +144 -0
  125. package/dist/src/commands/index.js +109 -0
  126. package/dist/src/commands/init.js +955 -0
  127. package/dist/src/commands/k8s/index.js +979 -0
  128. package/dist/src/commands/login.js +588 -0
  129. package/dist/src/commands/logout.js +61 -0
  130. package/dist/src/commands/logs.js +160 -0
  131. package/dist/src/commands/onboarding.js +382 -0
  132. package/dist/src/commands/pipeline.js +153 -0
  133. package/dist/src/commands/plan/display.js +216 -0
  134. package/dist/src/commands/plan/index.js +525 -0
  135. package/dist/src/commands/plugin.js +325 -0
  136. package/dist/src/commands/preview.js +356 -0
  137. package/dist/src/commands/profile.js +297 -0
  138. package/dist/src/commands/questionnaire.js +1021 -0
  139. package/dist/src/commands/resume.js +35 -0
  140. package/dist/src/commands/rollback.js +259 -0
  141. package/dist/src/commands/rollout.js +74 -0
  142. package/dist/src/commands/runbook.js +307 -0
  143. package/dist/src/commands/schedule.js +202 -0
  144. package/dist/src/commands/status.js +213 -0
  145. package/dist/src/commands/team/index.js +309 -0
  146. package/dist/src/commands/team-context.js +200 -0
  147. package/dist/src/commands/template.js +204 -0
  148. package/dist/src/commands/tf/index.js +989 -0
  149. package/dist/src/commands/upgrade.js +515 -0
  150. package/dist/src/commands/usage/index.js +118 -0
  151. package/dist/src/commands/version.js +145 -0
  152. package/dist/src/commands/watch.js +127 -0
  153. package/dist/src/compat/index.js +2 -0
  154. package/dist/src/compat/runtime.js +10 -0
  155. package/dist/src/compat/sqlite.js +144 -0
  156. package/dist/src/config/index.js +6 -0
  157. package/dist/src/config/manager.js +469 -0
  158. package/dist/src/config/mode-store.js +57 -0
  159. package/dist/src/config/profiles.js +66 -0
  160. package/dist/src/config/safety-policy.js +251 -0
  161. package/dist/src/config/schema.js +107 -0
  162. package/dist/src/config/types.js +311 -0
  163. package/dist/src/config/workspace-state.js +38 -0
  164. package/dist/src/context/context-db.js +138 -0
  165. package/dist/src/demo/index.js +295 -0
  166. package/dist/src/demo/scenarios/full-journey.js +226 -0
  167. package/dist/src/demo/scenarios/getting-started.js +124 -0
  168. package/dist/src/demo/scenarios/helm-release.js +334 -0
  169. package/dist/src/demo/scenarios/k8s-deployment.js +190 -0
  170. package/dist/src/demo/scenarios/terraform-vpc.js +167 -0
  171. package/dist/src/demo/types.js +6 -0
  172. package/dist/src/engine/cost-estimator.js +334 -0
  173. package/dist/src/engine/diagram-generator.js +192 -0
  174. package/dist/src/engine/drift-detector.js +688 -0
  175. package/dist/src/engine/executor.js +832 -0
  176. package/dist/src/engine/index.js +39 -0
  177. package/dist/src/engine/orchestrator.js +436 -0
  178. package/dist/src/engine/planner.js +616 -0
  179. package/dist/src/engine/safety.js +609 -0
  180. package/dist/src/engine/verifier.js +664 -0
  181. package/dist/src/enterprise/audit.js +241 -0
  182. package/dist/src/enterprise/auth.js +189 -0
  183. package/dist/src/enterprise/billing.js +512 -0
  184. package/dist/src/enterprise/index.js +16 -0
  185. package/dist/src/enterprise/teams.js +315 -0
  186. package/dist/src/generator/best-practices.js +1375 -0
  187. package/dist/src/generator/helm.js +495 -0
  188. package/dist/src/generator/index.js +11 -0
  189. package/dist/src/generator/intent-parser.js +420 -0
  190. package/dist/src/generator/kubernetes.js +773 -0
  191. package/dist/src/generator/terraform.js +1472 -0
  192. package/dist/src/history/index.js +6 -0
  193. package/dist/src/history/manager.js +199 -0
  194. package/dist/src/history/types.js +6 -0
  195. package/dist/src/hooks/config.js +318 -0
  196. package/dist/src/hooks/engine.js +317 -0
  197. package/dist/src/hooks/index.js +2 -0
  198. package/dist/src/llm/auth-bridge.js +157 -0
  199. package/dist/src/llm/circuit-breaker.js +116 -0
  200. package/dist/src/llm/config-loader.js +172 -0
  201. package/dist/src/llm/cost-calculator.js +137 -0
  202. package/dist/src/llm/index.js +7 -0
  203. package/dist/src/llm/model-aliases.js +99 -0
  204. package/dist/src/llm/provider-registry.js +57 -0
  205. package/dist/src/llm/providers/anthropic.js +430 -0
  206. package/dist/src/llm/providers/bedrock.js +409 -0
  207. package/dist/src/llm/providers/google.js +344 -0
  208. package/dist/src/llm/providers/ollama.js +661 -0
  209. package/dist/src/llm/providers/openai-compatible.js +289 -0
  210. package/dist/src/llm/providers/openai.js +284 -0
  211. package/dist/src/llm/providers/openrouter.js +293 -0
  212. package/dist/src/llm/router.js +844 -0
  213. package/dist/src/llm/types.js +69 -0
  214. package/dist/src/lsp/client.js +239 -0
  215. package/dist/src/lsp/languages.js +95 -0
  216. package/dist/src/lsp/manager.js +243 -0
  217. package/dist/src/mcp/client.js +289 -0
  218. package/dist/src/mcp/index.js +5 -0
  219. package/dist/src/mcp/manager.js +113 -0
  220. package/dist/src/nimbus.js +212 -0
  221. package/dist/src/plugins/index.js +13 -0
  222. package/dist/src/plugins/loader.js +280 -0
  223. package/dist/src/plugins/manager.js +282 -0
  224. package/dist/src/plugins/types.js +23 -0
  225. package/dist/src/scanners/cicd-scanner.js +230 -0
  226. package/dist/src/scanners/cloud-scanner.js +415 -0
  227. package/dist/src/scanners/framework-scanner.js +430 -0
  228. package/dist/src/scanners/iac-scanner.js +350 -0
  229. package/dist/src/scanners/index.js +454 -0
  230. package/dist/src/scanners/language-scanner.js +258 -0
  231. package/dist/src/scanners/package-manager-scanner.js +252 -0
  232. package/dist/src/scanners/types.js +6 -0
  233. package/dist/src/sessions/manager.js +395 -0
  234. package/dist/src/sessions/types.js +4 -0
  235. package/dist/src/sharing/sync.js +238 -0
  236. package/dist/src/sharing/viewer.js +131 -0
  237. package/dist/src/snapshots/index.js +1 -0
  238. package/dist/src/snapshots/manager.js +432 -0
  239. package/dist/src/state/artifacts.js +94 -0
  240. package/dist/src/state/audit.js +73 -0
  241. package/dist/src/state/billing.js +126 -0
  242. package/dist/src/state/checkpoints.js +81 -0
  243. package/dist/src/state/config.js +58 -0
  244. package/dist/src/state/conversations.js +7 -0
  245. package/dist/src/state/credentials.js +96 -0
  246. package/dist/src/state/db.js +53 -0
  247. package/dist/src/state/index.js +23 -0
  248. package/dist/src/state/messages.js +76 -0
  249. package/dist/src/state/projects.js +92 -0
  250. package/dist/src/state/schema.js +233 -0
  251. package/dist/src/state/sessions.js +79 -0
  252. package/dist/src/state/teams.js +131 -0
  253. package/dist/src/telemetry.js +91 -0
  254. package/dist/src/tools/aws-ops.js +747 -0
  255. package/dist/src/tools/azure-ops.js +491 -0
  256. package/dist/src/tools/file-ops.js +451 -0
  257. package/dist/src/tools/gcp-ops.js +559 -0
  258. package/dist/src/tools/git-ops.js +557 -0
  259. package/dist/src/tools/github-ops.js +460 -0
  260. package/dist/src/tools/helm-ops.js +634 -0
  261. package/dist/src/tools/index.js +16 -0
  262. package/dist/src/tools/k8s-ops.js +579 -0
  263. package/dist/src/tools/schemas/converter.js +129 -0
  264. package/dist/src/tools/schemas/devops.js +3319 -0
  265. package/dist/src/tools/schemas/index.js +19 -0
  266. package/dist/src/tools/schemas/standard.js +966 -0
  267. package/dist/src/tools/schemas/types.js +409 -0
  268. package/dist/src/tools/spawn-exec.js +109 -0
  269. package/dist/src/tools/terraform-ops.js +627 -0
  270. package/dist/src/types/config.js +1 -0
  271. package/dist/src/types/drift.js +4 -0
  272. package/dist/src/types/enterprise.js +5 -0
  273. package/dist/src/types/index.js +14 -0
  274. package/dist/src/types/plan.js +1 -0
  275. package/dist/src/types/request.js +1 -0
  276. package/dist/src/types/response.js +1 -0
  277. package/dist/src/types/service.js +1 -0
  278. package/dist/src/ui/App.js +1672 -0
  279. package/dist/src/ui/DeployPreview.js +60 -0
  280. package/dist/src/ui/FileDiffModal.js +108 -0
  281. package/dist/src/ui/Header.js +46 -0
  282. package/dist/src/ui/HelpModal.js +9 -0
  283. package/dist/src/ui/InputBox.js +408 -0
  284. package/dist/src/ui/MessageList.js +795 -0
  285. package/dist/src/ui/PermissionPrompt.js +72 -0
  286. package/dist/src/ui/StatusBar.js +109 -0
  287. package/dist/src/ui/TerminalPane.js +31 -0
  288. package/dist/src/ui/ToolCallDisplay.js +303 -0
  289. package/dist/src/ui/TreePane.js +83 -0
  290. package/dist/src/ui/chat-ui.js +721 -0
  291. package/dist/src/ui/index.js +11 -0
  292. package/dist/src/ui/ink/index.js +1325 -0
  293. package/dist/src/ui/streaming.js +137 -0
  294. package/dist/src/ui/theme.js +78 -0
  295. package/dist/src/ui/types.js +7 -0
  296. package/dist/src/utils/analytics.js +61 -0
  297. package/dist/src/utils/cost-warning.js +25 -0
  298. package/dist/src/utils/env.js +42 -0
  299. package/dist/src/utils/errors.js +54 -0
  300. package/dist/src/utils/event-bus.js +22 -0
  301. package/dist/src/utils/index.js +16 -0
  302. package/dist/src/utils/logger.js +150 -0
  303. package/dist/src/utils/rate-limiter.js +90 -0
  304. package/dist/src/utils/service-auth.js +36 -0
  305. package/dist/src/utils/validation.js +39 -0
  306. package/dist/src/version.js +3 -0
  307. package/dist/src/watcher/index.js +192 -0
  308. package/dist/src/wizard/approval.js +275 -0
  309. package/dist/src/wizard/index.js +13 -0
  310. package/dist/src/wizard/prompts.js +273 -0
  311. package/dist/src/wizard/types.js +4 -0
  312. package/dist/src/wizard/ui.js +453 -0
  313. package/dist/src/wizard/wizard.js +227 -0
  314. package/package.json +31 -23
  315. package/src/__tests__/alias.test.ts +133 -0
  316. package/src/__tests__/app.test.ts +1 -1
  317. package/src/__tests__/audit.test.ts +1 -1
  318. package/src/__tests__/circuit-breaker.test.ts +1 -1
  319. package/src/__tests__/cli-run.test.ts +237 -1
  320. package/src/__tests__/compat-sqlite.test.ts +68 -0
  321. package/src/__tests__/context-manager.test.ts +131 -1
  322. package/src/__tests__/context.test.ts +1 -1
  323. package/src/__tests__/devops-terminal-gaps.test.ts +718 -0
  324. package/src/__tests__/doctor.test.ts +48 -0
  325. package/src/__tests__/enterprise.test.ts +1 -1
  326. package/src/__tests__/export.test.ts +236 -0
  327. package/src/__tests__/gap-11-18-20.test.ts +958 -0
  328. package/src/__tests__/generator.test.ts +1 -1
  329. package/src/__tests__/helm-streaming.test.ts +127 -0
  330. package/src/__tests__/hooks.test.ts +1 -1
  331. package/src/__tests__/incident.test.ts +179 -0
  332. package/src/__tests__/init.test.ts +55 -4
  333. package/src/__tests__/intent-parser.test.ts +1 -1
  334. package/src/__tests__/llm-router.test.ts +1 -1
  335. package/src/__tests__/logs.test.ts +107 -0
  336. package/src/__tests__/loop-errors.test.ts +244 -0
  337. package/src/__tests__/lsp.test.ts +1 -1
  338. package/src/__tests__/modes.test.ts +1 -1
  339. package/src/__tests__/perf-optimizations.test.ts +847 -0
  340. package/src/__tests__/permissions.test.ts +1 -1
  341. package/src/__tests__/pipeline.test.ts +50 -0
  342. package/src/__tests__/polish-phase3.test.ts +340 -0
  343. package/src/__tests__/profile.test.ts +237 -0
  344. package/src/__tests__/rollback.test.ts +83 -0
  345. package/src/__tests__/runbook.test.ts +219 -0
  346. package/src/__tests__/schedule.test.ts +206 -0
  347. package/src/__tests__/serve.test.ts +1 -1
  348. package/src/__tests__/sessions.test.ts +96 -1
  349. package/src/__tests__/sharing.test.ts +53 -1
  350. package/src/__tests__/snapshots.test.ts +1 -1
  351. package/src/__tests__/standalone-migration.test.ts +199 -0
  352. package/src/__tests__/state-db.test.ts +1 -1
  353. package/src/__tests__/status.test.ts +158 -0
  354. package/src/__tests__/stream-with-tools.test.ts +71 -25
  355. package/src/__tests__/subagents.test.ts +1 -1
  356. package/src/__tests__/system-prompt.test.ts +82 -3
  357. package/src/__tests__/terminal-gap-v2.test.ts +395 -0
  358. package/src/__tests__/terminal-parity.test.ts +393 -0
  359. package/src/__tests__/tf-apply.test.ts +187 -0
  360. package/src/__tests__/tool-converter.test.ts +1 -1
  361. package/src/__tests__/tool-schemas.test.ts +209 -4
  362. package/src/__tests__/tools.test.ts +4 -3
  363. package/src/__tests__/version-json.test.ts +184 -0
  364. package/src/__tests__/version.test.ts +1 -1
  365. package/src/__tests__/watch.test.ts +129 -0
  366. package/src/agent/compaction-agent.ts +40 -1
  367. package/src/agent/context-manager.ts +67 -3
  368. package/src/agent/deploy-preview.ts +62 -1
  369. package/src/agent/expand-files.ts +108 -0
  370. package/src/agent/loop.ts +1312 -31
  371. package/src/agent/permissions.ts +51 -4
  372. package/src/agent/system-prompt.ts +573 -19
  373. package/src/app.ts +58 -0
  374. package/src/audit/security-scanner.ts +45 -0
  375. package/src/auth/keychain.ts +82 -0
  376. package/src/auth/oauth.ts +15 -5
  377. package/src/cli/init.ts +378 -5
  378. package/src/cli/run.ts +407 -16
  379. package/src/cli/serve.ts +78 -1
  380. package/src/cli/web.ts +10 -6
  381. package/src/cli.ts +312 -1
  382. package/src/clients/service-discovery.ts +30 -25
  383. package/src/commands/alias.ts +100 -0
  384. package/src/commands/audit/index.ts +121 -2
  385. package/src/commands/auth-cloud.ts +113 -0
  386. package/src/commands/auth-refresh.ts +187 -0
  387. package/src/commands/aws-discover.ts +144 -251
  388. package/src/commands/aws-terraform.ts +68 -118
  389. package/src/commands/chat.ts +9 -3
  390. package/src/commands/completions.ts +268 -0
  391. package/src/commands/config.ts +26 -0
  392. package/src/commands/cost/index.ts +218 -2
  393. package/src/commands/deploy.ts +260 -0
  394. package/src/commands/doctor.ts +744 -152
  395. package/src/commands/drift/index.ts +371 -23
  396. package/src/commands/export.ts +146 -0
  397. package/src/commands/generate-k8s.ts +9 -61
  398. package/src/commands/generate-terraform.ts +191 -449
  399. package/src/commands/help.ts +212 -36
  400. package/src/commands/history.ts +8 -1
  401. package/src/commands/incident.ts +166 -0
  402. package/src/commands/init.ts +5 -0
  403. package/src/commands/login.ts +86 -1
  404. package/src/commands/logs.ts +167 -0
  405. package/src/commands/onboarding.ts +211 -34
  406. package/src/commands/pipeline.ts +186 -0
  407. package/src/commands/plugin.ts +398 -0
  408. package/src/commands/profile.ts +342 -0
  409. package/src/commands/questionnaire.ts +0 -98
  410. package/src/commands/resume.ts +26 -34
  411. package/src/commands/rollback.ts +315 -0
  412. package/src/commands/rollout.ts +88 -0
  413. package/src/commands/runbook.ts +346 -0
  414. package/src/commands/schedule.ts +236 -0
  415. package/src/commands/status.ts +252 -0
  416. package/src/commands/team-context.ts +220 -0
  417. package/src/commands/template.ts +58 -57
  418. package/src/commands/tf/index.ts +70 -11
  419. package/src/commands/upgrade.ts +57 -0
  420. package/src/commands/version.ts +54 -50
  421. package/src/commands/watch.ts +153 -0
  422. package/src/compat/runtime.ts +1 -1
  423. package/src/compat/sqlite.ts +75 -5
  424. package/src/config/mode-store.ts +62 -0
  425. package/src/config/profiles.ts +84 -0
  426. package/src/config/types.ts +83 -1
  427. package/src/config/workspace-state.ts +53 -0
  428. package/src/engine/cost-estimator.ts +52 -10
  429. package/src/engine/executor.ts +33 -2
  430. package/src/engine/planner.ts +68 -1
  431. package/src/generator/terraform.ts +8 -0
  432. package/src/history/manager.ts +2 -74
  433. package/src/hooks/engine.ts +5 -4
  434. package/src/llm/cost-calculator.ts +2 -2
  435. package/src/llm/providers/anthropic.ts +50 -21
  436. package/src/llm/router.ts +76 -7
  437. package/src/lsp/languages.ts +3 -0
  438. package/src/lsp/manager.ts +21 -5
  439. package/src/nimbus.ts +37 -18
  440. package/src/sessions/manager.ts +108 -1
  441. package/src/sharing/sync.ts +4 -0
  442. package/src/sharing/viewer.ts +66 -0
  443. package/src/tools/file-ops.ts +22 -0
  444. package/src/tools/schemas/devops.ts +3007 -117
  445. package/src/tools/schemas/standard.ts +5 -1
  446. package/src/tools/schemas/types.ts +31 -1
  447. package/src/tools/spawn-exec.ts +148 -0
  448. package/src/ui/App.tsx +1183 -66
  449. package/src/ui/DeployPreview.tsx +62 -57
  450. package/src/ui/FileDiffModal.tsx +162 -0
  451. package/src/ui/Header.tsx +87 -24
  452. package/src/ui/HelpModal.tsx +57 -0
  453. package/src/ui/InputBox.tsx +163 -10
  454. package/src/ui/MessageList.tsx +487 -40
  455. package/src/ui/PermissionPrompt.tsx +17 -5
  456. package/src/ui/StatusBar.tsx +122 -3
  457. package/src/ui/TerminalPane.tsx +84 -0
  458. package/src/ui/ToolCallDisplay.tsx +252 -18
  459. package/src/ui/TreePane.tsx +132 -0
  460. package/src/ui/chat-ui.ts +41 -44
  461. package/src/ui/ink/index.ts +771 -38
  462. package/src/ui/streaming.ts +1 -1
  463. package/src/ui/theme.ts +104 -0
  464. package/src/ui/types.ts +18 -0
  465. package/src/version.ts +1 -1
  466. package/src/watcher/index.ts +66 -15
  467. package/src/wizard/types.ts +1 -0
  468. package/src/wizard/ui.ts +1 -1
  469. package/tsconfig.json +2 -2
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Base LLM Provider Interface
3
+ * Defines the contract that all LLM providers must implement
4
+ */
5
+ /**
6
+ * Extract the text content from a message's content field.
7
+ * Handles both plain strings and content block arrays.
8
+ */
9
+ export function getTextContent(content) {
10
+ if (typeof content === 'string') {
11
+ return content;
12
+ }
13
+ return content
14
+ .filter((b) => b.type === 'text')
15
+ .map(b => b.text)
16
+ .join('');
17
+ }
18
+ /**
19
+ * Check if a message contains image content blocks.
20
+ */
21
+ export function hasImageContent(content) {
22
+ if (typeof content === 'string') {
23
+ return false;
24
+ }
25
+ return content.some(b => b.type === 'image');
26
+ }
27
+ /**
28
+ * Base provider class with common utilities
29
+ */
30
+ export class BaseProvider {
31
+ /**
32
+ * Extract system prompt from messages
33
+ */
34
+ extractSystemPrompt(messages) {
35
+ const systemMessages = messages.filter(m => m.role === 'system');
36
+ if (systemMessages.length === 0) {
37
+ return undefined;
38
+ }
39
+ return systemMessages.map(m => getTextContent(m.content)).join('\n\n');
40
+ }
41
+ /**
42
+ * Filter out system messages
43
+ */
44
+ filterSystemMessages(messages) {
45
+ return messages.filter(m => m.role !== 'system');
46
+ }
47
+ /**
48
+ * Map finish reason to standard format
49
+ */
50
+ mapFinishReason(reason) {
51
+ if (!reason) {
52
+ return 'stop';
53
+ }
54
+ const normalized = reason.toLowerCase();
55
+ if (normalized.includes('stop') || normalized === 'end_turn') {
56
+ return 'stop';
57
+ }
58
+ if (normalized.includes('length') || normalized.includes('max_tokens')) {
59
+ return 'length';
60
+ }
61
+ if (normalized.includes('tool') || normalized.includes('function')) {
62
+ return 'tool_calls';
63
+ }
64
+ if (normalized.includes('content_filter') || normalized.includes('safety')) {
65
+ return 'content_filter';
66
+ }
67
+ return 'stop';
68
+ }
69
+ }
@@ -0,0 +1,239 @@
1
+ /**
2
+ * LSP Client -- JSON-RPC over stdio
3
+ *
4
+ * Implements the Language Server Protocol client that communicates
5
+ * with language servers via stdin/stdout using JSON-RPC 2.0.
6
+ */
7
+ import { spawn } from 'node:child_process';
8
+ import { EventEmitter } from 'node:events';
9
+ /** Severity labels for display. */
10
+ const SEVERITY_LABELS = {
11
+ 1: 'Error',
12
+ 2: 'Warning',
13
+ 3: 'Info',
14
+ 4: 'Hint',
15
+ };
16
+ export function severityLabel(severity) {
17
+ return SEVERITY_LABELS[severity] ?? 'Unknown';
18
+ }
19
+ /**
20
+ * LSP Client that communicates with a single language server.
21
+ */
22
+ export class LSPClient extends EventEmitter {
23
+ process = null;
24
+ config;
25
+ requestId = 0;
26
+ pending = new Map();
27
+ buffer = '';
28
+ initialized = false;
29
+ rootUri;
30
+ diagnostics = new Map();
31
+ constructor(config, rootUri) {
32
+ super();
33
+ this.config = config;
34
+ this.rootUri = rootUri;
35
+ }
36
+ /** Whether the client is connected and initialized. */
37
+ get isInitialized() {
38
+ return this.initialized;
39
+ }
40
+ /** Get the language ID. */
41
+ get languageId() {
42
+ return this.config.id;
43
+ }
44
+ /** Start the language server process. */
45
+ async start() {
46
+ try {
47
+ this.process = spawn(this.config.command, this.config.args, {
48
+ stdio: ['pipe', 'pipe', 'pipe'],
49
+ env: { ...process.env },
50
+ });
51
+ if (!this.process.stdout || !this.process.stdin) {
52
+ this.process.kill();
53
+ this.process = null;
54
+ return false;
55
+ }
56
+ this.process.stdout.on('data', (data) => {
57
+ this.handleData(data.toString());
58
+ });
59
+ this.process.stderr?.on('data', () => {
60
+ // Silently ignore stderr from language servers
61
+ });
62
+ this.process.on('exit', () => {
63
+ this.initialized = false;
64
+ this.process = null;
65
+ this.emit('exit');
66
+ });
67
+ // Send initialize request
68
+ const _initResult = await this.sendRequest('initialize', {
69
+ processId: process.pid,
70
+ rootUri: `file://${this.rootUri}`,
71
+ capabilities: {
72
+ textDocument: {
73
+ synchronization: { didOpen: true, didChange: true, didClose: true },
74
+ publishDiagnostics: { relatedInformation: true },
75
+ },
76
+ },
77
+ initializationOptions: this.config.initializationOptions ?? {},
78
+ });
79
+ // Send initialized notification
80
+ this.sendNotification('initialized', {});
81
+ this.initialized = true;
82
+ return true;
83
+ }
84
+ catch {
85
+ this.process = null;
86
+ return false;
87
+ }
88
+ }
89
+ /** Stop the language server. */
90
+ async stop() {
91
+ if (!this.process) {
92
+ return;
93
+ }
94
+ try {
95
+ await this.sendRequest('shutdown', null);
96
+ this.sendNotification('exit', null);
97
+ }
98
+ catch {
99
+ // Server may already be dead
100
+ }
101
+ // Force kill after 2 seconds
102
+ const proc = this.process;
103
+ setTimeout(() => {
104
+ if (proc && !proc.killed) {
105
+ proc.kill('SIGKILL');
106
+ }
107
+ }, 2000);
108
+ this.process = null;
109
+ this.initialized = false;
110
+ }
111
+ /** Notify the server that a file was opened or changed. */
112
+ async touchFile(filePath, content, version = 1) {
113
+ if (!this.initialized) {
114
+ return;
115
+ }
116
+ const uri = `file://${filePath}`;
117
+ const languageId = this.config.id;
118
+ // Send didOpen (simplified -- in a full implementation we'd track open state)
119
+ this.sendNotification('textDocument/didOpen', {
120
+ textDocument: { uri, languageId, version, text: content },
121
+ });
122
+ }
123
+ /** Request diagnostics for a file by waiting for publishDiagnostics. */
124
+ async getDiagnostics(filePath, timeoutMs = 2000) {
125
+ const uri = `file://${filePath}`;
126
+ // Return cached diagnostics if available
127
+ const cached = this.diagnostics.get(uri);
128
+ // Wait for new diagnostics up to timeout
129
+ return new Promise(resolve => {
130
+ const timer = setTimeout(() => {
131
+ resolve(cached ?? []);
132
+ }, timeoutMs);
133
+ const handler = (params) => {
134
+ if (params.uri === uri) {
135
+ clearTimeout(timer);
136
+ this.removeListener('diagnostics', handler);
137
+ resolve(this.diagnostics.get(uri) ?? []);
138
+ }
139
+ };
140
+ this.on('diagnostics', handler);
141
+ });
142
+ }
143
+ /** Get all cached diagnostics for a file. */
144
+ getCachedDiagnostics(filePath) {
145
+ return this.diagnostics.get(`file://${filePath}`) ?? [];
146
+ }
147
+ /** Send a JSON-RPC request and wait for response. */
148
+ sendRequest(method, params) {
149
+ return new Promise((resolve, reject) => {
150
+ if (!this.process?.stdin) {
151
+ reject(new Error('LSP process not running'));
152
+ return;
153
+ }
154
+ const id = ++this.requestId;
155
+ this.pending.set(id, { resolve, reject });
156
+ const message = JSON.stringify({ jsonrpc: '2.0', id, method, params });
157
+ const header = `Content-Length: ${Buffer.byteLength(message)}\r\n\r\n`;
158
+ this.process.stdin.write(header + message);
159
+ // Timeout after 10 seconds
160
+ setTimeout(() => {
161
+ if (this.pending.has(id)) {
162
+ this.pending.delete(id);
163
+ reject(new Error(`LSP request timeout: ${method}`));
164
+ }
165
+ }, 10000);
166
+ });
167
+ }
168
+ /** Send a JSON-RPC notification (no response expected). */
169
+ sendNotification(method, params) {
170
+ if (!this.process?.stdin) {
171
+ return;
172
+ }
173
+ const message = JSON.stringify({ jsonrpc: '2.0', method, params });
174
+ const header = `Content-Length: ${Buffer.byteLength(message)}\r\n\r\n`;
175
+ this.process.stdin.write(header + message);
176
+ }
177
+ /** Handle incoming data from the language server. */
178
+ handleData(data) {
179
+ this.buffer += data;
180
+ for (;;) {
181
+ const headerEnd = this.buffer.indexOf('\r\n\r\n');
182
+ if (headerEnd === -1) {
183
+ break;
184
+ }
185
+ const header = this.buffer.slice(0, headerEnd);
186
+ const contentLengthMatch = header.match(/Content-Length:\s*(\d+)/i);
187
+ if (!contentLengthMatch) {
188
+ this.buffer = this.buffer.slice(headerEnd + 4);
189
+ continue;
190
+ }
191
+ const contentLength = parseInt(contentLengthMatch[1], 10);
192
+ const bodyStart = headerEnd + 4;
193
+ if (this.buffer.length < bodyStart + contentLength) {
194
+ break;
195
+ }
196
+ const body = this.buffer.slice(bodyStart, bodyStart + contentLength);
197
+ this.buffer = this.buffer.slice(bodyStart + contentLength);
198
+ try {
199
+ const message = JSON.parse(body);
200
+ this.handleMessage(message);
201
+ }
202
+ catch {
203
+ // Ignore malformed messages
204
+ }
205
+ }
206
+ }
207
+ /** Handle a parsed JSON-RPC message. */
208
+ handleMessage(message) {
209
+ // Response to a request
210
+ if (message.id !== undefined && this.pending.has(message.id)) {
211
+ const handler = this.pending.get(message.id);
212
+ this.pending.delete(message.id);
213
+ if (message.error) {
214
+ handler.reject(new Error(message.error.message));
215
+ }
216
+ else {
217
+ handler.resolve(message.result);
218
+ }
219
+ return;
220
+ }
221
+ // Notification from server
222
+ if (message.method === 'textDocument/publishDiagnostics') {
223
+ const { uri, diagnostics: rawDiags } = message.params;
224
+ const parsed = rawDiags.map((d) => ({
225
+ file: uri.replace('file://', ''),
226
+ line: (d.range?.start?.line ?? 0) + 1,
227
+ column: (d.range?.start?.character ?? 0) + 1,
228
+ endLine: d.range?.end ? d.range.end.line + 1 : undefined,
229
+ endColumn: d.range?.end ? d.range.end.character + 1 : undefined,
230
+ severity: d.severity ?? 1,
231
+ message: d.message,
232
+ source: d.source,
233
+ code: d.code,
234
+ }));
235
+ this.diagnostics.set(uri, parsed);
236
+ this.emit('diagnostics', message.params);
237
+ }
238
+ }
239
+ }
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Language Server Configurations
3
+ *
4
+ * Defines the LSP binary names, install commands, and file associations
5
+ * for each supported language.
6
+ */
7
+ /** Supported language server configurations. */
8
+ export const LANGUAGE_CONFIGS = [
9
+ {
10
+ id: 'typescript',
11
+ name: 'TypeScript',
12
+ extensions: ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'],
13
+ command: 'typescript-language-server',
14
+ args: ['--stdio'],
15
+ installHint: 'npm install -g typescript-language-server typescript',
16
+ initializationOptions: {
17
+ preferences: {
18
+ includeInlayParameterNameHints: 'none',
19
+ },
20
+ },
21
+ },
22
+ {
23
+ id: 'go',
24
+ name: 'Go',
25
+ extensions: ['.go'],
26
+ command: 'gopls',
27
+ args: ['serve'],
28
+ installHint: 'go install golang.org/x/tools/gopls@latest',
29
+ },
30
+ {
31
+ id: 'python',
32
+ name: 'Python',
33
+ extensions: ['.py', '.pyi'],
34
+ command: 'pylsp',
35
+ args: [],
36
+ installHint: 'pip install python-lsp-server',
37
+ },
38
+ {
39
+ id: 'terraform',
40
+ name: 'Terraform/HCL',
41
+ extensions: ['.tf', '.tfvars', '.hcl'],
42
+ command: 'terraform-ls',
43
+ args: ['serve'],
44
+ installHint: 'brew install hashicorp/tap/terraform-ls',
45
+ },
46
+ {
47
+ id: 'yaml',
48
+ name: 'YAML',
49
+ extensions: ['.yaml', '.yml'],
50
+ command: 'yaml-language-server',
51
+ args: ['--stdio'],
52
+ installHint: 'npm install -g yaml-language-server',
53
+ },
54
+ {
55
+ id: 'docker',
56
+ name: 'Docker',
57
+ extensions: ['Dockerfile', '.dockerfile'],
58
+ command: 'docker-langserver',
59
+ args: ['--stdio'],
60
+ installHint: 'npm install -g dockerfile-language-server-nodejs',
61
+ },
62
+ ];
63
+ /**
64
+ * Find the language config for a given file path.
65
+ * Returns undefined if no matching language server is configured.
66
+ */
67
+ export function getLanguageForFile(filePath) {
68
+ const lowerPath = filePath.toLowerCase();
69
+ const baseName = filePath.split('/').pop() ?? '';
70
+ for (const config of LANGUAGE_CONFIGS) {
71
+ for (const ext of config.extensions) {
72
+ if (ext.startsWith('.')) {
73
+ if (lowerPath.endsWith(ext)) {
74
+ return config;
75
+ }
76
+ }
77
+ else {
78
+ // Match exact filename (e.g., Dockerfile)
79
+ if (baseName === ext || baseName.toLowerCase() === ext.toLowerCase()) {
80
+ return config;
81
+ }
82
+ }
83
+ }
84
+ }
85
+ return undefined;
86
+ }
87
+ /**
88
+ * Get the priority order of language configs.
89
+ * TypeScript > Go > Python > HCL > YAML > Docker
90
+ */
91
+ export function getLanguagePriority() {
92
+ return [...LANGUAGE_CONFIGS];
93
+ }
94
+ /** Language IDs relevant for DevOps workflows. */
95
+ export const DEVOPS_LANGUAGE_IDS = ['terraform', 'yaml', 'docker'];
@@ -0,0 +1,243 @@
1
+ /**
2
+ * LSP Manager -- Language Server Lifecycle
3
+ *
4
+ * Manages starting, stopping, and querying multiple language servers.
5
+ * Lazy loading: servers only start when a file of that type is first edited.
6
+ * Auto-stop: servers shut down after 5 minutes of inactivity.
7
+ */
8
+ import { readFile } from 'node:fs/promises';
9
+ import { exec } from 'node:child_process';
10
+ import { promisify } from 'node:util';
11
+ import { EventEmitter } from 'node:events';
12
+ import { LSPClient } from './client';
13
+ import { getLanguageForFile, LANGUAGE_CONFIGS } from './languages';
14
+ const execAsync = promisify(exec);
15
+ /** Default idle timeout: 5 minutes. */
16
+ const DEFAULT_IDLE_TIMEOUT = 5 * 60 * 1000;
17
+ /** Delay after file change before requesting diagnostics. */
18
+ const DIAGNOSTIC_DELAY = 500;
19
+ export class LSPManager extends EventEmitter {
20
+ clients = new Map();
21
+ idleTimers = new Map();
22
+ rootUri;
23
+ availabilityCache = new Map();
24
+ fileVersions = new Map();
25
+ enabled = true;
26
+ enabledLanguages;
27
+ /** C4: Track languages already reported as unavailable to avoid duplicate events. */
28
+ failedLSPs = new Set();
29
+ constructor(rootUri, options) {
30
+ super();
31
+ this.rootUri = rootUri ?? process.cwd();
32
+ this.enabledLanguages = options?.enabledLanguages;
33
+ }
34
+ /** Enable or disable LSP integration. */
35
+ setEnabled(enabled) {
36
+ this.enabled = enabled;
37
+ if (!enabled) {
38
+ this.stopAll();
39
+ }
40
+ }
41
+ /**
42
+ * Notify the LSP manager that a file was modified.
43
+ * This lazily starts the appropriate language server and sends the update.
44
+ */
45
+ async touchFile(filePath) {
46
+ if (!this.enabled) {
47
+ return;
48
+ }
49
+ const config = getLanguageForFile(filePath);
50
+ if (!config) {
51
+ return;
52
+ }
53
+ // Skip non-enabled language servers if a filter is set
54
+ if (this.enabledLanguages && !this.enabledLanguages.includes(config.id)) {
55
+ return;
56
+ }
57
+ // Ensure the client is running
58
+ const client = await this.ensureClient(config);
59
+ if (!client) {
60
+ return;
61
+ }
62
+ // Read file content and bump version
63
+ let content;
64
+ try {
65
+ content = await readFile(filePath, 'utf-8');
66
+ }
67
+ catch {
68
+ return;
69
+ }
70
+ const version = (this.fileVersions.get(filePath) ?? 0) + 1;
71
+ this.fileVersions.set(filePath, version);
72
+ await client.touchFile(filePath, content, version);
73
+ this.resetIdleTimer(config.id);
74
+ }
75
+ /**
76
+ * Get diagnostics for a file.
77
+ * Waits up to `delayMs` for the LSP to process and return results.
78
+ */
79
+ async getDiagnostics(filePath, delayMs = DIAGNOSTIC_DELAY) {
80
+ if (!this.enabled) {
81
+ return [];
82
+ }
83
+ const config = getLanguageForFile(filePath);
84
+ if (!config) {
85
+ return [];
86
+ }
87
+ const client = this.clients.get(config.id);
88
+ if (!client?.isInitialized) {
89
+ return [];
90
+ }
91
+ // Wait a brief moment for the LSP to process
92
+ await new Promise(resolve => setTimeout(resolve, delayMs));
93
+ return client.getDiagnostics(filePath);
94
+ }
95
+ /**
96
+ * Get errors only (severity 1 = Error).
97
+ */
98
+ async getErrors(filePath) {
99
+ const diagnostics = await this.getDiagnostics(filePath);
100
+ return diagnostics.filter(d => d.severity === 1);
101
+ }
102
+ /**
103
+ * Format diagnostics as messages suitable for injection into the agent conversation.
104
+ */
105
+ formatDiagnosticsForAgent(diagnostics) {
106
+ if (diagnostics.length === 0) {
107
+ return null;
108
+ }
109
+ const errors = diagnostics.filter(d => d.severity === 1);
110
+ const warnings = diagnostics.filter(d => d.severity === 2);
111
+ if (errors.length === 0 && warnings.length === 0) {
112
+ return null;
113
+ }
114
+ const lines = ['[LSP Diagnostics]'];
115
+ for (const d of errors) {
116
+ const loc = `${d.file}:${d.line}:${d.column}`;
117
+ lines.push(` Error: ${loc} — ${d.message}${d.source ? ` (${d.source})` : ''}`);
118
+ }
119
+ for (const d of warnings.slice(0, 5)) {
120
+ const loc = `${d.file}:${d.line}:${d.column}`;
121
+ lines.push(` Warning: ${loc} — ${d.message}${d.source ? ` (${d.source})` : ''}`);
122
+ }
123
+ if (warnings.length > 5) {
124
+ lines.push(` ... and ${warnings.length - 5} more warnings`);
125
+ }
126
+ return lines.join('\n');
127
+ }
128
+ /** Get status of all known language servers. */
129
+ async getStatus() {
130
+ const statuses = [];
131
+ for (const config of LANGUAGE_CONFIGS) {
132
+ const client = this.clients.get(config.id);
133
+ const available = await this.isAvailable(config);
134
+ statuses.push({
135
+ language: config.name,
136
+ active: client?.isInitialized ?? false,
137
+ available,
138
+ });
139
+ }
140
+ return statuses;
141
+ }
142
+ /** Stop all running language servers. */
143
+ async stopAll() {
144
+ for (const [id, client] of this.clients) {
145
+ await client.stop();
146
+ const timer = this.idleTimers.get(id);
147
+ if (timer) {
148
+ clearTimeout(timer);
149
+ }
150
+ }
151
+ this.clients.clear();
152
+ this.idleTimers.clear();
153
+ }
154
+ /** Stop a specific language server. */
155
+ async stop(languageId) {
156
+ const client = this.clients.get(languageId);
157
+ if (client) {
158
+ await client.stop();
159
+ this.clients.delete(languageId);
160
+ }
161
+ const timer = this.idleTimers.get(languageId);
162
+ if (timer) {
163
+ clearTimeout(timer);
164
+ this.idleTimers.delete(languageId);
165
+ }
166
+ }
167
+ /** Ensure a client exists and is initialized for the given language. */
168
+ async ensureClient(config) {
169
+ const existing = this.clients.get(config.id);
170
+ if (existing?.isInitialized) {
171
+ return existing;
172
+ }
173
+ // Check if the binary is available
174
+ const available = await this.isAvailable(config);
175
+ if (!available) {
176
+ // C4: Emit once per language so the TUI can surface a diagnostic message
177
+ if (!this.failedLSPs.has(config.id)) {
178
+ this.failedLSPs.add(config.id);
179
+ this.emit('lsp-unavailable', config.name ?? config.id, config.command);
180
+ }
181
+ return null;
182
+ }
183
+ const client = new LSPClient(config, this.rootUri);
184
+ const started = await client.start();
185
+ if (!started) {
186
+ return null;
187
+ }
188
+ this.clients.set(config.id, client);
189
+ this.resetIdleTimer(config.id);
190
+ client.on('exit', () => {
191
+ this.clients.delete(config.id);
192
+ const timer = this.idleTimers.get(config.id);
193
+ if (timer) {
194
+ clearTimeout(timer);
195
+ this.idleTimers.delete(config.id);
196
+ }
197
+ });
198
+ return client;
199
+ }
200
+ /** Check if a language server binary is available in PATH. */
201
+ async isAvailable(config) {
202
+ const cached = this.availabilityCache.get(config.id);
203
+ if (cached !== undefined) {
204
+ return cached;
205
+ }
206
+ try {
207
+ await execAsync(`which ${config.command}`);
208
+ this.availabilityCache.set(config.id, true);
209
+ return true;
210
+ }
211
+ catch {
212
+ this.availabilityCache.set(config.id, false);
213
+ return false;
214
+ }
215
+ }
216
+ /** Reset the idle timer for a language server. */
217
+ resetIdleTimer(languageId) {
218
+ const existing = this.idleTimers.get(languageId);
219
+ if (existing) {
220
+ clearTimeout(existing);
221
+ }
222
+ const config = LANGUAGE_CONFIGS.find(c => c.id === languageId);
223
+ const timeout = config?.idleTimeout ?? DEFAULT_IDLE_TIMEOUT;
224
+ const timer = setTimeout(() => {
225
+ this.stop(languageId);
226
+ }, timeout);
227
+ this.idleTimers.set(languageId, timer);
228
+ }
229
+ }
230
+ /** Singleton LSP manager instance. */
231
+ let lspManagerInstance = null;
232
+ /** Get or create the singleton LSP manager. */
233
+ export function getLSPManager(rootUri, options) {
234
+ if (!lspManagerInstance) {
235
+ lspManagerInstance = new LSPManager(rootUri, options);
236
+ }
237
+ return lspManagerInstance;
238
+ }
239
+ /** Reset the singleton (for testing). */
240
+ export function resetLSPManager() {
241
+ lspManagerInstance?.stopAll();
242
+ lspManagerInstance = null;
243
+ }