@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,35 @@
1
+ /**
2
+ * Resume Command
3
+ * Resume a Nimbus session by ID (or last session if omitted).
4
+ * Looks up sessions from SQLite, then launches chat with that session.
5
+ */
6
+ import { ui } from '../wizard/ui';
7
+ import { SessionManager } from '../sessions/manager';
8
+ export async function resumeCommand(taskIdOrOptions = {}) {
9
+ const taskId = typeof taskIdOrOptions === 'string' ? taskIdOrOptions : taskIdOrOptions.taskId;
10
+ ui.header('Resume Session');
11
+ const sessionManager = SessionManager.getInstance();
12
+ // If no taskId given, try to resume the most recent session
13
+ if (!taskId) {
14
+ const sessions = sessionManager.list();
15
+ if (sessions.length === 0) {
16
+ ui.error('No sessions found. Start a new session with "nimbus chat".');
17
+ process.exit(1);
18
+ }
19
+ const lastSession = sessions[0];
20
+ ui.info(`Resuming last session: ${lastSession.id.slice(0, 8)}`);
21
+ const { chatCommand } = await import('./chat');
22
+ await chatCommand({ continue: true });
23
+ return;
24
+ }
25
+ // Look up specific session by ID (or prefix)
26
+ const sessions = sessionManager.list();
27
+ const found = sessions.find((s) => s.id === taskId || s.id.startsWith(taskId));
28
+ if (!found) {
29
+ ui.error(`Session not found. Use "nimbus sessions" to list available sessions.`);
30
+ process.exit(1);
31
+ }
32
+ ui.info(`Resuming session: ${found.id.slice(0, 8)}`);
33
+ const { chatCommand } = await import('./chat');
34
+ await chatCommand({ continue: true });
35
+ }
@@ -0,0 +1,259 @@
1
+ /**
2
+ * nimbus rollback — Infrastructure Rollback Assistant
3
+ *
4
+ * Provides guided rollback for Helm releases and Kubernetes deployments.
5
+ * For Terraform, explains the rollback approach via state management.
6
+ *
7
+ * G20: New command added to the gap fix plan.
8
+ *
9
+ * Usage:
10
+ * nimbus rollback --helm <release>
11
+ * nimbus rollback --helm <release> --namespace <ns>
12
+ * nimbus rollback --k8s <deployment>
13
+ * nimbus rollback --k8s <deployment> --namespace <ns>
14
+ * nimbus rollback --tf
15
+ */
16
+ import { ui, confirm, select } from '../wizard';
17
+ /**
18
+ * Run the nimbus rollback command.
19
+ */
20
+ export async function rollbackCommand(options) {
21
+ const { execFileSync } = await import('node:child_process');
22
+ const run = (cmd, args) => {
23
+ return execFileSync(cmd, args, {
24
+ encoding: 'utf-8',
25
+ timeout: 30000,
26
+ stdio: ['pipe', 'pipe', 'pipe'],
27
+ });
28
+ };
29
+ // Helm rollback
30
+ if (options.helm) {
31
+ await rollbackHelm(options.helm, options.namespace, run);
32
+ return;
33
+ }
34
+ // Kubernetes deployment rollback
35
+ if (options.k8s) {
36
+ await rollbackK8s(options.k8s, options.namespace, run);
37
+ return;
38
+ }
39
+ // Terraform state rollback (--terraform flag)
40
+ if (options.terraform) {
41
+ await rollbackTerraformState(options.tfDir ?? process.cwd(), run);
42
+ return;
43
+ }
44
+ // Terraform guidance (--tf flag)
45
+ if (options.tf) {
46
+ rollbackTerraformGuidance();
47
+ return;
48
+ }
49
+ ui.error('Specify a rollback target: --helm <release>, --k8s <deployment>, --tf, or --terraform');
50
+ ui.print('Usage:');
51
+ ui.print(' nimbus rollback --helm <release> [--namespace <ns>]');
52
+ ui.print(' nimbus rollback --k8s <deployment> [--namespace <ns>]');
53
+ ui.print(' nimbus rollback --tf');
54
+ ui.print(' nimbus rollback --terraform [--tf-dir <path>]');
55
+ }
56
+ async function rollbackHelm(release, namespace, run) {
57
+ const nsArgs = namespace ? ['--namespace', namespace] : [];
58
+ ui.info(`Fetching history for Helm release: ${release}`);
59
+ let historyOutput;
60
+ try {
61
+ historyOutput = run('helm', ['history', release, '--output', 'table', ...nsArgs]);
62
+ }
63
+ catch (e) {
64
+ const msg = e instanceof Error ? e.message : String(e);
65
+ ui.error(`Failed to fetch Helm history: ${msg}`);
66
+ return;
67
+ }
68
+ ui.newLine();
69
+ ui.print(historyOutput);
70
+ ui.newLine();
71
+ // Parse revision numbers from history output
72
+ const revisionLines = historyOutput
73
+ .split('\n')
74
+ .slice(1) // skip header
75
+ .filter(line => /^\d+/.test(line.trim()));
76
+ if (revisionLines.length === 0) {
77
+ ui.warning('No revision history found.');
78
+ return;
79
+ }
80
+ const revisionOptions = revisionLines.map(line => {
81
+ const parts = line.trim().split(/\s+/);
82
+ const rev = parts[0];
83
+ const status = parts[2] ?? '';
84
+ const description = parts.slice(4).join(' ') ?? '';
85
+ return {
86
+ value: rev,
87
+ label: `Revision ${rev} — ${status} ${description}`.trim(),
88
+ };
89
+ });
90
+ const selectedRevision = await select({
91
+ message: `Roll back ${release} to which revision?`,
92
+ options: revisionOptions,
93
+ });
94
+ if (!selectedRevision) {
95
+ ui.info('Rollback cancelled.');
96
+ return;
97
+ }
98
+ const confirmed = await confirm({
99
+ message: `Roll back ${release} to revision ${selectedRevision}? This will update the release.`,
100
+ defaultValue: false,
101
+ });
102
+ if (!confirmed) {
103
+ ui.info('Rollback cancelled.');
104
+ return;
105
+ }
106
+ ui.startSpinner({ message: `Rolling back ${release} to revision ${selectedRevision}...` });
107
+ try {
108
+ run('helm', ['rollback', release, selectedRevision, ...nsArgs]);
109
+ ui.stopSpinnerSuccess(`${release} rolled back to revision ${selectedRevision}`);
110
+ }
111
+ catch (e) {
112
+ const msg = e instanceof Error ? e.message : String(e);
113
+ ui.stopSpinnerFail(`Rollback failed: ${msg}`);
114
+ }
115
+ }
116
+ async function rollbackK8s(deployment, namespace, run) {
117
+ const nsArgs = namespace ? ['-n', namespace] : [];
118
+ const deployTarget = `deployment/${deployment}`;
119
+ ui.info(`Fetching rollout history for ${deployTarget}`);
120
+ let historyOutput;
121
+ try {
122
+ historyOutput = run('kubectl', ['rollout', 'history', deployTarget, ...nsArgs]);
123
+ }
124
+ catch (e) {
125
+ const msg = e instanceof Error ? e.message : String(e);
126
+ ui.error(`Failed to fetch rollout history: ${msg}`);
127
+ return;
128
+ }
129
+ ui.newLine();
130
+ ui.print(historyOutput);
131
+ ui.newLine();
132
+ const confirmed = await confirm({
133
+ message: `Undo the last rollout for ${deployTarget}? This will revert to the previous revision.`,
134
+ defaultValue: false,
135
+ });
136
+ if (!confirmed) {
137
+ ui.info('Rollback cancelled.');
138
+ return;
139
+ }
140
+ ui.startSpinner({ message: `Rolling back ${deployTarget}...` });
141
+ try {
142
+ run('kubectl', ['rollout', 'undo', deployTarget, ...nsArgs]);
143
+ ui.stopSpinnerSuccess(`${deployTarget} rolled back`);
144
+ // Show status
145
+ const status = run('kubectl', ['rollout', 'status', deployTarget, ...nsArgs]);
146
+ ui.print(status);
147
+ }
148
+ catch (e) {
149
+ const msg = e instanceof Error ? e.message : String(e);
150
+ ui.stopSpinnerFail(`Rollback failed: ${msg}`);
151
+ }
152
+ }
153
+ async function rollbackTerraformState(dir, run) {
154
+ const { existsSync, readdirSync } = await import('node:fs');
155
+ // Detect if terraform directory exists (look for *.tf files)
156
+ let hasTfFiles = false;
157
+ try {
158
+ hasTfFiles = existsSync(dir) && readdirSync(dir).some(f => f.endsWith('.tf'));
159
+ }
160
+ catch {
161
+ /* ignore */
162
+ }
163
+ if (!hasTfFiles) {
164
+ ui.warning(`No *.tf files found in ${dir}. Is this a Terraform directory?`);
165
+ ui.dim('Use --tf-dir <path> to specify a different directory.');
166
+ return;
167
+ }
168
+ // Show current workspace
169
+ let currentWorkspace = 'default';
170
+ try {
171
+ currentWorkspace = run('terraform', ['workspace', 'show']).trim();
172
+ ui.info(`Current Terraform workspace: ${currentWorkspace}`);
173
+ }
174
+ catch {
175
+ ui.dim('Could not determine current workspace (terraform not in PATH?)');
176
+ }
177
+ ui.newLine();
178
+ // Show state list
179
+ let stateList = '';
180
+ try {
181
+ stateList = run('terraform', ['state', 'list']).trim();
182
+ }
183
+ catch (e) {
184
+ const msg = e instanceof Error ? e.message : String(e);
185
+ ui.warning(`Could not list terraform state: ${msg}`);
186
+ }
187
+ if (stateList) {
188
+ ui.info('Current state resources:');
189
+ for (const line of stateList.split('\n').slice(0, 20)) {
190
+ ui.print(` ${line}`);
191
+ }
192
+ const total = stateList.split('\n').length;
193
+ if (total > 20)
194
+ ui.dim(` ... and ${total - 20} more`);
195
+ ui.newLine();
196
+ }
197
+ // Rollback guidance specific to state
198
+ ui.box({
199
+ title: `Terraform State Rollback — workspace: ${currentWorkspace}`,
200
+ content: [
201
+ '',
202
+ 'To roll back Terraform state in workspace "' + currentWorkspace + '":',
203
+ '',
204
+ '1. Pull current state:',
205
+ ' terraform state pull > state-backup.tfstate',
206
+ '',
207
+ '2. Push a previous state backup:',
208
+ ' terraform state push <backup>.tfstate',
209
+ '',
210
+ '3. Or revert IaC code and re-apply:',
211
+ ' git checkout <commit> -- *.tf',
212
+ ' terraform plan && terraform apply',
213
+ '',
214
+ '4. For targeted resource rollback:',
215
+ ' terraform apply -target=<resource>',
216
+ '',
217
+ 'Run `nimbus chat` for AI-assisted rollback guidance.',
218
+ '',
219
+ ],
220
+ style: 'rounded',
221
+ borderColor: 'yellow',
222
+ padding: 0,
223
+ });
224
+ ui.newLine();
225
+ }
226
+ function rollbackTerraformGuidance() {
227
+ ui.newLine();
228
+ ui.box({
229
+ title: 'Terraform Rollback Guidance',
230
+ content: [
231
+ '',
232
+ 'Terraform does not have a built-in rollback command.',
233
+ 'To restore previous infrastructure state, use one of these approaches:',
234
+ '',
235
+ '1. Revert IaC code to a previous git commit:',
236
+ ' git checkout <commit> -- *.tf',
237
+ ' terraform plan && terraform apply',
238
+ '',
239
+ '2. Restore state from a backup:',
240
+ ' terraform state pull > current.tfstate',
241
+ ' terraform state push <backup>.tfstate',
242
+ '',
243
+ '3. Use workspace-specific state:',
244
+ ' terraform workspace select <env>',
245
+ ' terraform state list',
246
+ '',
247
+ '4. Target a specific resource for rollback:',
248
+ ' terraform apply -target=<resource>',
249
+ '',
250
+ 'Run `nimbus chat` to get AI-assisted rollback guidance.',
251
+ '',
252
+ ],
253
+ style: 'rounded',
254
+ borderColor: 'yellow',
255
+ padding: 0,
256
+ });
257
+ ui.newLine();
258
+ }
259
+ export default rollbackCommand;
@@ -0,0 +1,74 @@
1
+ /**
2
+ * nimbus rollout — Watch Kubernetes Deployment Rollouts
3
+ *
4
+ * Streams real-time rollout status for a Kubernetes deployment.
5
+ * Wraps `kubectl rollout status deployment/<name> --watch`.
6
+ *
7
+ * L1: New command for DevOps parity.
8
+ *
9
+ * Usage:
10
+ * nimbus rollout <deployment>
11
+ * nimbus rollout <deployment> --namespace <ns>
12
+ * nimbus rollout <deployment> --timeout 10m
13
+ */
14
+ import { spawnExec } from '../tools/spawn-exec';
15
+ /**
16
+ * Run the nimbus rollout command.
17
+ * Streams kubectl rollout status with live output.
18
+ */
19
+ export async function rolloutCommand(options) {
20
+ const { deployment, namespace, timeout = '5m' } = options;
21
+ const nsFlag = namespace ? `-n ${namespace}` : '';
22
+ const timeoutFlag = `--timeout=${timeout}`;
23
+ const command = `kubectl rollout status deployment/${deployment} ${nsFlag} ${timeoutFlag} --watch`.trim().replace(/\s+/g, ' ');
24
+ console.log(`Watching rollout: deployment/${deployment}${namespace ? ` (namespace: ${namespace})` : ''}`);
25
+ console.log(`Timeout: ${timeout}\n`);
26
+ const ac = new AbortController();
27
+ // Allow Ctrl+C to gracefully abort the rollout watch
28
+ process.on('SIGINT', () => {
29
+ console.log('\nRollout watch interrupted.');
30
+ ac.abort();
31
+ });
32
+ try {
33
+ const result = await spawnExec(command, {
34
+ onChunk: (chunk) => {
35
+ process.stdout.write(chunk);
36
+ },
37
+ timeout: parseTimeoutToMs(timeout),
38
+ });
39
+ const combined = [result.stdout, result.stderr].filter(Boolean).join('\n');
40
+ if (result.exitCode !== 0) {
41
+ console.error(`\nRollout failed (exit code ${result.exitCode}):`);
42
+ if (combined)
43
+ console.error(combined);
44
+ process.exitCode = 1;
45
+ }
46
+ else {
47
+ console.log('\nRollout complete.');
48
+ }
49
+ }
50
+ catch (err) {
51
+ if (err.message?.includes('aborted')) {
52
+ // Already printed abort message
53
+ }
54
+ else {
55
+ console.error(`Rollout watch error: ${err instanceof Error ? err.message : String(err)}`);
56
+ process.exitCode = 1;
57
+ }
58
+ }
59
+ }
60
+ /**
61
+ * Parse a kubectl-style timeout string (e.g. "5m", "30s", "2h") to milliseconds.
62
+ */
63
+ export function parseTimeoutToMs(timeout) {
64
+ const match = timeout.match(/^(\d+)(s|m|h)$/);
65
+ if (!match)
66
+ return 300_000; // default 5 min
67
+ const value = parseInt(match[1]);
68
+ switch (match[2]) {
69
+ case 's': return value * 1000;
70
+ case 'm': return value * 60 * 1000;
71
+ case 'h': return value * 3600 * 1000;
72
+ default: return 300_000;
73
+ }
74
+ }
@@ -0,0 +1,307 @@
1
+ /**
2
+ * Runbook Command (G15)
3
+ *
4
+ * Load and execute operational runbooks as agent prompts.
5
+ *
6
+ * Runbook YAML format:
7
+ * name: rotate-certs
8
+ * description: Rotate TLS certs in prod namespace
9
+ * context: prod # profile to activate
10
+ * steps:
11
+ * - Check for expiring certs in all namespaces
12
+ * - Rotate each cert using cert-manager annotate
13
+ * - Verify new certs are valid and pods restarted
14
+ *
15
+ * Usage:
16
+ * nimbus runbook list
17
+ * nimbus runbook run <name> [--auto]
18
+ * nimbus runbook create <name>
19
+ */
20
+ import { existsSync, readdirSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
21
+ import { join, basename } from 'node:path';
22
+ import { homedir } from 'node:os';
23
+ // ---------------------------------------------------------------------------
24
+ // Helpers
25
+ // ---------------------------------------------------------------------------
26
+ /** Directories to scan for runbooks. */
27
+ const RUNBOOK_DIRS = [
28
+ join(homedir(), '.nimbus', 'runbooks'),
29
+ join(process.cwd(), 'runbooks'),
30
+ join(process.cwd(), 'docs', 'runbooks'),
31
+ join(process.cwd(), '.github', 'runbooks'),
32
+ ];
33
+ /**
34
+ * Find all runbook YAML files in the standard directories.
35
+ */
36
+ function findRunbooks() {
37
+ const results = [];
38
+ for (const dir of RUNBOOK_DIRS) {
39
+ if (!existsSync(dir))
40
+ continue;
41
+ try {
42
+ const files = readdirSync(dir).filter(f => /\.(yaml|yml)$/.test(f));
43
+ for (const file of files) {
44
+ results.push({ path: join(dir, file), dir, file });
45
+ }
46
+ }
47
+ catch { /* non-critical */ }
48
+ }
49
+ return results;
50
+ }
51
+ /**
52
+ * Minimal YAML parser for simple runbook format (no external dep).
53
+ * Supports both plain string steps and structured steps with if:/require_approval: fields.
54
+ *
55
+ * Structured step example:
56
+ * steps:
57
+ * - name: Check certs
58
+ * run: Check for expiring certs
59
+ * if: cert_count > 0
60
+ * require_approval: true
61
+ */
62
+ function parseRunbookYaml(content) {
63
+ const lines = content.split('\n');
64
+ const def = { name: '', steps: [] };
65
+ let inSteps = false;
66
+ // We parse in two modes: simple (step is a plain `- text` line) and
67
+ // structured (step is a YAML mapping started by `- name:` or `- run:`).
68
+ let currentStep = null;
69
+ const flushStep = () => {
70
+ if (currentStep) {
71
+ def.steps.push(currentStep);
72
+ currentStep = null;
73
+ }
74
+ };
75
+ for (const raw of lines) {
76
+ const line = raw.trimEnd();
77
+ if (line.startsWith('#') || !line.trim())
78
+ continue;
79
+ if (line.startsWith('name:') && !inSteps) {
80
+ def.name = line.slice(5).trim().replace(/^['"]|['"]$/g, '');
81
+ }
82
+ else if (line.startsWith('description:') && !inSteps) {
83
+ def.description = line.slice(12).trim().replace(/^['"]|['"]$/g, '');
84
+ }
85
+ else if (line.startsWith('context:') && !inSteps) {
86
+ def.context = line.slice(8).trim().replace(/^['"]|['"]$/g, '');
87
+ }
88
+ else if (line.trim() === 'steps:') {
89
+ inSteps = true;
90
+ }
91
+ else if (inSteps) {
92
+ // Top-level step item: starts with exactly two spaces + "- "
93
+ if (/^ {0,2}- /.test(line)) {
94
+ flushStep();
95
+ const stepText = line.replace(/^\s*-\s*/, '').trim();
96
+ // Detect structured step: starts with "name:", "run:", "action:"
97
+ if (/^(name|run|action):/.test(stepText)) {
98
+ const val = stepText.replace(/^(name|run|action):\s*/, '').replace(/^['"]|['"]$/g, '');
99
+ currentStep = { text: val };
100
+ }
101
+ else if (stepText === '') {
102
+ // blank mapping start — next indented lines fill it in
103
+ currentStep = { text: '' };
104
+ }
105
+ else {
106
+ // Plain string step
107
+ def.steps.push({ text: stepText.replace(/^['"]|['"]$/g, '') });
108
+ }
109
+ }
110
+ else if (currentStep && /^\s+(name|run|action):/.test(line)) {
111
+ // Structured field continuation inside a step block
112
+ const val = line.replace(/^\s+(name|run|action):\s*/, '').replace(/^['"]|['"]$/g, '');
113
+ if (!currentStep.text)
114
+ currentStep.text = val;
115
+ }
116
+ else if (currentStep && /^\s+if:/.test(line)) {
117
+ const val = line.replace(/^\s+if:\s*/, '').replace(/^['"]|['"]$/g, '');
118
+ currentStep.if = val;
119
+ }
120
+ else if (currentStep && /^\s+require_approval:/.test(line)) {
121
+ const val = line.replace(/^\s+require_approval:\s*/, '').trim();
122
+ currentStep.require_approval = val === 'true';
123
+ }
124
+ else if (!/^\s/.test(line)) {
125
+ // Non-indented non-step line — exit step parsing
126
+ flushStep();
127
+ inSteps = false;
128
+ }
129
+ }
130
+ }
131
+ flushStep();
132
+ return def;
133
+ }
134
+ /**
135
+ * Build a multi-step agent prompt from a runbook definition.
136
+ * Supports GAP-24 conditional steps (if:) and approval gates (require_approval:).
137
+ */
138
+ function buildRunbookPrompt(def) {
139
+ const parts = [
140
+ `# Runbook: ${def.name}`,
141
+ ];
142
+ if (def.description)
143
+ parts.push(`\n${def.description}`);
144
+ if (def.context)
145
+ parts.push(`\nContext/profile: ${def.context}`);
146
+ parts.push('\n## Steps to execute in order:');
147
+ def.steps.forEach((step, i) => {
148
+ // GAP-24: In step parsing loop
149
+ const ifCondition = step.if;
150
+ const requireApproval = step.require_approval;
151
+ let stepText = `Step ${i + 1}: ${step.text}`;
152
+ if (ifCondition) {
153
+ stepText += `\n [CONDITIONAL: Only proceed if: ${ifCondition}]`;
154
+ stepText += `\nAfter completing this step, check: ${ifCondition}. If the condition evaluates to false, stop and report the status without continuing to the next step.`;
155
+ }
156
+ if (requireApproval) {
157
+ stepText += `\n [REQUIRES APPROVAL: State this step's plan and wait for explicit user approval before executing]`;
158
+ stepText = `IMPORTANT: Before executing this step, explicitly state what you are about to do and wait for the user to say "approve" or "yes" before proceeding.\n` + stepText;
159
+ }
160
+ parts.push(stepText);
161
+ });
162
+ parts.push('\nExecute each step in sequence. Check for errors after each step before proceeding. Report progress clearly.');
163
+ return parts.join('\n');
164
+ }
165
+ // ---------------------------------------------------------------------------
166
+ // Subcommands
167
+ // ---------------------------------------------------------------------------
168
+ async function runbookList() {
169
+ const runbooks = findRunbooks();
170
+ if (runbooks.length === 0) {
171
+ console.log('No runbooks found.');
172
+ console.log('');
173
+ console.log('Create one at:');
174
+ for (const dir of RUNBOOK_DIRS.slice(0, 2)) {
175
+ console.log(` ${dir}/<name>.yaml`);
176
+ }
177
+ return;
178
+ }
179
+ console.log('Available runbooks:\n');
180
+ for (const rb of runbooks) {
181
+ try {
182
+ const content = readFileSync(rb.path, 'utf-8');
183
+ const def = parseRunbookYaml(content);
184
+ console.log(` ${def.name.padEnd(24)} ${def.description ?? ''}`);
185
+ console.log(` ${''.padEnd(24)} (${rb.path})`);
186
+ }
187
+ catch {
188
+ console.log(` ${basename(rb.path)} (parse error)`);
189
+ }
190
+ }
191
+ }
192
+ async function runbookRun(name, options) {
193
+ const runbooks = findRunbooks();
194
+ const match = runbooks.find(rb => {
195
+ try {
196
+ const def = parseRunbookYaml(readFileSync(rb.path, 'utf-8'));
197
+ return def.name === name || basename(rb.file, '.yaml') === name || basename(rb.file, '.yml') === name;
198
+ }
199
+ catch {
200
+ return false;
201
+ }
202
+ });
203
+ if (!match) {
204
+ console.error(`Runbook not found: ${name}`);
205
+ console.log('Run "nimbus runbook list" to see available runbooks.');
206
+ process.exit(1);
207
+ }
208
+ const def = parseRunbookYaml(readFileSync(match.path, 'utf-8'));
209
+ const prompt = buildRunbookPrompt(def);
210
+ console.log(`Executing runbook: ${def.name}`);
211
+ if (def.description)
212
+ console.log(`Description: ${def.description}`);
213
+ console.log(`Steps: ${def.steps.length}`);
214
+ console.log('');
215
+ if (!options.auto) {
216
+ // Prompt for confirmation in interactive mode
217
+ const readline = await import('node:readline');
218
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
219
+ const answer = await new Promise(resolve => {
220
+ rl.question('Proceed with runbook execution? (y/N) ', resolve);
221
+ });
222
+ rl.close();
223
+ if (!answer.toLowerCase().startsWith('y')) {
224
+ console.log('Runbook execution cancelled.');
225
+ return;
226
+ }
227
+ }
228
+ const { chatCommand } = await import('./chat');
229
+ await chatCommand({ initialPrompt: prompt, mode: 'deploy' });
230
+ }
231
+ async function runbookCreate(name) {
232
+ const targetDir = join(homedir(), '.nimbus', 'runbooks');
233
+ mkdirSync(targetDir, { recursive: true });
234
+ const targetPath = join(targetDir, `${name}.yaml`);
235
+ if (existsSync(targetPath)) {
236
+ console.error(`Runbook already exists: ${targetPath}`);
237
+ process.exit(1);
238
+ }
239
+ const readline = await import('node:readline');
240
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
241
+ const question = (q) => new Promise(resolve => rl.question(q, resolve));
242
+ console.log(`Creating runbook: ${name}`);
243
+ const description = await question('Description: ');
244
+ const context = await question('Context/profile (optional, e.g. "prod"): ');
245
+ console.log('Enter steps (empty line to finish):');
246
+ const steps = [];
247
+ let stepNum = 1;
248
+ while (true) {
249
+ const step = await question(`Step ${stepNum}: `);
250
+ if (!step.trim())
251
+ break;
252
+ steps.push(step.trim());
253
+ stepNum++;
254
+ }
255
+ rl.close();
256
+ if (steps.length === 0) {
257
+ console.error('Runbook must have at least one step.');
258
+ process.exit(1);
259
+ }
260
+ const yaml = [
261
+ `name: ${name}`,
262
+ `description: ${description}`,
263
+ context ? `context: ${context}` : '# context: prod',
264
+ 'steps:',
265
+ ...steps.map(s => ` - ${s}`),
266
+ ].join('\n') + '\n';
267
+ writeFileSync(targetPath, yaml, 'utf-8');
268
+ console.log(`\nRunbook saved to: ${targetPath}`);
269
+ console.log(`Run with: nimbus runbook run ${name}`);
270
+ }
271
+ // ---------------------------------------------------------------------------
272
+ // Main export
273
+ // ---------------------------------------------------------------------------
274
+ export async function runbookCommand(subcommand, args) {
275
+ switch (subcommand) {
276
+ case 'list':
277
+ case 'ls':
278
+ await runbookList();
279
+ break;
280
+ case 'run': {
281
+ const name = args[0];
282
+ if (!name) {
283
+ console.error('Usage: nimbus runbook run <name> [--auto]');
284
+ process.exit(1);
285
+ }
286
+ await runbookRun(name, { auto: args.includes('--auto') });
287
+ break;
288
+ }
289
+ case 'create':
290
+ case 'new': {
291
+ const name = args[0];
292
+ if (!name) {
293
+ console.error('Usage: nimbus runbook create <name>');
294
+ process.exit(1);
295
+ }
296
+ await runbookCreate(name);
297
+ break;
298
+ }
299
+ default:
300
+ console.log('Usage: nimbus runbook <list|run|create>');
301
+ console.log('');
302
+ console.log(' list List available runbooks');
303
+ console.log(' run <name> Execute a runbook as an agent prompt');
304
+ console.log(' create <name> Create a new runbook interactively');
305
+ break;
306
+ }
307
+ }