@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,60 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text, useInput } from 'ink';
3
+ /** Map change action to a prefix character and colour. */
4
+ const ACTION_DISPLAY = {
5
+ create: { prefix: '+', color: 'green' },
6
+ modify: { prefix: '~', color: 'yellow' },
7
+ destroy: { prefix: '-', color: 'red' },
8
+ replace: { prefix: '-/+', color: 'magenta' },
9
+ };
10
+ /**
11
+ * Compute summary counts for the banner line.
12
+ */
13
+ function summaryCounts(changes) {
14
+ let add = 0;
15
+ let change = 0;
16
+ let destroy = 0;
17
+ for (const c of changes) {
18
+ switch (c.action) {
19
+ case 'create':
20
+ add++;
21
+ break;
22
+ case 'modify':
23
+ case 'replace':
24
+ change++;
25
+ break;
26
+ case 'destroy':
27
+ destroy++;
28
+ break;
29
+ }
30
+ }
31
+ return { add, change, destroy };
32
+ }
33
+ /**
34
+ * A single row in the change table.
35
+ */
36
+ function ChangeRow({ change }) {
37
+ const display = ACTION_DISPLAY[change.action];
38
+ return (_jsxs(Box, { children: [_jsx(Text, { color: display.color, bold: true, children: display.prefix.padEnd(4) }), _jsx(Text, { children: change.resourceType }), _jsx(Text, { dimColor: true, children: "." }), _jsx(Text, { bold: true, children: change.resourceName }), change.details && _jsxs(Text, { dimColor: true, children: [" (", change.details, ")"] })] }));
39
+ }
40
+ /**
41
+ * DeployPreview renders the full preview modal with a change table and
42
+ * action key legend.
43
+ */
44
+ export function DeployPreview({ preview, onDecide }) {
45
+ useInput(input => {
46
+ switch (input) {
47
+ case 'a':
48
+ onDecide('approve');
49
+ break;
50
+ case 'r':
51
+ onDecide('reject');
52
+ break;
53
+ case 'p':
54
+ onDecide('show_plan');
55
+ break;
56
+ }
57
+ });
58
+ const counts = summaryCounts(preview.changes);
59
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: "Tip: Run /plan to review changes in detail before applying." }), _jsxs(Box, { flexDirection: "column", borderStyle: "double", borderColor: "yellow", paddingX: 1, paddingY: 1, children: [_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { bold: true, color: "yellow", children: "Deploy Preview" }), _jsxs(Text, { dimColor: true, children: [" (", preview.tool, ")"] })] }), _jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { color: "green", children: ["+", counts.add, " to add"] }), _jsx(Text, { children: " " }), _jsxs(Text, { color: "yellow", children: ["~", counts.change, " to change"] }), _jsx(Text, { children: " " }), _jsxs(Text, { color: "red", children: ["-", counts.destroy, " to destroy"] })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [preview.changes.map((change, idx) => (_jsx(ChangeRow, { change: change }, idx))), preview.changes.length === 0 && _jsx(Text, { dimColor: true, children: "No resource changes detected." })] }), preview.costImpact && (_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "Cost impact: " }), _jsx(Text, { children: preview.costImpact })] })), preview.blastRadius && (_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "Blast radius: " }), _jsx(Text, { color: "yellow", children: preview.blastRadius })] })), preview.affectedServices && preview.affectedServices.length > 0 && (_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "Affected services: " }), _jsx(Text, { children: preview.affectedServices.join(', ') })] })), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: "green", bold: true, children: "[a]" }), _jsx(Text, { children: " Approve " }), _jsx(Text, { color: "red", bold: true, children: "[r]" }), _jsx(Text, { children: " Reject " }), _jsx(Text, { color: "cyan", bold: true, children: "[p]" }), _jsx(Text, { children: " Show full plan" })] })] })] }));
60
+ }
@@ -0,0 +1,108 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * FileDiffModal Component
4
+ *
5
+ * Shows a proposed file change as a unified diff and waits for the user
6
+ * to approve or reject before the agent applies the change.
7
+ */
8
+ import { useState } from 'react';
9
+ import { Box, Text, useInput } from 'ink';
10
+ /**
11
+ * G9: Process a FileDiffBatch sequentially, presenting each diff via the
12
+ * provided `showDiff` callback (which renders a `FileDiffRequest`).
13
+ *
14
+ * The caller supplies `showDiff` which wires into App state. Each file is
15
+ * presented in order; 'apply-all' or 'reject-all' short-circuits the rest.
16
+ */
17
+ export function queueFileDiffBatch(batch, showDiff) {
18
+ const decisions = [];
19
+ function processNext(index) {
20
+ if (index >= batch.files.length) {
21
+ batch.onComplete(decisions);
22
+ return;
23
+ }
24
+ const file = batch.files[index];
25
+ showDiff({
26
+ toolName: file.toolName,
27
+ filePath: file.filePath,
28
+ diff: file.diff,
29
+ currentIndex: index + 1,
30
+ totalCount: batch.files.length,
31
+ onDecide: (decision) => {
32
+ decisions.push(decision);
33
+ if (decision === 'apply-all') {
34
+ // Fill remaining with 'apply'
35
+ for (let i = index + 1; i < batch.files.length; i++) {
36
+ decisions.push('apply');
37
+ }
38
+ batch.onComplete(decisions);
39
+ }
40
+ else if (decision === 'reject-all') {
41
+ // Fill remaining with 'reject'
42
+ for (let i = index + 1; i < batch.files.length; i++) {
43
+ decisions.push('reject');
44
+ }
45
+ batch.onComplete(decisions);
46
+ }
47
+ else {
48
+ processNext(index + 1);
49
+ }
50
+ },
51
+ });
52
+ }
53
+ processNext(0);
54
+ }
55
+ const VISIBLE_LINES = 30;
56
+ /**
57
+ * Modal overlay that displays a proposed file diff and collects user approval.
58
+ *
59
+ * Keyboard bindings:
60
+ * a — Apply this change
61
+ * r — Reject this change
62
+ * A — Apply this and all remaining changes (skip future prompts)
63
+ * R — Reject this and all remaining changes
64
+ * ↑/↓ — Scroll up/down one line
65
+ * PgUp/PgDn — Scroll up/down one page
66
+ */
67
+ export function FileDiffModal({ request }) {
68
+ const [scrollOffset, setScrollOffset] = useState(0);
69
+ const diffLines = request.diff.split('\n');
70
+ const total = diffLines.length;
71
+ const maxOffset = Math.max(0, total - VISIBLE_LINES);
72
+ useInput((input, key) => {
73
+ if (input === 'a')
74
+ request.onDecide('apply');
75
+ if (input === 'r')
76
+ request.onDecide('reject');
77
+ if (input === 'A')
78
+ request.onDecide('apply-all');
79
+ if (input === 'R')
80
+ request.onDecide('reject-all');
81
+ if (key.upArrow)
82
+ setScrollOffset(o => Math.max(0, o - 1));
83
+ if (key.downArrow)
84
+ setScrollOffset(o => Math.min(maxOffset, o + 1));
85
+ if (key.pageUp)
86
+ setScrollOffset(o => Math.max(0, o - VISIBLE_LINES));
87
+ if (key.pageDown)
88
+ setScrollOffset(o => Math.min(maxOffset, o + VISIBLE_LINES));
89
+ });
90
+ const endLine = Math.min(scrollOffset + VISIBLE_LINES, total);
91
+ const displayLines = diffLines.slice(scrollOffset, endLine);
92
+ const progress = request.totalCount && request.currentIndex
93
+ ? ` (${request.currentIndex}/${request.totalCount})`
94
+ : '';
95
+ const scrollIndicator = total > VISIBLE_LINES
96
+ ? ` [${scrollOffset + 1}–${endLine} of ${total} lines]`
97
+ : '';
98
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [_jsxs(Text, { bold: true, color: "yellow", children: [request.toolName, progress, ": ", request.filePath, scrollIndicator] }), _jsx(Box, { flexDirection: "column", marginY: 1, children: displayLines.map((line, i) => {
99
+ let color;
100
+ if (line.startsWith('+') && !line.startsWith('+++'))
101
+ color = 'green';
102
+ else if (line.startsWith('-') && !line.startsWith('---'))
103
+ color = 'red';
104
+ else if (line.startsWith('@@'))
105
+ color = 'cyan';
106
+ return (_jsx(Text, { color: color, dimColor: !color, children: line }, i));
107
+ }) }), _jsx(Text, { dimColor: true, children: " [a] Apply [r] Reject [A] Apply all [R] Reject all [\u2191/\u2193] Scroll [PgUp/PgDn] Page" })] }));
108
+ }
@@ -0,0 +1,46 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { VERSION } from '../version';
4
+ /** Map each mode to its display colour. */
5
+ const MODE_COLORS = {
6
+ plan: 'blue',
7
+ build: 'yellow',
8
+ deploy: 'red',
9
+ };
10
+ /** Prominent mode labels for visual distinction (Gap 11). */
11
+ const MODE_LABELS = {
12
+ plan: '[P] PLAN',
13
+ build: '[B] BUILD',
14
+ deploy: '[D] DEPLOY',
15
+ };
16
+ /**
17
+ * Truncate a session ID to a short prefix for display purposes.
18
+ */
19
+ function shortId(id) {
20
+ return id.length > 8 ? id.slice(0, 8) : id;
21
+ }
22
+ /**
23
+ * Determine if an environment name is production-like.
24
+ */
25
+ function isProdEnvironment(name) {
26
+ return /prod|production|live/i.test(name);
27
+ }
28
+ /**
29
+ * Header renders a single-line banner containing the CLI version, the model
30
+ * name, the abbreviated session ID, and a colour-coded mode badge.
31
+ */
32
+ export function Header({ session }) {
33
+ const modeColor = MODE_COLORS[session.mode];
34
+ const modeLabel = MODE_LABELS[session.mode];
35
+ // Gap 11: deploy mode gets a red border on the entire header for visual urgency
36
+ const borderColor = session.mode === 'deploy' ? 'red' : 'cyan';
37
+ const tfColor = session.terraformWorkspace && isProdEnvironment(session.terraformWorkspace)
38
+ ? 'yellow'
39
+ : 'green';
40
+ const k8sColor = session.kubectlContext && isProdEnvironment(session.kubectlContext)
41
+ ? 'yellow'
42
+ : 'green';
43
+ const isProd = isProdEnvironment(session.terraformWorkspace ?? '') || isProdEnvironment(session.kubectlContext ?? '');
44
+ const showProdWarning = session.mode === 'deploy' && isProd;
45
+ return (_jsxs(Box, { flexDirection: "column", width: "100%", children: [_jsxs(Box, { borderStyle: "round", borderColor: borderColor, paddingX: 1, justifyContent: "space-between", width: "100%", children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, color: "cyan", children: "nimbus" }), _jsxs(Text, { dimColor: true, children: [" v", VERSION] }), _jsxs(Text, { dimColor: true, children: [" ", ' -- '] }), _jsx(Text, { children: session.model }), _jsxs(Text, { dimColor: true, children: [" ", ' -- '] }), _jsxs(Text, { dimColor: true, children: ["session: ", shortId(session.id)] })] }), _jsxs(Box, { children: [session.llmHealth === 'checking' && _jsx(Text, { color: "yellow", dimColor: true, children: " [~] " }), session.llmHealth === 'ok' && _jsx(Text, { color: "green", children: " [+] " }), session.llmHealth === 'error' && _jsx(Text, { color: "red", children: " [-] no LLM" }), session.terraformWorkspace && (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsxs(Text, { color: tfColor, bold: true, children: ["tf:", session.terraformWorkspace] }), isProdEnvironment(session.terraformWorkspace) && (_jsx(Text, { color: "red", bold: true, children: " [PROD]" }))] })), session.kubectlContext && (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsxs(Text, { color: k8sColor, bold: true, children: ["k8s:", session.kubectlContext] }), isProdEnvironment(session.kubectlContext) && (_jsx(Text, { color: "red", bold: true, children: " [PROD]" }))] })), (session.terraformWorkspace || session.kubectlContext) && (_jsx(Text, { dimColor: true, children: " [/k8s-ctx | /tf-ws to switch]" }))] }), _jsx(Box, { children: _jsxs(Text, { color: modeColor, bold: true, inverse: true, children: [' ', modeLabel, ' '] }) })] }), showProdWarning && (_jsx(Box, { paddingX: 2, children: _jsx(Text, { color: "red", bold: true, children: "!! DEPLOY MODE \u2014 targeting production environment !!" }) }))] }));
46
+ }
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text, useInput } from 'ink';
3
+ export function HelpModal({ onClose }) {
4
+ useInput((input, key) => {
5
+ if (key.escape || input === 'q' || input === '?')
6
+ onClose();
7
+ });
8
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", padding: 1, marginY: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: " Nimbus DevOps Agent \u2014 Help " }), _jsx(Text, { children: " " }), _jsx(Text, { bold: true, children: "Slash Commands:" }), _jsx(Text, { children: " /help Show this help" }), _jsx(Text, { children: " /mode plan Switch to plan mode (read-only tools)" }), _jsx(Text, { children: " /mode build Switch to build mode (file + infra tools)" }), _jsx(Text, { children: " /mode deploy Switch to deploy mode (all tools + previews)" }), _jsx(Text, { children: " /clear Clear conversation history" }), _jsx(Text, { children: " /compact Compress context to free tokens" }), _jsx(Text, { children: " /context Show context window usage" }), _jsx(Text, { children: " /cost Show token usage and cost" }), _jsx(Text, { children: " /diff Show unstaged git diff" }), _jsx(Text, { children: " /init Regenerate NIMBUS.md project context" }), _jsx(Text, { children: " /model [name] Show or switch the active model" }), _jsx(Text, { children: " /models List all available provider models" }), _jsx(Text, { children: " /undo Undo last file change (snapshot)" }), _jsx(Text, { children: " /redo Redo last undone change" }), _jsx(Text, { children: " /sessions List active sessions" }), _jsx(Text, { children: " /new [name] Create a new session" }), _jsxs(Text, { children: [" /switch ", '<id>', " Switch to a different session"] }), _jsx(Text, { children: " " }), _jsx(Text, { bold: true, children: "DevOps Tools Available:" }), _jsx(Text, { children: " terraform, kubectl, helm, cloud_discover, cost_estimate," }), _jsx(Text, { children: " drift_detect, deploy_preview, git, task (subagent)" }), _jsx(Text, { children: " " }), _jsx(Text, { bold: true, children: "Keyboard Shortcuts:" }), _jsx(Text, { children: " ? Open this help panel" }), _jsx(Text, { children: " Tab Cycle mode (plan \u2192 build \u2192 deploy)" }), _jsx(Text, { children: " Ctrl+R Search input history" }), _jsx(Text, { children: " Ctrl+C Interrupt or exit" }), _jsx(Text, { children: " Esc / q / ? Close this help" }), _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: "Press Esc, q, or ? to close" })] }));
9
+ }
@@ -0,0 +1,408 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * InputBox Component
4
+ *
5
+ * A text input area with a "> " prompt character. Uses ink-text-input for
6
+ * editing and submits on Enter. The parent component receives the submitted
7
+ * text via the `onSubmit` callback.
8
+ *
9
+ * Features:
10
+ * - Input history (Up/Down arrows)
11
+ * - Multi-line paste detection with line count indicator
12
+ * - Slash command autocomplete (Tab to cycle)
13
+ * - @file mention with Tab completion (type @ then Tab to cycle files)
14
+ * - Reverse search (Ctrl+R) through history
15
+ */
16
+ import { useState, useCallback, useRef, useEffect } from 'react';
17
+ import { Box, Text, useInput } from 'ink';
18
+ import TextInput from 'ink-text-input';
19
+ import { spawnSync } from 'node:child_process';
20
+ import { writeFileSync, readFileSync, unlinkSync, existsSync, mkdirSync } from 'node:fs';
21
+ import { join } from 'node:path';
22
+ import { homedir } from 'node:os';
23
+ /** Maximum number of history entries to keep. */
24
+ const MAX_HISTORY = 100;
25
+ /** Path to the persistent input history file. */
26
+ const HISTORY_FILE = join(homedir(), '.nimbus', 'input-history.json');
27
+ /** Load persisted input history from disk (returns [] on any error). */
28
+ function loadHistory() {
29
+ try {
30
+ if (!existsSync(HISTORY_FILE))
31
+ return [];
32
+ const raw = readFileSync(HISTORY_FILE, 'utf-8');
33
+ const parsed = JSON.parse(raw);
34
+ if (Array.isArray(parsed))
35
+ return parsed.slice(-MAX_HISTORY);
36
+ }
37
+ catch { /* ignore */ }
38
+ return [];
39
+ }
40
+ /** Save input history to disk (fire-and-forget, ignores errors). */
41
+ function saveHistory(entries) {
42
+ try {
43
+ const dir = join(homedir(), '.nimbus');
44
+ if (!existsSync(dir))
45
+ mkdirSync(dir, { recursive: true });
46
+ writeFileSync(HISTORY_FILE, JSON.stringify(entries.slice(-MAX_HISTORY)), 'utf-8');
47
+ }
48
+ catch { /* non-critical */ }
49
+ }
50
+ /** All recognized slash commands for autocomplete. */
51
+ const SLASH_COMMANDS = [
52
+ '/alias',
53
+ '/apply',
54
+ '/branch',
55
+ '/auth-refresh',
56
+ '/clear',
57
+ '/compact',
58
+ '/context',
59
+ '/cost',
60
+ '/diff',
61
+ '/drift',
62
+ '/explain',
63
+ '/export',
64
+ '/help',
65
+ '/init',
66
+ '/k8s-ctx',
67
+ '/model',
68
+ '/models',
69
+ '/mode',
70
+ '/new',
71
+ '/plan',
72
+ '/profile',
73
+ '/redo',
74
+ '/rollback',
75
+ '/search',
76
+ '/sessions',
77
+ '/switch',
78
+ '/terminal',
79
+ '/tf-ws',
80
+ '/theme',
81
+ '/tools',
82
+ '/tree',
83
+ '/undo',
84
+ '/watch',
85
+ '/workspace',
86
+ ];
87
+ /** L1: Known subagent names for @agent completions. */
88
+ const AGENT_NAMES = ['explore', 'infra', 'security', 'cost', 'general'];
89
+ /**
90
+ * InputBox renders a ">" prompt followed by an editable text field.
91
+ * Pressing Enter submits the value and clears the field. Pressing Escape
92
+ * fires the optional onAbort callback. Up/Down arrows navigate history.
93
+ * Tab autocompletes slash commands. Ctrl+R opens reverse search.
94
+ */
95
+ export function InputBox({ onSubmit, onAbort, placeholder, disabled = false, mode = 'build', onLineCountChange, prefill, onFetchCompletions }) {
96
+ const [value, setValue] = useState(prefill ?? '');
97
+ // GAP-21: When prefill changes externally, update the input value
98
+ const prevPrefill = useRef(prefill);
99
+ if (prefill !== prevPrefill.current) {
100
+ prevPrefill.current = prefill;
101
+ if (prefill !== undefined) {
102
+ setValue(prefill);
103
+ }
104
+ }
105
+ // History: most recent entry is at the end (C1: loaded from disk)
106
+ const history = useRef(loadHistory());
107
+ // -1 means "not browsing history" (showing current draft)
108
+ const historyIndex = useRef(-1);
109
+ // Stores the in-progress text before the user started browsing history
110
+ const draft = useRef('');
111
+ // C1: Persist history to disk on unmount
112
+ useEffect(() => {
113
+ return () => {
114
+ saveHistory(history.current);
115
+ };
116
+ }, []);
117
+ // Slash command autocomplete state
118
+ const [slashHint, setSlashHint] = useState('');
119
+ const suggestionIndex = useRef(0);
120
+ const lastSuggestions = useRef([]);
121
+ // @file completion state
122
+ const [fileSuggestions, setFileSuggestions] = useState([]);
123
+ const [fileHint, setFileHint] = useState('');
124
+ const fileSuggestionIndex = useRef(0);
125
+ // H3: Dynamic completions for slash command arguments
126
+ const [dynamicSuggestions, setDynamicSuggestions] = useState([]);
127
+ const [dynamicHint, setDynamicHint] = useState('');
128
+ const dynamicSuggestionIndex = useRef(0);
129
+ // Cache: prefix → {items, ts}
130
+ const dynamicCache = useRef(new Map());
131
+ // Ctrl+R search mode
132
+ const [searchMode, setSearchMode] = useState(false);
133
+ const [searchQuery, setSearchQuery] = useState('');
134
+ const [searchResults, setSearchResults] = useState([]);
135
+ const handleSubmit = useCallback((submitted) => {
136
+ const trimmed = submitted.trim();
137
+ if (trimmed.length === 0) {
138
+ return;
139
+ }
140
+ onSubmit(trimmed);
141
+ // Add to history (avoid consecutive duplicates)
142
+ const h = history.current;
143
+ if (h.length === 0 || h[h.length - 1] !== trimmed) {
144
+ h.push(trimmed);
145
+ if (h.length > MAX_HISTORY) {
146
+ h.shift();
147
+ }
148
+ // C1: Persist to disk on every new entry
149
+ saveHistory(h);
150
+ }
151
+ // Reset history navigation
152
+ historyIndex.current = -1;
153
+ draft.current = '';
154
+ setValue('');
155
+ setSlashHint('');
156
+ }, [onSubmit]);
157
+ // Handle Escape, Up/Down arrows, Tab autocomplete, Ctrl+R
158
+ useInput((input, key) => {
159
+ // --- Ctrl+E: open $EDITOR for multi-line input composition (Gap 3) ---
160
+ if (input === 'e' && key.ctrl) {
161
+ const editor = process.env.EDITOR ?? process.env.VISUAL ?? 'nano';
162
+ const tmpFile = `/tmp/nimbus-input-${Date.now()}.md`;
163
+ try {
164
+ writeFileSync(tmpFile, value, 'utf-8');
165
+ spawnSync(editor, [tmpFile], { stdio: 'inherit' });
166
+ const edited = readFileSync(tmpFile, 'utf-8');
167
+ try {
168
+ unlinkSync(tmpFile);
169
+ }
170
+ catch { /* ignore */ }
171
+ setValue(edited);
172
+ }
173
+ catch {
174
+ /* editor not available — ignore */
175
+ }
176
+ return;
177
+ }
178
+ // --- Ctrl+R: toggle search mode ---
179
+ if (input === 'r' && key.ctrl) {
180
+ if (!searchMode) {
181
+ setSearchMode(true);
182
+ setSearchQuery('');
183
+ setSearchResults([]);
184
+ }
185
+ else {
186
+ setSearchMode(false);
187
+ }
188
+ return;
189
+ }
190
+ // --- Search mode key handling ---
191
+ if (searchMode) {
192
+ if (key.escape) {
193
+ setSearchMode(false);
194
+ return;
195
+ }
196
+ if (key.return) {
197
+ // Select top result
198
+ if (searchResults.length > 0) {
199
+ setValue(searchResults[0]);
200
+ }
201
+ setSearchMode(false);
202
+ return;
203
+ }
204
+ // Let the search TextInput handle other keys
205
+ return;
206
+ }
207
+ if (key.escape && onAbort) {
208
+ onAbort();
209
+ return;
210
+ }
211
+ // --- Tab: autocomplete ---
212
+ if (key.tab) {
213
+ // @file completion
214
+ const atMatch = value.match(/@(\S*)$/);
215
+ if (atMatch && fileSuggestions.length > 0) {
216
+ const idx = fileSuggestionIndex.current % fileSuggestions.length;
217
+ const replacement = `${value.slice(0, value.length - atMatch[0].length)}@${fileSuggestions[idx]}`;
218
+ setValue(replacement);
219
+ fileSuggestionIndex.current = idx + 1;
220
+ setFileHint(`[${fileSuggestions.length} files, Tab to cycle]`);
221
+ return;
222
+ }
223
+ // Slash command completion
224
+ if (value.startsWith('/')) {
225
+ // H3: Dynamic argument completions for /k8s-ctx, /tf-ws, /model, /profile
226
+ const dynamicPrefixes = ['/k8s-ctx ', '/tf-ws ', '/model ', '/profile '];
227
+ const dynMatch = dynamicPrefixes.find(p => value.startsWith(p));
228
+ if (dynMatch && onFetchCompletions) {
229
+ const partial = value.slice(dynMatch.length);
230
+ // Cycle through already-fetched suggestions
231
+ if (dynamicSuggestions.length > 0) {
232
+ const filtered = partial ? dynamicSuggestions.filter(s => s.startsWith(partial)) : dynamicSuggestions;
233
+ if (filtered.length > 0) {
234
+ const idx = dynamicSuggestionIndex.current % filtered.length;
235
+ setValue(`${dynMatch}${filtered[idx]}`);
236
+ dynamicSuggestionIndex.current = idx + 1;
237
+ setDynamicHint(`[${filtered.length} options, Tab to cycle]`);
238
+ return;
239
+ }
240
+ }
241
+ // Fetch completions (non-blocking, cache 30s)
242
+ const cacheKey = dynMatch;
243
+ const cached = dynamicCache.current.get(cacheKey);
244
+ const CACHE_TTL = 30_000;
245
+ if (!cached || Date.now() - cached.ts > CACHE_TTL) {
246
+ onFetchCompletions(value).then(items => {
247
+ dynamicCache.current.set(cacheKey, { items, ts: Date.now() });
248
+ setDynamicSuggestions(items);
249
+ dynamicSuggestionIndex.current = 0;
250
+ if (items.length > 0) {
251
+ setValue(`${dynMatch}${items[0]}`);
252
+ setDynamicHint(`[${items.length} options, Tab to cycle]`);
253
+ }
254
+ }).catch(() => { });
255
+ }
256
+ else {
257
+ setDynamicSuggestions(cached.items);
258
+ dynamicSuggestionIndex.current = 0;
259
+ }
260
+ return;
261
+ }
262
+ const prefix = value.toLowerCase();
263
+ const matches = SLASH_COMMANDS.filter(cmd => cmd.startsWith(prefix));
264
+ if (matches.length === 0) {
265
+ setSlashHint('');
266
+ return;
267
+ }
268
+ if (matches.length === 1) {
269
+ setValue(`${matches[0]} `);
270
+ setSlashHint('');
271
+ lastSuggestions.current = [];
272
+ return;
273
+ }
274
+ // Multiple matches: cycle through them
275
+ if (lastSuggestions.current.length === matches.length &&
276
+ lastSuggestions.current.every((s, i) => s === matches[i])) {
277
+ suggestionIndex.current = (suggestionIndex.current + 1) % matches.length;
278
+ }
279
+ else {
280
+ lastSuggestions.current = matches;
281
+ suggestionIndex.current = 0;
282
+ }
283
+ setValue(matches[suggestionIndex.current]);
284
+ setSlashHint(`[${matches.length} matches, Tab to cycle]`);
285
+ }
286
+ return;
287
+ }
288
+ const h = history.current;
289
+ if (h.length === 0) {
290
+ return;
291
+ }
292
+ if (key.upArrow) {
293
+ if (historyIndex.current === -1) {
294
+ // Starting to browse: save current draft
295
+ draft.current = value;
296
+ historyIndex.current = h.length - 1;
297
+ }
298
+ else if (historyIndex.current > 0) {
299
+ historyIndex.current--;
300
+ }
301
+ setValue(h[historyIndex.current]);
302
+ return;
303
+ }
304
+ if (key.downArrow) {
305
+ if (historyIndex.current === -1) {
306
+ return;
307
+ } // not browsing
308
+ if (historyIndex.current < h.length - 1) {
309
+ historyIndex.current++;
310
+ setValue(h[historyIndex.current]);
311
+ }
312
+ else {
313
+ // Past the end of history: restore draft
314
+ historyIndex.current = -1;
315
+ setValue(draft.current);
316
+ }
317
+ return;
318
+ }
319
+ }, { isActive: !disabled });
320
+ // Count lines for multi-line paste indicator
321
+ const lineCount = value.split('\n').length;
322
+ const isMultiLine = lineCount > 1;
323
+ if (disabled) {
324
+ return (_jsxs(Box, { paddingX: 1, children: [_jsx(Text, { dimColor: true, children: '> ' }), _jsx(Text, { dimColor: true, italic: true, children: placeholder ?? 'waiting...' })] }));
325
+ }
326
+ // --- Search mode UI ---
327
+ if (searchMode) {
328
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsxs(Box, { children: [_jsx(Text, { color: "yellow", children: '(reverse-search): ' }), _jsx(TextInput, { value: searchQuery, onChange: q => {
329
+ setSearchQuery(q);
330
+ if (q.length > 0) {
331
+ const results = history.current
332
+ .filter(entry => entry.toLowerCase().includes(q.toLowerCase()))
333
+ .reverse()
334
+ .slice(0, 10);
335
+ setSearchResults(results);
336
+ }
337
+ else {
338
+ setSearchResults([]);
339
+ }
340
+ }, onSubmit: () => {
341
+ if (searchResults.length > 0) {
342
+ setValue(searchResults[0]);
343
+ }
344
+ setSearchMode(false);
345
+ }, placeholder: "type to search history..." })] }), searchResults.length > 0 && (_jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [searchResults.slice(0, 5).map((result, i) => (_jsxs(Text, { dimColor: i > 0, children: [i === 0 ? '> ' : ' ', result.length > 80 ? `${result.slice(0, 77)}...` : result] }, i))), searchResults.length > 5 && (_jsxs(Text, { dimColor: true, italic: true, children: [' ', "... ", searchResults.length - 5, " more"] }))] }))] }));
346
+ }
347
+ // Gap 11: mode-specific prompt character and color
348
+ const promptLabel = mode === 'plan' ? '[plan]> ' : mode === 'deploy' ? '[DEPLOY]> ' : '[build]> ';
349
+ const promptColor = mode === 'plan' ? 'blue' : mode === 'deploy' ? 'red' : 'green';
350
+ // --- Normal input UI ---
351
+ return (_jsxs(Box, { paddingX: 1, children: [_jsx(Text, { bold: true, color: promptColor, children: promptLabel }), _jsx(TextInput, { value: value, onChange: v => {
352
+ // L2: Guard against large paste (>10KB) which would freeze the Ink UI
353
+ const safeV = v.length > 10_000 ? v.slice(0, 10_000) : v;
354
+ setValue(safeV);
355
+ onLineCountChange?.(safeV.split('\n').length);
356
+ // If user types while browsing history, exit history mode
357
+ if (historyIndex.current !== -1) {
358
+ historyIndex.current = -1;
359
+ }
360
+ // Reset slash autocomplete on any change
361
+ if (!v.startsWith('/')) {
362
+ setSlashHint('');
363
+ lastSuggestions.current = [];
364
+ }
365
+ // @file / @agent mention detection
366
+ const atMatch = v.match(/@(\S*)$/);
367
+ if (atMatch) {
368
+ const partial = atMatch[1];
369
+ // L1: Try agent names first
370
+ const agentMatches = AGENT_NAMES.filter(a => a.startsWith(partial.toLowerCase()));
371
+ if (agentMatches.length > 0 && partial.length > 0) {
372
+ setFileSuggestions(agentMatches.map(a => `@${a} `));
373
+ fileSuggestionIndex.current = 0;
374
+ setFileHint(`[${agentMatches.length} agent${agentMatches.length > 1 ? 's' : ''}, Tab to complete]`);
375
+ }
376
+ else {
377
+ try {
378
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
379
+ const fs = require('node:fs');
380
+ const cwd = process.cwd();
381
+ const entries = fs.readdirSync(cwd, { withFileTypes: true });
382
+ const matches = entries
383
+ .filter(e => !e.name.startsWith('.') && e.name.toLowerCase().includes(partial.toLowerCase()))
384
+ .map(e => (e.isDirectory() ? `${e.name}/` : e.name))
385
+ .slice(0, 10);
386
+ setFileSuggestions(matches);
387
+ fileSuggestionIndex.current = 0;
388
+ if (matches.length > 0) {
389
+ setFileHint(`[${matches.length} files, Tab to complete]`);
390
+ }
391
+ else {
392
+ setFileHint('');
393
+ }
394
+ }
395
+ catch {
396
+ setFileSuggestions([]);
397
+ setFileHint('');
398
+ }
399
+ }
400
+ }
401
+ else {
402
+ if (fileSuggestions.length > 0) {
403
+ setFileSuggestions([]);
404
+ setFileHint('');
405
+ }
406
+ }
407
+ }, onSubmit: handleSubmit, placeholder: placeholder ?? 'Type a DevOps command... (e.g. "terraform plan", "check pods", "list releases")' }), isMultiLine && _jsx(Text, { color: "cyan", children: ` [${lineCount} lines]` }), slashHint && _jsx(Text, { dimColor: true, children: ` ${slashHint}` }), fileHint && _jsx(Text, { dimColor: true, children: ` ${fileHint}` }), dynamicHint && _jsx(Text, { dimColor: true, children: ` ${dynamicHint}` })] }));
408
+ }