@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,1263 @@
1
+ /**
2
+ * Generate Kubernetes Manifests Command
3
+ *
4
+ * Interactive wizard for generating K8s resources
5
+ *
6
+ * Usage: nimbus generate k8s [options]
7
+ */
8
+ import { logger } from '../utils';
9
+ import { createWizard, ui, select, multiSelect, confirm, input, pathInput, } from '../wizard';
10
+ /**
11
+ * Run the generate k8s command
12
+ */
13
+ export async function generateK8sCommand(options = {}) {
14
+ logger.info('Starting Kubernetes manifest generation wizard');
15
+ // Non-interactive mode
16
+ if (options.nonInteractive) {
17
+ await runNonInteractive(options);
18
+ return;
19
+ }
20
+ // Interactive wizard mode
21
+ const wizard = createWizard({
22
+ title: 'nimbus generate k8s',
23
+ description: 'Generate Kubernetes manifests for your application',
24
+ initialContext: {
25
+ workloadType: options.workloadType,
26
+ namespace: options.namespace,
27
+ name: options.name,
28
+ image: options.image,
29
+ replicas: options.replicas,
30
+ containerPort: options.port,
31
+ serviceType: options.serviceType,
32
+ outputPath: options.output,
33
+ includeIngress: options.includeIngress,
34
+ includeHpa: options.includeHpa,
35
+ includePdb: options.includePdb,
36
+ includeConfigMap: options.includeConfigMap,
37
+ includeSecret: options.includeSecret,
38
+ cpuRequest: options.cpuRequest,
39
+ cpuLimit: options.cpuLimit,
40
+ memoryRequest: options.memoryRequest,
41
+ memoryLimit: options.memoryLimit,
42
+ },
43
+ steps: createWizardSteps(),
44
+ onEvent: event => {
45
+ logger.debug('Wizard event', { type: event.type });
46
+ },
47
+ });
48
+ const result = await wizard.run();
49
+ if (result.success) {
50
+ ui.newLine();
51
+ ui.box({
52
+ title: 'Complete!',
53
+ content: [
54
+ 'Your Kubernetes manifests have been generated.',
55
+ '',
56
+ 'Generated files:',
57
+ ...(result.context.generatedFiles?.map(f => ` - ${f}`) || [' - (manifests generated)']),
58
+ '',
59
+ 'Next steps:',
60
+ ` 1. Review the generated files in ${result.context.outputPath}`,
61
+ ' 2. Customize values as needed for your environment',
62
+ ' 3. Run "kubectl apply -f <path>" or "nimbus apply k8s <path>"',
63
+ ],
64
+ style: 'rounded',
65
+ borderColor: 'green',
66
+ padding: 1,
67
+ });
68
+ }
69
+ else {
70
+ ui.error(`Wizard failed: ${result.error?.message || 'Unknown error'}`);
71
+ process.exit(1);
72
+ }
73
+ }
74
+ /**
75
+ * Create wizard steps
76
+ */
77
+ function createWizardSteps() {
78
+ return [
79
+ // Step 1: Workload Type Selection
80
+ {
81
+ id: 'workload-type',
82
+ title: 'Workload Type',
83
+ description: 'Select the type of Kubernetes workload to generate',
84
+ execute: workloadTypeStep,
85
+ },
86
+ // Step 2: Basic Configuration
87
+ {
88
+ id: 'basic-config',
89
+ title: 'Basic Configuration',
90
+ description: 'Configure name, namespace, and image',
91
+ execute: basicConfigStep,
92
+ },
93
+ // Step 3: Replica Configuration (not for DaemonSet or Job)
94
+ {
95
+ id: 'replicas',
96
+ title: 'Replica Configuration',
97
+ description: 'Configure replicas and scaling options',
98
+ condition: ctx => !['daemonset', 'job'].includes(ctx.workloadType || ''),
99
+ execute: replicaConfigStep,
100
+ },
101
+ // Step 4: Job/CronJob Configuration
102
+ {
103
+ id: 'job-config',
104
+ title: 'Job Configuration',
105
+ description: 'Configure job-specific settings',
106
+ condition: ctx => ['job', 'cronjob'].includes(ctx.workloadType || ''),
107
+ execute: jobConfigStep,
108
+ },
109
+ // Step 5: Port & Service Configuration
110
+ {
111
+ id: 'service-config',
112
+ title: 'Port & Service Configuration',
113
+ description: 'Configure container ports and service exposure',
114
+ execute: serviceConfigStep,
115
+ },
116
+ // Step 6: Resource Limits
117
+ {
118
+ id: 'resources',
119
+ title: 'Resource Limits',
120
+ description: 'Configure CPU and memory requests/limits',
121
+ execute: resourceLimitsStep,
122
+ },
123
+ // Step 7: Additional Resources
124
+ {
125
+ id: 'additional-resources',
126
+ title: 'Additional Resources',
127
+ description: 'Select additional Kubernetes resources to generate',
128
+ execute: additionalResourcesStep,
129
+ },
130
+ // Step 8: Health Checks
131
+ {
132
+ id: 'health-checks',
133
+ title: 'Health Checks',
134
+ description: 'Configure liveness and readiness probes',
135
+ condition: ctx => !['job', 'cronjob'].includes(ctx.workloadType || ''),
136
+ execute: healthChecksStep,
137
+ },
138
+ // Step 9: Output Configuration
139
+ {
140
+ id: 'output',
141
+ title: 'Output Configuration',
142
+ description: 'Configure where and how to save the manifests',
143
+ execute: outputConfigStep,
144
+ },
145
+ // Step 10: Generate
146
+ {
147
+ id: 'generate',
148
+ title: 'Generate Manifests',
149
+ description: 'Generating your Kubernetes manifests...',
150
+ execute: generateStep,
151
+ },
152
+ ];
153
+ }
154
+ /**
155
+ * Step 1: Workload Type Selection
156
+ */
157
+ async function workloadTypeStep(ctx) {
158
+ const workloadType = await select({
159
+ message: 'Select workload type:',
160
+ options: [
161
+ {
162
+ value: 'deployment',
163
+ label: 'Deployment',
164
+ description: 'Stateless application with rolling updates (most common)',
165
+ },
166
+ {
167
+ value: 'statefulset',
168
+ label: 'StatefulSet',
169
+ description: 'Stateful application with stable network identity and storage',
170
+ },
171
+ {
172
+ value: 'daemonset',
173
+ label: 'DaemonSet',
174
+ description: 'Run a pod on every node (e.g., monitoring agents)',
175
+ },
176
+ {
177
+ value: 'job',
178
+ label: 'Job',
179
+ description: 'Run a task to completion',
180
+ },
181
+ {
182
+ value: 'cronjob',
183
+ label: 'CronJob',
184
+ description: 'Run a job on a schedule',
185
+ },
186
+ ],
187
+ defaultValue: ctx.workloadType || 'deployment',
188
+ });
189
+ if (!workloadType) {
190
+ return { success: false, error: 'No workload type selected' };
191
+ }
192
+ return {
193
+ success: true,
194
+ data: { workloadType },
195
+ };
196
+ }
197
+ /**
198
+ * Step 2: Basic Configuration
199
+ */
200
+ async function basicConfigStep(ctx) {
201
+ // Application name
202
+ const name = await input({
203
+ message: 'Application name:',
204
+ defaultValue: ctx.name || 'my-app',
205
+ validate: value => {
206
+ if (!value) {
207
+ return 'Name is required';
208
+ }
209
+ if (!/^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/.test(value)) {
210
+ return 'Name must be lowercase alphanumeric with dashes only';
211
+ }
212
+ return true;
213
+ },
214
+ });
215
+ if (!name) {
216
+ return { success: false, error: 'Name is required' };
217
+ }
218
+ // Namespace
219
+ ui.newLine();
220
+ const namespace = await input({
221
+ message: 'Namespace:',
222
+ defaultValue: ctx.namespace || 'default',
223
+ validate: value => {
224
+ if (!value) {
225
+ return 'Namespace is required';
226
+ }
227
+ if (!/^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/.test(value)) {
228
+ return 'Namespace must be lowercase alphanumeric with dashes only';
229
+ }
230
+ return true;
231
+ },
232
+ });
233
+ if (!namespace) {
234
+ return { success: false, error: 'Namespace is required' };
235
+ }
236
+ // Container image
237
+ ui.newLine();
238
+ const image = await input({
239
+ message: 'Container image (e.g., nginx:latest, myregistry/myapp):',
240
+ defaultValue: ctx.image || '',
241
+ validate: value => {
242
+ if (!value) {
243
+ return 'Image is required';
244
+ }
245
+ return true;
246
+ },
247
+ });
248
+ if (!image) {
249
+ return { success: false, error: 'Image is required' };
250
+ }
251
+ // Parse image and tag
252
+ const imageParts = image.split(':');
253
+ const imageBase = imageParts[0];
254
+ const imageTag = imageParts[1] || 'latest';
255
+ return {
256
+ success: true,
257
+ data: {
258
+ name,
259
+ namespace,
260
+ image: imageBase,
261
+ imageTag,
262
+ },
263
+ };
264
+ }
265
+ /**
266
+ * Step 3: Replica Configuration
267
+ */
268
+ async function replicaConfigStep(ctx) {
269
+ const replicas = await input({
270
+ message: 'Number of replicas:',
271
+ defaultValue: String(ctx.replicas || 2),
272
+ validate: value => {
273
+ const num = parseInt(value, 10);
274
+ if (isNaN(num) || num < 1) {
275
+ return 'Must be a positive number';
276
+ }
277
+ return true;
278
+ },
279
+ });
280
+ if (!replicas) {
281
+ return { success: false, error: 'Replicas required' };
282
+ }
283
+ // Ask about HPA
284
+ ui.newLine();
285
+ const includeHpa = ctx.includeHpa ??
286
+ (await confirm({
287
+ message: 'Include Horizontal Pod Autoscaler (HPA)?',
288
+ defaultValue: false,
289
+ }));
290
+ let minReplicas;
291
+ let maxReplicas;
292
+ let targetCPUUtilization;
293
+ if (includeHpa) {
294
+ ui.newLine();
295
+ const min = await input({
296
+ message: 'Minimum replicas:',
297
+ defaultValue: String(ctx.minReplicas || Math.max(1, parseInt(replicas, 10) - 1)),
298
+ validate: value => {
299
+ const num = parseInt(value, 10);
300
+ if (isNaN(num) || num < 1) {
301
+ return 'Must be a positive number';
302
+ }
303
+ return true;
304
+ },
305
+ });
306
+ minReplicas = parseInt(min || '1', 10);
307
+ const max = await input({
308
+ message: 'Maximum replicas:',
309
+ defaultValue: String(ctx.maxReplicas || parseInt(replicas, 10) * 2),
310
+ validate: value => {
311
+ const num = parseInt(value, 10);
312
+ if (isNaN(num) || num < minReplicas) {
313
+ return `Must be >= ${minReplicas}`;
314
+ }
315
+ return true;
316
+ },
317
+ });
318
+ maxReplicas = parseInt(max || '4', 10);
319
+ const cpu = await input({
320
+ message: 'Target CPU utilization (%):',
321
+ defaultValue: String(ctx.targetCPUUtilization || 70),
322
+ validate: value => {
323
+ const num = parseInt(value, 10);
324
+ if (isNaN(num) || num < 1 || num > 100) {
325
+ return 'Must be between 1 and 100';
326
+ }
327
+ return true;
328
+ },
329
+ });
330
+ targetCPUUtilization = parseInt(cpu || '70', 10);
331
+ }
332
+ return {
333
+ success: true,
334
+ data: {
335
+ replicas: parseInt(replicas, 10),
336
+ includeHpa,
337
+ minReplicas,
338
+ maxReplicas,
339
+ targetCPUUtilization,
340
+ },
341
+ };
342
+ }
343
+ /**
344
+ * Step 4: Job/CronJob Configuration
345
+ */
346
+ async function jobConfigStep(ctx) {
347
+ let schedule;
348
+ if (ctx.workloadType === 'cronjob') {
349
+ const scheduleInput = await input({
350
+ message: 'Cron schedule (e.g., "*/5 * * * *" for every 5 minutes):',
351
+ defaultValue: ctx.schedule || '0 * * * *',
352
+ validate: value => {
353
+ if (!value) {
354
+ return 'Schedule is required for CronJob';
355
+ }
356
+ // Basic cron validation (5 fields)
357
+ const parts = value.trim().split(/\s+/);
358
+ if (parts.length !== 5) {
359
+ return 'Schedule must have 5 fields (min hour day month weekday)';
360
+ }
361
+ return true;
362
+ },
363
+ });
364
+ schedule = scheduleInput;
365
+ }
366
+ // Backoff limit
367
+ ui.newLine();
368
+ const backoffInput = await input({
369
+ message: 'Backoff limit (retries on failure):',
370
+ defaultValue: String(ctx.backoffLimit || 6),
371
+ validate: value => {
372
+ const num = parseInt(value, 10);
373
+ if (isNaN(num) || num < 0) {
374
+ return 'Must be a non-negative number';
375
+ }
376
+ return true;
377
+ },
378
+ });
379
+ const backoffLimit = parseInt(backoffInput || '6', 10);
380
+ // Completions
381
+ ui.newLine();
382
+ const completionsInput = await input({
383
+ message: 'Number of completions (total successful pods):',
384
+ defaultValue: String(ctx.completions || 1),
385
+ validate: value => {
386
+ const num = parseInt(value, 10);
387
+ if (isNaN(num) || num < 1) {
388
+ return 'Must be a positive number';
389
+ }
390
+ return true;
391
+ },
392
+ });
393
+ const completions = parseInt(completionsInput || '1', 10);
394
+ // Parallelism
395
+ ui.newLine();
396
+ const parallelismInput = await input({
397
+ message: 'Parallelism (concurrent pods):',
398
+ defaultValue: String(ctx.parallelism || 1),
399
+ validate: value => {
400
+ const num = parseInt(value, 10);
401
+ if (isNaN(num) || num < 1) {
402
+ return 'Must be a positive number';
403
+ }
404
+ return true;
405
+ },
406
+ });
407
+ const parallelism = parseInt(parallelismInput || '1', 10);
408
+ return {
409
+ success: true,
410
+ data: {
411
+ schedule,
412
+ backoffLimit,
413
+ completions,
414
+ parallelism,
415
+ },
416
+ };
417
+ }
418
+ /**
419
+ * Step 5: Port & Service Configuration
420
+ */
421
+ async function serviceConfigStep(ctx) {
422
+ // Container port
423
+ const portInput = await input({
424
+ message: 'Container port:',
425
+ defaultValue: String(ctx.containerPort || 8080),
426
+ validate: value => {
427
+ const num = parseInt(value, 10);
428
+ if (isNaN(num) || num < 1 || num > 65535) {
429
+ return 'Must be between 1 and 65535';
430
+ }
431
+ return true;
432
+ },
433
+ });
434
+ if (!portInput) {
435
+ return { success: false, error: 'Port is required' };
436
+ }
437
+ const containerPort = parseInt(portInput, 10);
438
+ // Service exposure (not for Jobs)
439
+ let includeService = false;
440
+ let serviceType = 'ClusterIP';
441
+ let servicePort = containerPort;
442
+ if (!['job', 'cronjob'].includes(ctx.workloadType || '')) {
443
+ ui.newLine();
444
+ includeService = await confirm({
445
+ message: 'Create a Service to expose this workload?',
446
+ defaultValue: true,
447
+ });
448
+ if (includeService) {
449
+ ui.newLine();
450
+ serviceType =
451
+ (await select({
452
+ message: 'Service type:',
453
+ options: [
454
+ {
455
+ value: 'ClusterIP',
456
+ label: 'ClusterIP',
457
+ description: 'Internal cluster access only (default)',
458
+ },
459
+ {
460
+ value: 'NodePort',
461
+ label: 'NodePort',
462
+ description: "Expose on each node's IP at a static port",
463
+ },
464
+ {
465
+ value: 'LoadBalancer',
466
+ label: 'LoadBalancer',
467
+ description: 'External load balancer (cloud provider)',
468
+ },
469
+ ],
470
+ defaultValue: ctx.serviceType || 'ClusterIP',
471
+ })) || 'ClusterIP';
472
+ const servicePortInput = await input({
473
+ message: 'Service port:',
474
+ defaultValue: String(ctx.servicePort || containerPort),
475
+ validate: value => {
476
+ const num = parseInt(value, 10);
477
+ if (isNaN(num) || num < 1 || num > 65535) {
478
+ return 'Must be between 1 and 65535';
479
+ }
480
+ return true;
481
+ },
482
+ });
483
+ servicePort = parseInt(servicePortInput || String(containerPort), 10);
484
+ }
485
+ }
486
+ return {
487
+ success: true,
488
+ data: {
489
+ containerPort,
490
+ includeService,
491
+ serviceType,
492
+ servicePort,
493
+ },
494
+ };
495
+ }
496
+ /**
497
+ * Step 6: Resource Limits
498
+ */
499
+ async function resourceLimitsStep(ctx) {
500
+ const setLimits = await confirm({
501
+ message: 'Configure resource requests and limits? (recommended for production)',
502
+ defaultValue: true,
503
+ });
504
+ if (!setLimits) {
505
+ return { success: true, data: {} };
506
+ }
507
+ ui.newLine();
508
+ ui.info('Resource requests (guaranteed resources):');
509
+ const cpuRequest = await input({
510
+ message: 'CPU request (e.g., 100m, 0.5):',
511
+ defaultValue: ctx.cpuRequest || '100m',
512
+ });
513
+ const memoryRequest = await input({
514
+ message: 'Memory request (e.g., 128Mi, 1Gi):',
515
+ defaultValue: ctx.memoryRequest || '128Mi',
516
+ });
517
+ ui.newLine();
518
+ ui.info('Resource limits (maximum allowed):');
519
+ const cpuLimit = await input({
520
+ message: 'CPU limit (e.g., 500m, 1):',
521
+ defaultValue: ctx.cpuLimit || '500m',
522
+ });
523
+ const memoryLimit = await input({
524
+ message: 'Memory limit (e.g., 256Mi, 2Gi):',
525
+ defaultValue: ctx.memoryLimit || '256Mi',
526
+ });
527
+ return {
528
+ success: true,
529
+ data: {
530
+ cpuRequest,
531
+ cpuLimit,
532
+ memoryRequest,
533
+ memoryLimit,
534
+ },
535
+ };
536
+ }
537
+ /**
538
+ * Step 7: Additional Resources
539
+ */
540
+ async function additionalResourcesStep(ctx) {
541
+ const resourceOptions = [
542
+ { value: 'configmap', label: 'ConfigMap', description: 'Environment configuration' },
543
+ { value: 'secret', label: 'Secret', description: 'Sensitive data (passwords, tokens)' },
544
+ ];
545
+ // Only show Ingress for services
546
+ if (ctx.includeService) {
547
+ resourceOptions.push({
548
+ value: 'ingress',
549
+ label: 'Ingress',
550
+ description: 'HTTP/HTTPS routing (requires ingress controller)',
551
+ });
552
+ }
553
+ // Only show PDB for Deployments/StatefulSets
554
+ if (['deployment', 'statefulset'].includes(ctx.workloadType || '')) {
555
+ resourceOptions.push({
556
+ value: 'pdb',
557
+ label: 'PodDisruptionBudget',
558
+ description: 'Ensure availability during disruptions',
559
+ });
560
+ }
561
+ const selectedResources = (await multiSelect({
562
+ message: 'Select additional resources to generate:',
563
+ options: resourceOptions,
564
+ required: false,
565
+ }));
566
+ const includeConfigMap = selectedResources.includes('configmap') || ctx.includeConfigMap;
567
+ const includeSecret = selectedResources.includes('secret') || ctx.includeSecret;
568
+ const includeIngress = selectedResources.includes('ingress') || ctx.includeIngress;
569
+ const includePdb = selectedResources.includes('pdb') || ctx.includePdb;
570
+ // Ingress configuration
571
+ let ingressHost;
572
+ let ingressPath;
573
+ let ingressTls = false;
574
+ if (includeIngress) {
575
+ ui.newLine();
576
+ ui.info('Ingress Configuration:');
577
+ ingressHost = await input({
578
+ message: 'Hostname (e.g., app.example.com):',
579
+ defaultValue: ctx.ingressHost || `${ctx.name}.example.com`,
580
+ });
581
+ ingressPath = await input({
582
+ message: 'Path:',
583
+ defaultValue: ctx.ingressPath || '/',
584
+ });
585
+ ingressTls = await confirm({
586
+ message: 'Enable TLS?',
587
+ defaultValue: ctx.ingressTls ?? true,
588
+ });
589
+ }
590
+ // PDB configuration
591
+ let minAvailable;
592
+ if (includePdb) {
593
+ ui.newLine();
594
+ const minAvailableInput = await input({
595
+ message: 'Minimum available pods (number or percentage like "50%"):',
596
+ defaultValue: String(ctx.minAvailable || 1),
597
+ });
598
+ minAvailable = minAvailableInput?.includes('%')
599
+ ? minAvailableInput
600
+ : parseInt(minAvailableInput || '1', 10);
601
+ }
602
+ return {
603
+ success: true,
604
+ data: {
605
+ includeConfigMap,
606
+ includeSecret,
607
+ includeIngress,
608
+ includePdb,
609
+ ingressHost,
610
+ ingressPath,
611
+ ingressTls,
612
+ minAvailable,
613
+ },
614
+ };
615
+ }
616
+ /**
617
+ * Step 8: Health Checks
618
+ */
619
+ async function healthChecksStep(ctx) {
620
+ const includeProbes = await confirm({
621
+ message: 'Configure health check probes? (recommended for production)',
622
+ defaultValue: true,
623
+ });
624
+ if (!includeProbes) {
625
+ return { success: true, data: { includeProbes: false } };
626
+ }
627
+ ui.newLine();
628
+ const livenessPath = await input({
629
+ message: 'Liveness probe path (e.g., /healthz):',
630
+ defaultValue: ctx.livenessPath || '/healthz',
631
+ });
632
+ const readinessPath = await input({
633
+ message: 'Readiness probe path (e.g., /ready):',
634
+ defaultValue: ctx.readinessPath || '/ready',
635
+ });
636
+ return {
637
+ success: true,
638
+ data: {
639
+ includeProbes,
640
+ livenessPath,
641
+ readinessPath,
642
+ },
643
+ };
644
+ }
645
+ /**
646
+ * Step 9: Output Configuration
647
+ */
648
+ async function outputConfigStep(ctx) {
649
+ const outputFormat = await select({
650
+ message: 'Output format:',
651
+ options: [
652
+ {
653
+ value: 'multiple',
654
+ label: 'Multiple files',
655
+ description: 'One file per resource (deployment.yaml, service.yaml, etc.)',
656
+ },
657
+ {
658
+ value: 'single',
659
+ label: 'Single file',
660
+ description: 'All resources in one file with document separators',
661
+ },
662
+ {
663
+ value: 'kustomize',
664
+ label: 'Kustomize structure',
665
+ description: 'Base with kustomization.yaml for overlays',
666
+ },
667
+ ],
668
+ defaultValue: ctx.outputFormat || 'multiple',
669
+ });
670
+ ui.newLine();
671
+ const outputPath = await pathInput('Output directory:', ctx.outputPath || `./${ctx.name}-k8s`);
672
+ if (!outputPath) {
673
+ return { success: false, error: 'Output path is required' };
674
+ }
675
+ return {
676
+ success: true,
677
+ data: {
678
+ outputFormat,
679
+ outputPath,
680
+ },
681
+ };
682
+ }
683
+ /**
684
+ * Step 10: Generate Manifests
685
+ */
686
+ async function generateStep(ctx) {
687
+ ui.startSpinner({ message: 'Generating Kubernetes manifests...' });
688
+ try {
689
+ // Generate manifests locally using built-in generator
690
+ const files = generateManifestsLocally(ctx);
691
+ await writeFilesToDisk(files, ctx.outputPath);
692
+ ui.stopSpinnerSuccess(`Generated ${files.length} manifest(s)`);
693
+ return {
694
+ success: true,
695
+ data: {
696
+ generatedFiles: files.map(f => f.path),
697
+ },
698
+ };
699
+ }
700
+ catch (error) {
701
+ return {
702
+ success: false,
703
+ error: error.message,
704
+ };
705
+ }
706
+ }
707
+ /**
708
+ * Build the generation request from context
709
+ */
710
+ function buildGenerationRequest(ctx) {
711
+ return {
712
+ workloadType: ctx.workloadType,
713
+ name: ctx.name,
714
+ namespace: ctx.namespace,
715
+ image: ctx.image,
716
+ imageTag: ctx.imageTag,
717
+ replicas: ctx.replicas,
718
+ containerPort: ctx.containerPort,
719
+ serviceType: ctx.serviceType,
720
+ servicePort: ctx.servicePort,
721
+ cpuRequest: ctx.cpuRequest,
722
+ cpuLimit: ctx.cpuLimit,
723
+ memoryRequest: ctx.memoryRequest,
724
+ memoryLimit: ctx.memoryLimit,
725
+ includeService: ctx.includeService,
726
+ includeIngress: ctx.includeIngress,
727
+ includeHpa: ctx.includeHpa,
728
+ includePdb: ctx.includePdb,
729
+ includeConfigMap: ctx.includeConfigMap,
730
+ includeSecret: ctx.includeSecret,
731
+ includeProbes: ctx.includeProbes,
732
+ livenessPath: ctx.livenessPath,
733
+ readinessPath: ctx.readinessPath,
734
+ ingressHost: ctx.ingressHost,
735
+ ingressPath: ctx.ingressPath,
736
+ ingressTls: ctx.ingressTls,
737
+ minAvailable: ctx.minAvailable,
738
+ minReplicas: ctx.minReplicas,
739
+ maxReplicas: ctx.maxReplicas,
740
+ targetCPUUtilization: ctx.targetCPUUtilization,
741
+ schedule: ctx.schedule,
742
+ backoffLimit: ctx.backoffLimit,
743
+ completions: ctx.completions,
744
+ parallelism: ctx.parallelism,
745
+ outputFormat: ctx.outputFormat,
746
+ outputPath: ctx.outputPath,
747
+ };
748
+ }
749
+ /**
750
+ * Generate manifests locally when service is unavailable
751
+ */
752
+ function generateManifestsLocally(ctx) {
753
+ const files = [];
754
+ const labels = {
755
+ 'app.kubernetes.io/name': ctx.name || 'unnamed',
756
+ 'app.kubernetes.io/instance': ctx.name || 'unnamed',
757
+ 'app.kubernetes.io/managed-by': 'nimbus',
758
+ };
759
+ // Generate main workload
760
+ const workloadManifest = generateWorkloadManifest(ctx, labels);
761
+ files.push({
762
+ name: `${ctx.workloadType}.yaml`,
763
+ content: workloadManifest,
764
+ path: `${ctx.outputPath}/${ctx.workloadType}.yaml`,
765
+ });
766
+ // Generate Service
767
+ if (ctx.includeService) {
768
+ files.push({
769
+ name: 'service.yaml',
770
+ content: generateServiceManifest(ctx, labels),
771
+ path: `${ctx.outputPath}/service.yaml`,
772
+ });
773
+ }
774
+ // Generate Ingress
775
+ if (ctx.includeIngress) {
776
+ files.push({
777
+ name: 'ingress.yaml',
778
+ content: generateIngressManifest(ctx, labels),
779
+ path: `${ctx.outputPath}/ingress.yaml`,
780
+ });
781
+ }
782
+ // Generate HPA
783
+ if (ctx.includeHpa) {
784
+ files.push({
785
+ name: 'hpa.yaml',
786
+ content: generateHpaManifest(ctx, labels),
787
+ path: `${ctx.outputPath}/hpa.yaml`,
788
+ });
789
+ }
790
+ // Generate PDB
791
+ if (ctx.includePdb) {
792
+ files.push({
793
+ name: 'pdb.yaml',
794
+ content: generatePdbManifest(ctx, labels),
795
+ path: `${ctx.outputPath}/pdb.yaml`,
796
+ });
797
+ }
798
+ // Generate ConfigMap
799
+ if (ctx.includeConfigMap) {
800
+ files.push({
801
+ name: 'configmap.yaml',
802
+ content: generateConfigMapManifest(ctx, labels),
803
+ path: `${ctx.outputPath}/configmap.yaml`,
804
+ });
805
+ }
806
+ // Generate Secret
807
+ if (ctx.includeSecret) {
808
+ files.push({
809
+ name: 'secret.yaml',
810
+ content: generateSecretManifest(ctx, labels),
811
+ path: `${ctx.outputPath}/secret.yaml`,
812
+ });
813
+ }
814
+ return files;
815
+ }
816
+ /**
817
+ * Generate main workload manifest
818
+ */
819
+ function generateWorkloadManifest(ctx, labels) {
820
+ const { workloadType, name, namespace, image, imageTag, replicas, containerPort } = ctx;
821
+ // Build container spec
822
+ const containerSpec = {
823
+ name,
824
+ image: `${image}:${imageTag || 'latest'}`,
825
+ ports: containerPort ? [{ containerPort }] : undefined,
826
+ resources: ctx.cpuRequest || ctx.memoryRequest
827
+ ? {
828
+ requests: {
829
+ ...(ctx.cpuRequest && { cpu: ctx.cpuRequest }),
830
+ ...(ctx.memoryRequest && { memory: ctx.memoryRequest }),
831
+ },
832
+ limits: {
833
+ ...(ctx.cpuLimit && { cpu: ctx.cpuLimit }),
834
+ ...(ctx.memoryLimit && { memory: ctx.memoryLimit }),
835
+ },
836
+ }
837
+ : undefined,
838
+ };
839
+ // Add probes if configured
840
+ if (ctx.includeProbes) {
841
+ containerSpec.livenessProbe = {
842
+ httpGet: {
843
+ path: ctx.livenessPath || '/healthz',
844
+ port: containerPort,
845
+ },
846
+ initialDelaySeconds: 10,
847
+ periodSeconds: 10,
848
+ };
849
+ containerSpec.readinessProbe = {
850
+ httpGet: {
851
+ path: ctx.readinessPath || '/ready',
852
+ port: containerPort,
853
+ },
854
+ initialDelaySeconds: 5,
855
+ periodSeconds: 5,
856
+ };
857
+ }
858
+ // Add envFrom if ConfigMap or Secret
859
+ const envFrom = [];
860
+ if (ctx.includeConfigMap) {
861
+ envFrom.push({ configMapRef: { name: `${name}-config` } });
862
+ }
863
+ if (ctx.includeSecret) {
864
+ envFrom.push({ secretRef: { name: `${name}-secret` } });
865
+ }
866
+ if (envFrom.length > 0) {
867
+ containerSpec.envFrom = envFrom;
868
+ }
869
+ // Clean up undefined values
870
+ Object.keys(containerSpec).forEach(key => {
871
+ if (containerSpec[key] === undefined) {
872
+ delete containerSpec[key];
873
+ }
874
+ });
875
+ const podSpec = {
876
+ containers: [containerSpec],
877
+ };
878
+ let manifest;
879
+ switch (workloadType) {
880
+ case 'deployment':
881
+ manifest = {
882
+ apiVersion: 'apps/v1',
883
+ kind: 'Deployment',
884
+ metadata: { name, namespace, labels },
885
+ spec: {
886
+ replicas,
887
+ selector: { matchLabels: { 'app.kubernetes.io/name': name } },
888
+ template: {
889
+ metadata: { labels },
890
+ spec: podSpec,
891
+ },
892
+ },
893
+ };
894
+ break;
895
+ case 'statefulset':
896
+ manifest = {
897
+ apiVersion: 'apps/v1',
898
+ kind: 'StatefulSet',
899
+ metadata: { name, namespace, labels },
900
+ spec: {
901
+ replicas,
902
+ serviceName: name,
903
+ selector: { matchLabels: { 'app.kubernetes.io/name': name } },
904
+ template: {
905
+ metadata: { labels },
906
+ spec: podSpec,
907
+ },
908
+ },
909
+ };
910
+ break;
911
+ case 'daemonset':
912
+ manifest = {
913
+ apiVersion: 'apps/v1',
914
+ kind: 'DaemonSet',
915
+ metadata: { name, namespace, labels },
916
+ spec: {
917
+ selector: { matchLabels: { 'app.kubernetes.io/name': name } },
918
+ template: {
919
+ metadata: { labels },
920
+ spec: podSpec,
921
+ },
922
+ },
923
+ };
924
+ break;
925
+ case 'job':
926
+ manifest = {
927
+ apiVersion: 'batch/v1',
928
+ kind: 'Job',
929
+ metadata: { name, namespace, labels },
930
+ spec: {
931
+ backoffLimit: ctx.backoffLimit,
932
+ completions: ctx.completions,
933
+ parallelism: ctx.parallelism,
934
+ template: {
935
+ metadata: { labels },
936
+ spec: {
937
+ ...podSpec,
938
+ restartPolicy: 'Never',
939
+ },
940
+ },
941
+ },
942
+ };
943
+ break;
944
+ case 'cronjob':
945
+ manifest = {
946
+ apiVersion: 'batch/v1',
947
+ kind: 'CronJob',
948
+ metadata: { name, namespace, labels },
949
+ spec: {
950
+ schedule: ctx.schedule,
951
+ jobTemplate: {
952
+ spec: {
953
+ backoffLimit: ctx.backoffLimit,
954
+ template: {
955
+ metadata: { labels },
956
+ spec: {
957
+ ...podSpec,
958
+ restartPolicy: 'Never',
959
+ },
960
+ },
961
+ },
962
+ },
963
+ },
964
+ };
965
+ break;
966
+ default:
967
+ manifest = {};
968
+ }
969
+ return toYaml(manifest);
970
+ }
971
+ /**
972
+ * Generate Service manifest
973
+ */
974
+ function generateServiceManifest(ctx, labels) {
975
+ const manifest = {
976
+ apiVersion: 'v1',
977
+ kind: 'Service',
978
+ metadata: {
979
+ name: ctx.name,
980
+ namespace: ctx.namespace,
981
+ labels,
982
+ },
983
+ spec: {
984
+ type: ctx.serviceType,
985
+ selector: { 'app.kubernetes.io/name': ctx.name },
986
+ ports: [
987
+ {
988
+ port: ctx.servicePort,
989
+ targetPort: ctx.containerPort,
990
+ protocol: 'TCP',
991
+ },
992
+ ],
993
+ },
994
+ };
995
+ return toYaml(manifest);
996
+ }
997
+ /**
998
+ * Generate Ingress manifest
999
+ */
1000
+ function generateIngressManifest(ctx, labels) {
1001
+ const manifest = {
1002
+ apiVersion: 'networking.k8s.io/v1',
1003
+ kind: 'Ingress',
1004
+ metadata: {
1005
+ name: ctx.name,
1006
+ namespace: ctx.namespace,
1007
+ labels,
1008
+ annotations: {
1009
+ 'kubernetes.io/ingress.class': 'nginx',
1010
+ },
1011
+ },
1012
+ spec: {
1013
+ rules: [
1014
+ {
1015
+ host: ctx.ingressHost,
1016
+ http: {
1017
+ paths: [
1018
+ {
1019
+ path: ctx.ingressPath || '/',
1020
+ pathType: 'Prefix',
1021
+ backend: {
1022
+ service: {
1023
+ name: ctx.name,
1024
+ port: { number: ctx.servicePort },
1025
+ },
1026
+ },
1027
+ },
1028
+ ],
1029
+ },
1030
+ },
1031
+ ],
1032
+ },
1033
+ };
1034
+ if (ctx.ingressTls) {
1035
+ manifest.spec.tls = [
1036
+ {
1037
+ hosts: [ctx.ingressHost],
1038
+ secretName: `${ctx.name}-tls`,
1039
+ },
1040
+ ];
1041
+ }
1042
+ return toYaml(manifest);
1043
+ }
1044
+ /**
1045
+ * Generate HPA manifest
1046
+ */
1047
+ function generateHpaManifest(ctx, labels) {
1048
+ const manifest = {
1049
+ apiVersion: 'autoscaling/v2',
1050
+ kind: 'HorizontalPodAutoscaler',
1051
+ metadata: {
1052
+ name: ctx.name,
1053
+ namespace: ctx.namespace,
1054
+ labels,
1055
+ },
1056
+ spec: {
1057
+ scaleTargetRef: {
1058
+ apiVersion: 'apps/v1',
1059
+ kind: ctx.workloadType === 'statefulset' ? 'StatefulSet' : 'Deployment',
1060
+ name: ctx.name,
1061
+ },
1062
+ minReplicas: ctx.minReplicas,
1063
+ maxReplicas: ctx.maxReplicas,
1064
+ metrics: [
1065
+ {
1066
+ type: 'Resource',
1067
+ resource: {
1068
+ name: 'cpu',
1069
+ target: {
1070
+ type: 'Utilization',
1071
+ averageUtilization: ctx.targetCPUUtilization,
1072
+ },
1073
+ },
1074
+ },
1075
+ ],
1076
+ },
1077
+ };
1078
+ return toYaml(manifest);
1079
+ }
1080
+ /**
1081
+ * Generate PDB manifest
1082
+ */
1083
+ function generatePdbManifest(ctx, labels) {
1084
+ const manifest = {
1085
+ apiVersion: 'policy/v1',
1086
+ kind: 'PodDisruptionBudget',
1087
+ metadata: {
1088
+ name: ctx.name,
1089
+ namespace: ctx.namespace,
1090
+ labels,
1091
+ },
1092
+ spec: {
1093
+ minAvailable: ctx.minAvailable,
1094
+ selector: { matchLabels: { 'app.kubernetes.io/name': ctx.name } },
1095
+ },
1096
+ };
1097
+ return toYaml(manifest);
1098
+ }
1099
+ /**
1100
+ * Generate ConfigMap manifest
1101
+ */
1102
+ function generateConfigMapManifest(ctx, labels) {
1103
+ const manifest = {
1104
+ apiVersion: 'v1',
1105
+ kind: 'ConfigMap',
1106
+ metadata: {
1107
+ name: `${ctx.name}-config`,
1108
+ namespace: ctx.namespace,
1109
+ labels,
1110
+ },
1111
+ data: {
1112
+ // Placeholder values - user should customize
1113
+ APP_ENV: 'production',
1114
+ LOG_LEVEL: 'info',
1115
+ },
1116
+ };
1117
+ return toYaml(manifest);
1118
+ }
1119
+ /**
1120
+ * Generate Secret manifest
1121
+ */
1122
+ function generateSecretManifest(ctx, labels) {
1123
+ const manifest = {
1124
+ apiVersion: 'v1',
1125
+ kind: 'Secret',
1126
+ metadata: {
1127
+ name: `${ctx.name}-secret`,
1128
+ namespace: ctx.namespace,
1129
+ labels,
1130
+ },
1131
+ type: 'Opaque',
1132
+ stringData: {
1133
+ // Placeholder values - user should customize
1134
+ 'example-key': 'example-value',
1135
+ },
1136
+ };
1137
+ return toYaml(manifest);
1138
+ }
1139
+ /**
1140
+ * Convert object to YAML string
1141
+ */
1142
+ function toYaml(obj, indent = 0) {
1143
+ const lines = [];
1144
+ const spaces = ' '.repeat(indent);
1145
+ for (const [key, value] of Object.entries(obj)) {
1146
+ if (value === undefined || value === null) {
1147
+ continue;
1148
+ }
1149
+ if (Array.isArray(value)) {
1150
+ if (value.length === 0) {
1151
+ continue;
1152
+ }
1153
+ lines.push(`${spaces}${key}:`);
1154
+ for (const item of value) {
1155
+ if (typeof item === 'object' && item !== null) {
1156
+ const itemYaml = toYaml(item, indent + 1);
1157
+ const itemLines = itemYaml.split('\n').filter(l => l.trim());
1158
+ lines.push(`${spaces}- ${itemLines[0].trim()}`);
1159
+ for (let i = 1; i < itemLines.length; i++) {
1160
+ lines.push(`${spaces} ${itemLines[i].trim()}`);
1161
+ }
1162
+ }
1163
+ else {
1164
+ lines.push(`${spaces}- ${item}`);
1165
+ }
1166
+ }
1167
+ }
1168
+ else if (typeof value === 'object') {
1169
+ lines.push(`${spaces}${key}:`);
1170
+ lines.push(toYaml(value, indent + 1));
1171
+ }
1172
+ else if (typeof value === 'string' &&
1173
+ (value.includes(':') || value.includes('#') || value.includes('\n'))) {
1174
+ lines.push(`${spaces}${key}: "${value}"`);
1175
+ }
1176
+ else {
1177
+ lines.push(`${spaces}${key}: ${value}`);
1178
+ }
1179
+ }
1180
+ return lines.join('\n');
1181
+ }
1182
+ /**
1183
+ * Write files to disk
1184
+ */
1185
+ async function writeFilesToDisk(files, outputPath) {
1186
+ const fs = await import('fs/promises');
1187
+ const path = await import('path');
1188
+ // Create output directory
1189
+ await fs.mkdir(outputPath, { recursive: true });
1190
+ // Write each file
1191
+ for (const file of files) {
1192
+ const filePath = path.join(outputPath, file.name);
1193
+ await fs.writeFile(filePath, file.content, 'utf-8');
1194
+ }
1195
+ }
1196
+ /**
1197
+ * Run in non-interactive mode
1198
+ */
1199
+ async function runNonInteractive(options) {
1200
+ ui.header('nimbus generate k8s', 'Non-interactive mode');
1201
+ // Validate required options
1202
+ if (!options.name) {
1203
+ ui.error('Name is required in non-interactive mode (--name)');
1204
+ process.exit(1);
1205
+ }
1206
+ if (!options.image) {
1207
+ ui.error('Image is required in non-interactive mode (--image)');
1208
+ process.exit(1);
1209
+ }
1210
+ ui.info(`Workload type: ${options.workloadType || 'deployment'}`);
1211
+ ui.info(`Name: ${options.name}`);
1212
+ ui.info(`Namespace: ${options.namespace || 'default'}`);
1213
+ ui.info(`Image: ${options.image}`);
1214
+ ui.info(`Output: ${options.output || `./${options.name}-k8s`}`);
1215
+ // Build context from options
1216
+ const ctx = {
1217
+ workloadType: options.workloadType || 'deployment',
1218
+ name: options.name,
1219
+ namespace: options.namespace || 'default',
1220
+ image: options.image.split(':')[0],
1221
+ imageTag: options.image.split(':')[1] || 'latest',
1222
+ replicas: options.replicas ?? 2,
1223
+ containerPort: options.port ?? 8080,
1224
+ serviceType: options.serviceType || 'ClusterIP',
1225
+ includeService: true,
1226
+ includeIngress: options.includeIngress ?? false,
1227
+ includeHpa: options.includeHpa ?? false,
1228
+ includePdb: options.includePdb ?? false,
1229
+ includeConfigMap: options.includeConfigMap ?? false,
1230
+ includeSecret: options.includeSecret ?? false,
1231
+ cpuRequest: options.cpuRequest,
1232
+ cpuLimit: options.cpuLimit,
1233
+ memoryRequest: options.memoryRequest,
1234
+ memoryLimit: options.memoryLimit,
1235
+ outputPath: options.output || `./${options.name}-k8s`,
1236
+ outputFormat: 'multiple',
1237
+ };
1238
+ ui.newLine();
1239
+ ui.startSpinner({ message: 'Generating manifests...' });
1240
+ try {
1241
+ const files = generateManifestsLocally(ctx);
1242
+ await writeFilesToDisk(files, ctx.outputPath);
1243
+ ui.stopSpinnerSuccess(`Generated ${files.length} manifest(s)`);
1244
+ ui.newLine();
1245
+ ui.box({
1246
+ title: 'Complete!',
1247
+ content: [
1248
+ `Generated ${files.length} manifest(s) in ${ctx.outputPath}:`,
1249
+ ...files.map(f => ` - ${f.name}`),
1250
+ ],
1251
+ style: 'rounded',
1252
+ borderColor: 'green',
1253
+ padding: 1,
1254
+ });
1255
+ }
1256
+ catch (error) {
1257
+ ui.stopSpinnerFail('Generation failed');
1258
+ ui.error(error.message);
1259
+ process.exit(1);
1260
+ }
1261
+ }
1262
+ // Export as default command
1263
+ export default generateK8sCommand;