@build-astron-co/nimbus 0.2.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 (313) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +628 -0
  3. package/bin/nimbus +38 -0
  4. package/package.json +80 -0
  5. package/src/__tests__/app.test.ts +76 -0
  6. package/src/__tests__/audit.test.ts +877 -0
  7. package/src/__tests__/circuit-breaker.test.ts +116 -0
  8. package/src/__tests__/cli-run.test.ts +115 -0
  9. package/src/__tests__/context-manager.test.ts +502 -0
  10. package/src/__tests__/context.test.ts +242 -0
  11. package/src/__tests__/enterprise.test.ts +401 -0
  12. package/src/__tests__/generator.test.ts +433 -0
  13. package/src/__tests__/hooks.test.ts +582 -0
  14. package/src/__tests__/init.test.ts +436 -0
  15. package/src/__tests__/intent-parser.test.ts +229 -0
  16. package/src/__tests__/llm-router.test.ts +209 -0
  17. package/src/__tests__/lsp.test.ts +293 -0
  18. package/src/__tests__/modes.test.ts +336 -0
  19. package/src/__tests__/permissions.test.ts +338 -0
  20. package/src/__tests__/serve.test.ts +275 -0
  21. package/src/__tests__/sessions.test.ts +227 -0
  22. package/src/__tests__/sharing.test.ts +288 -0
  23. package/src/__tests__/snapshots.test.ts +581 -0
  24. package/src/__tests__/state-db.test.ts +334 -0
  25. package/src/__tests__/stream-with-tools.test.ts +732 -0
  26. package/src/__tests__/subagents.test.ts +176 -0
  27. package/src/__tests__/system-prompt.test.ts +169 -0
  28. package/src/__tests__/tool-converter.test.ts +256 -0
  29. package/src/__tests__/tool-schemas.test.ts +397 -0
  30. package/src/__tests__/tools.test.ts +143 -0
  31. package/src/__tests__/version.test.ts +49 -0
  32. package/src/agent/compaction-agent.ts +227 -0
  33. package/src/agent/context-manager.ts +435 -0
  34. package/src/agent/context.ts +427 -0
  35. package/src/agent/deploy-preview.ts +426 -0
  36. package/src/agent/index.ts +68 -0
  37. package/src/agent/loop.ts +717 -0
  38. package/src/agent/modes.ts +429 -0
  39. package/src/agent/permissions.ts +466 -0
  40. package/src/agent/subagents/base.ts +116 -0
  41. package/src/agent/subagents/cost.ts +51 -0
  42. package/src/agent/subagents/explore.ts +42 -0
  43. package/src/agent/subagents/general.ts +54 -0
  44. package/src/agent/subagents/index.ts +102 -0
  45. package/src/agent/subagents/infra.ts +59 -0
  46. package/src/agent/subagents/security.ts +69 -0
  47. package/src/agent/system-prompt.ts +436 -0
  48. package/src/app.ts +122 -0
  49. package/src/audit/activity-log.ts +290 -0
  50. package/src/audit/compliance-checker.ts +540 -0
  51. package/src/audit/cost-tracker.ts +318 -0
  52. package/src/audit/index.ts +23 -0
  53. package/src/audit/security-scanner.ts +596 -0
  54. package/src/auth/guard.ts +75 -0
  55. package/src/auth/index.ts +56 -0
  56. package/src/auth/oauth.ts +455 -0
  57. package/src/auth/providers.ts +470 -0
  58. package/src/auth/sso.ts +113 -0
  59. package/src/auth/store.ts +505 -0
  60. package/src/auth/types.ts +187 -0
  61. package/src/build.ts +141 -0
  62. package/src/cli/index.ts +16 -0
  63. package/src/cli/init.ts +854 -0
  64. package/src/cli/openapi-spec.ts +356 -0
  65. package/src/cli/run.ts +237 -0
  66. package/src/cli/serve-auth.ts +80 -0
  67. package/src/cli/serve.ts +462 -0
  68. package/src/cli/web.ts +67 -0
  69. package/src/cli.ts +1417 -0
  70. package/src/clients/core-engine-client.ts +227 -0
  71. package/src/clients/enterprise-client.ts +334 -0
  72. package/src/clients/generator-client.ts +351 -0
  73. package/src/clients/git-client.ts +627 -0
  74. package/src/clients/github-client.ts +410 -0
  75. package/src/clients/helm-client.ts +504 -0
  76. package/src/clients/index.ts +80 -0
  77. package/src/clients/k8s-client.ts +497 -0
  78. package/src/clients/llm-client.ts +161 -0
  79. package/src/clients/rest-client.ts +130 -0
  80. package/src/clients/service-discovery.ts +33 -0
  81. package/src/clients/terraform-client.ts +482 -0
  82. package/src/clients/tools-client.ts +1843 -0
  83. package/src/clients/ws-client.ts +115 -0
  84. package/src/commands/analyze/index.ts +352 -0
  85. package/src/commands/apply/helm.ts +473 -0
  86. package/src/commands/apply/index.ts +213 -0
  87. package/src/commands/apply/k8s.ts +454 -0
  88. package/src/commands/apply/terraform.ts +582 -0
  89. package/src/commands/ask.ts +167 -0
  90. package/src/commands/audit/index.ts +238 -0
  91. package/src/commands/auth-cloud.ts +294 -0
  92. package/src/commands/auth-list.ts +134 -0
  93. package/src/commands/auth-profile.ts +121 -0
  94. package/src/commands/auth-status.ts +141 -0
  95. package/src/commands/aws/ec2.ts +501 -0
  96. package/src/commands/aws/iam.ts +397 -0
  97. package/src/commands/aws/index.ts +133 -0
  98. package/src/commands/aws/lambda.ts +396 -0
  99. package/src/commands/aws/rds.ts +439 -0
  100. package/src/commands/aws/s3.ts +439 -0
  101. package/src/commands/aws/vpc.ts +393 -0
  102. package/src/commands/aws-discover.ts +649 -0
  103. package/src/commands/aws-terraform.ts +805 -0
  104. package/src/commands/azure/aks.ts +376 -0
  105. package/src/commands/azure/functions.ts +253 -0
  106. package/src/commands/azure/index.ts +116 -0
  107. package/src/commands/azure/storage.ts +478 -0
  108. package/src/commands/azure/vm.ts +355 -0
  109. package/src/commands/billing/index.ts +256 -0
  110. package/src/commands/chat.ts +314 -0
  111. package/src/commands/config.ts +346 -0
  112. package/src/commands/cost/cloud-cost-estimator.ts +266 -0
  113. package/src/commands/cost/estimator.ts +79 -0
  114. package/src/commands/cost/index.ts +594 -0
  115. package/src/commands/cost/parsers/terraform.ts +273 -0
  116. package/src/commands/cost/parsers/types.ts +25 -0
  117. package/src/commands/cost/pricing/aws.ts +544 -0
  118. package/src/commands/cost/pricing/azure.ts +499 -0
  119. package/src/commands/cost/pricing/gcp.ts +396 -0
  120. package/src/commands/cost/pricing/index.ts +40 -0
  121. package/src/commands/demo.ts +250 -0
  122. package/src/commands/doctor.ts +794 -0
  123. package/src/commands/drift/index.ts +439 -0
  124. package/src/commands/explain.ts +277 -0
  125. package/src/commands/feedback.ts +389 -0
  126. package/src/commands/fix.ts +324 -0
  127. package/src/commands/fs/index.ts +402 -0
  128. package/src/commands/gcp/compute.ts +325 -0
  129. package/src/commands/gcp/functions.ts +271 -0
  130. package/src/commands/gcp/gke.ts +438 -0
  131. package/src/commands/gcp/iam.ts +344 -0
  132. package/src/commands/gcp/index.ts +129 -0
  133. package/src/commands/gcp/storage.ts +284 -0
  134. package/src/commands/generate-helm.ts +1249 -0
  135. package/src/commands/generate-k8s.ts +1560 -0
  136. package/src/commands/generate-terraform.ts +1460 -0
  137. package/src/commands/gh/index.ts +863 -0
  138. package/src/commands/git/index.ts +1343 -0
  139. package/src/commands/helm/index.ts +1126 -0
  140. package/src/commands/help.ts +539 -0
  141. package/src/commands/history.ts +142 -0
  142. package/src/commands/import.ts +868 -0
  143. package/src/commands/index.ts +367 -0
  144. package/src/commands/init.ts +1046 -0
  145. package/src/commands/k8s/index.ts +1137 -0
  146. package/src/commands/login.ts +631 -0
  147. package/src/commands/logout.ts +83 -0
  148. package/src/commands/onboarding.ts +228 -0
  149. package/src/commands/plan/display.ts +279 -0
  150. package/src/commands/plan/index.ts +599 -0
  151. package/src/commands/preview.ts +452 -0
  152. package/src/commands/questionnaire.ts +1270 -0
  153. package/src/commands/resume.ts +55 -0
  154. package/src/commands/team/index.ts +346 -0
  155. package/src/commands/template.ts +232 -0
  156. package/src/commands/tf/index.ts +1034 -0
  157. package/src/commands/upgrade.ts +550 -0
  158. package/src/commands/usage/index.ts +134 -0
  159. package/src/commands/version.ts +170 -0
  160. package/src/compat/index.ts +2 -0
  161. package/src/compat/runtime.ts +12 -0
  162. package/src/compat/sqlite.ts +107 -0
  163. package/src/config/index.ts +17 -0
  164. package/src/config/manager.ts +530 -0
  165. package/src/config/safety-policy.ts +358 -0
  166. package/src/config/schema.ts +125 -0
  167. package/src/config/types.ts +527 -0
  168. package/src/context/context-db.ts +199 -0
  169. package/src/demo/index.ts +349 -0
  170. package/src/demo/scenarios/full-journey.ts +229 -0
  171. package/src/demo/scenarios/getting-started.ts +127 -0
  172. package/src/demo/scenarios/helm-release.ts +341 -0
  173. package/src/demo/scenarios/k8s-deployment.ts +194 -0
  174. package/src/demo/scenarios/terraform-vpc.ts +170 -0
  175. package/src/demo/types.ts +92 -0
  176. package/src/engine/cost-estimator.ts +438 -0
  177. package/src/engine/diagram-generator.ts +256 -0
  178. package/src/engine/drift-detector.ts +902 -0
  179. package/src/engine/executor.ts +1035 -0
  180. package/src/engine/index.ts +76 -0
  181. package/src/engine/orchestrator.ts +636 -0
  182. package/src/engine/planner.ts +720 -0
  183. package/src/engine/safety.ts +743 -0
  184. package/src/engine/verifier.ts +770 -0
  185. package/src/enterprise/audit.ts +348 -0
  186. package/src/enterprise/auth.ts +270 -0
  187. package/src/enterprise/billing.ts +822 -0
  188. package/src/enterprise/index.ts +17 -0
  189. package/src/enterprise/teams.ts +443 -0
  190. package/src/generator/best-practices.ts +1608 -0
  191. package/src/generator/helm.ts +630 -0
  192. package/src/generator/index.ts +37 -0
  193. package/src/generator/intent-parser.ts +514 -0
  194. package/src/generator/kubernetes.ts +976 -0
  195. package/src/generator/terraform.ts +1867 -0
  196. package/src/history/index.ts +8 -0
  197. package/src/history/manager.ts +322 -0
  198. package/src/history/types.ts +34 -0
  199. package/src/hooks/config.ts +432 -0
  200. package/src/hooks/engine.ts +391 -0
  201. package/src/hooks/index.ts +4 -0
  202. package/src/llm/auth-bridge.ts +198 -0
  203. package/src/llm/circuit-breaker.ts +140 -0
  204. package/src/llm/config-loader.ts +201 -0
  205. package/src/llm/cost-calculator.ts +171 -0
  206. package/src/llm/index.ts +8 -0
  207. package/src/llm/model-aliases.ts +115 -0
  208. package/src/llm/provider-registry.ts +63 -0
  209. package/src/llm/providers/anthropic.ts +433 -0
  210. package/src/llm/providers/bedrock.ts +477 -0
  211. package/src/llm/providers/google.ts +405 -0
  212. package/src/llm/providers/ollama.ts +767 -0
  213. package/src/llm/providers/openai-compatible.ts +340 -0
  214. package/src/llm/providers/openai.ts +328 -0
  215. package/src/llm/providers/openrouter.ts +338 -0
  216. package/src/llm/router.ts +1035 -0
  217. package/src/llm/types.ts +232 -0
  218. package/src/lsp/client.ts +298 -0
  219. package/src/lsp/languages.ts +116 -0
  220. package/src/lsp/manager.ts +278 -0
  221. package/src/mcp/client.ts +402 -0
  222. package/src/mcp/index.ts +5 -0
  223. package/src/mcp/manager.ts +133 -0
  224. package/src/nimbus.ts +214 -0
  225. package/src/plugins/index.ts +27 -0
  226. package/src/plugins/loader.ts +334 -0
  227. package/src/plugins/manager.ts +376 -0
  228. package/src/plugins/types.ts +284 -0
  229. package/src/scanners/cicd-scanner.ts +258 -0
  230. package/src/scanners/cloud-scanner.ts +466 -0
  231. package/src/scanners/framework-scanner.ts +469 -0
  232. package/src/scanners/iac-scanner.ts +388 -0
  233. package/src/scanners/index.ts +539 -0
  234. package/src/scanners/language-scanner.ts +276 -0
  235. package/src/scanners/package-manager-scanner.ts +277 -0
  236. package/src/scanners/types.ts +172 -0
  237. package/src/sessions/manager.ts +365 -0
  238. package/src/sessions/types.ts +44 -0
  239. package/src/sharing/sync.ts +296 -0
  240. package/src/sharing/viewer.ts +97 -0
  241. package/src/snapshots/index.ts +2 -0
  242. package/src/snapshots/manager.ts +530 -0
  243. package/src/state/artifacts.ts +147 -0
  244. package/src/state/audit.ts +137 -0
  245. package/src/state/billing.ts +240 -0
  246. package/src/state/checkpoints.ts +117 -0
  247. package/src/state/config.ts +67 -0
  248. package/src/state/conversations.ts +14 -0
  249. package/src/state/credentials.ts +154 -0
  250. package/src/state/db.ts +58 -0
  251. package/src/state/index.ts +26 -0
  252. package/src/state/messages.ts +115 -0
  253. package/src/state/projects.ts +123 -0
  254. package/src/state/schema.ts +236 -0
  255. package/src/state/sessions.ts +147 -0
  256. package/src/state/teams.ts +200 -0
  257. package/src/telemetry.ts +108 -0
  258. package/src/tools/aws-ops.ts +952 -0
  259. package/src/tools/azure-ops.ts +579 -0
  260. package/src/tools/file-ops.ts +593 -0
  261. package/src/tools/gcp-ops.ts +625 -0
  262. package/src/tools/git-ops.ts +773 -0
  263. package/src/tools/github-ops.ts +799 -0
  264. package/src/tools/helm-ops.ts +943 -0
  265. package/src/tools/index.ts +17 -0
  266. package/src/tools/k8s-ops.ts +819 -0
  267. package/src/tools/schemas/converter.ts +184 -0
  268. package/src/tools/schemas/devops.ts +612 -0
  269. package/src/tools/schemas/index.ts +73 -0
  270. package/src/tools/schemas/standard.ts +1144 -0
  271. package/src/tools/schemas/types.ts +705 -0
  272. package/src/tools/terraform-ops.ts +862 -0
  273. package/src/types/ambient.d.ts +193 -0
  274. package/src/types/config.ts +83 -0
  275. package/src/types/drift.ts +116 -0
  276. package/src/types/enterprise.ts +335 -0
  277. package/src/types/index.ts +20 -0
  278. package/src/types/plan.ts +44 -0
  279. package/src/types/request.ts +65 -0
  280. package/src/types/response.ts +54 -0
  281. package/src/types/service.ts +51 -0
  282. package/src/ui/App.tsx +997 -0
  283. package/src/ui/DeployPreview.tsx +169 -0
  284. package/src/ui/Header.tsx +68 -0
  285. package/src/ui/InputBox.tsx +350 -0
  286. package/src/ui/MessageList.tsx +585 -0
  287. package/src/ui/PermissionPrompt.tsx +151 -0
  288. package/src/ui/StatusBar.tsx +158 -0
  289. package/src/ui/ToolCallDisplay.tsx +409 -0
  290. package/src/ui/chat-ui.ts +853 -0
  291. package/src/ui/index.ts +33 -0
  292. package/src/ui/ink/index.ts +711 -0
  293. package/src/ui/streaming.ts +176 -0
  294. package/src/ui/types.ts +57 -0
  295. package/src/utils/analytics.ts +72 -0
  296. package/src/utils/cost-warning.ts +27 -0
  297. package/src/utils/env.ts +46 -0
  298. package/src/utils/errors.ts +69 -0
  299. package/src/utils/event-bus.ts +38 -0
  300. package/src/utils/index.ts +24 -0
  301. package/src/utils/logger.ts +171 -0
  302. package/src/utils/rate-limiter.ts +121 -0
  303. package/src/utils/service-auth.ts +49 -0
  304. package/src/utils/validation.ts +53 -0
  305. package/src/version.ts +4 -0
  306. package/src/watcher/index.ts +163 -0
  307. package/src/wizard/approval.ts +383 -0
  308. package/src/wizard/index.ts +25 -0
  309. package/src/wizard/prompts.ts +338 -0
  310. package/src/wizard/types.ts +171 -0
  311. package/src/wizard/ui.ts +556 -0
  312. package/src/wizard/wizard.ts +304 -0
  313. package/tsconfig.json +24 -0
@@ -0,0 +1,1560 @@
1
+ /**
2
+ * Generate Kubernetes Manifests Command
3
+ *
4
+ * Interactive wizard for generating K8s resources
5
+ *
6
+ * Usage: nimbus generate k8s [options]
7
+ */
8
+
9
+ import { logger } from '../utils';
10
+ import { RestClient } from '../clients';
11
+ import {
12
+ createWizard,
13
+ ui,
14
+ select,
15
+ multiSelect,
16
+ confirm,
17
+ input,
18
+ pathInput,
19
+ type WizardStep,
20
+ type StepResult,
21
+ } from '../wizard';
22
+
23
+ // Generator Service client
24
+ const generatorUrl = process.env.GENERATOR_SERVICE_URL || 'http://localhost:3003';
25
+ const generatorClient = new RestClient(generatorUrl);
26
+
27
+ /**
28
+ * Workload types for Kubernetes
29
+ */
30
+ export type K8sWorkloadType = 'deployment' | 'statefulset' | 'daemonset' | 'job' | 'cronjob';
31
+
32
+ /**
33
+ * Service types for Kubernetes
34
+ */
35
+ export type K8sServiceType = 'ClusterIP' | 'NodePort' | 'LoadBalancer' | 'None';
36
+
37
+ /**
38
+ * Command options from CLI arguments
39
+ */
40
+ export interface GenerateK8sOptions {
41
+ workloadType?: K8sWorkloadType;
42
+ namespace?: string;
43
+ name?: string;
44
+ image?: string;
45
+ replicas?: number;
46
+ port?: number;
47
+ serviceType?: K8sServiceType;
48
+ output?: string;
49
+ nonInteractive?: boolean;
50
+ includeIngress?: boolean;
51
+ includeHpa?: boolean;
52
+ includePdb?: boolean;
53
+ includeConfigMap?: boolean;
54
+ includeSecret?: boolean;
55
+ cpuRequest?: string;
56
+ cpuLimit?: string;
57
+ memoryRequest?: string;
58
+ memoryLimit?: string;
59
+ }
60
+
61
+ /**
62
+ * Wizard context for K8s generation
63
+ */
64
+ export interface K8sWizardContext {
65
+ // Workload configuration
66
+ workloadType?: K8sWorkloadType;
67
+ name?: string;
68
+ namespace?: string;
69
+ image?: string;
70
+ imageTag?: string;
71
+
72
+ // Replica configuration
73
+ replicas?: number;
74
+ minReplicas?: number;
75
+ maxReplicas?: number;
76
+ targetCPUUtilization?: number;
77
+
78
+ // Port & Service configuration
79
+ containerPort?: number;
80
+ serviceType?: K8sServiceType;
81
+ servicePort?: number;
82
+
83
+ // Resource limits
84
+ cpuRequest?: string;
85
+ cpuLimit?: string;
86
+ memoryRequest?: string;
87
+ memoryLimit?: string;
88
+
89
+ // Additional resources
90
+ includeService?: boolean;
91
+ includeIngress?: boolean;
92
+ includeHpa?: boolean;
93
+ includePdb?: boolean;
94
+ includeConfigMap?: boolean;
95
+ includeSecret?: boolean;
96
+
97
+ // Ingress configuration
98
+ ingressHost?: string;
99
+ ingressPath?: string;
100
+ ingressTls?: boolean;
101
+
102
+ // PDB configuration
103
+ minAvailable?: number | string;
104
+
105
+ // Job/CronJob specific
106
+ schedule?: string;
107
+ backoffLimit?: number;
108
+ completions?: number;
109
+ parallelism?: number;
110
+
111
+ // Health checks
112
+ includeProbes?: boolean;
113
+ livenessPath?: string;
114
+ readinessPath?: string;
115
+
116
+ // Output
117
+ outputPath?: string;
118
+ outputFormat?: 'multiple' | 'single' | 'kustomize';
119
+ generatedFiles?: string[];
120
+ }
121
+
122
+ /**
123
+ * Run the generate k8s command
124
+ */
125
+ export async function generateK8sCommand(options: GenerateK8sOptions = {}): Promise<void> {
126
+ logger.info('Starting Kubernetes manifest generation wizard');
127
+
128
+ // Non-interactive mode
129
+ if (options.nonInteractive) {
130
+ await runNonInteractive(options);
131
+ return;
132
+ }
133
+
134
+ // Interactive wizard mode
135
+ const wizard = createWizard<K8sWizardContext>({
136
+ title: 'nimbus generate k8s',
137
+ description: 'Generate Kubernetes manifests for your application',
138
+ initialContext: {
139
+ workloadType: options.workloadType,
140
+ namespace: options.namespace,
141
+ name: options.name,
142
+ image: options.image,
143
+ replicas: options.replicas,
144
+ containerPort: options.port,
145
+ serviceType: options.serviceType,
146
+ outputPath: options.output,
147
+ includeIngress: options.includeIngress,
148
+ includeHpa: options.includeHpa,
149
+ includePdb: options.includePdb,
150
+ includeConfigMap: options.includeConfigMap,
151
+ includeSecret: options.includeSecret,
152
+ cpuRequest: options.cpuRequest,
153
+ cpuLimit: options.cpuLimit,
154
+ memoryRequest: options.memoryRequest,
155
+ memoryLimit: options.memoryLimit,
156
+ },
157
+ steps: createWizardSteps(),
158
+ onEvent: event => {
159
+ logger.debug('Wizard event', { type: event.type });
160
+ },
161
+ });
162
+
163
+ const result = await wizard.run();
164
+
165
+ if (result.success) {
166
+ ui.newLine();
167
+ ui.box({
168
+ title: 'Complete!',
169
+ content: [
170
+ 'Your Kubernetes manifests have been generated.',
171
+ '',
172
+ 'Generated files:',
173
+ ...(result.context.generatedFiles?.map(f => ` - ${f}`) || [' - (manifests generated)']),
174
+ '',
175
+ 'Next steps:',
176
+ ` 1. Review the generated files in ${result.context.outputPath}`,
177
+ ' 2. Customize values as needed for your environment',
178
+ ' 3. Run "kubectl apply -f <path>" or "nimbus apply k8s <path>"',
179
+ ],
180
+ style: 'rounded',
181
+ borderColor: 'green',
182
+ padding: 1,
183
+ });
184
+ } else {
185
+ ui.error(`Wizard failed: ${result.error?.message || 'Unknown error'}`);
186
+ process.exit(1);
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Create wizard steps
192
+ */
193
+ function createWizardSteps(): WizardStep<K8sWizardContext>[] {
194
+ return [
195
+ // Step 1: Workload Type Selection
196
+ {
197
+ id: 'workload-type',
198
+ title: 'Workload Type',
199
+ description: 'Select the type of Kubernetes workload to generate',
200
+ execute: workloadTypeStep,
201
+ },
202
+
203
+ // Step 2: Basic Configuration
204
+ {
205
+ id: 'basic-config',
206
+ title: 'Basic Configuration',
207
+ description: 'Configure name, namespace, and image',
208
+ execute: basicConfigStep,
209
+ },
210
+
211
+ // Step 3: Replica Configuration (not for DaemonSet or Job)
212
+ {
213
+ id: 'replicas',
214
+ title: 'Replica Configuration',
215
+ description: 'Configure replicas and scaling options',
216
+ condition: ctx => !['daemonset', 'job'].includes(ctx.workloadType || ''),
217
+ execute: replicaConfigStep,
218
+ },
219
+
220
+ // Step 4: Job/CronJob Configuration
221
+ {
222
+ id: 'job-config',
223
+ title: 'Job Configuration',
224
+ description: 'Configure job-specific settings',
225
+ condition: ctx => ['job', 'cronjob'].includes(ctx.workloadType || ''),
226
+ execute: jobConfigStep,
227
+ },
228
+
229
+ // Step 5: Port & Service Configuration
230
+ {
231
+ id: 'service-config',
232
+ title: 'Port & Service Configuration',
233
+ description: 'Configure container ports and service exposure',
234
+ execute: serviceConfigStep,
235
+ },
236
+
237
+ // Step 6: Resource Limits
238
+ {
239
+ id: 'resources',
240
+ title: 'Resource Limits',
241
+ description: 'Configure CPU and memory requests/limits',
242
+ execute: resourceLimitsStep,
243
+ },
244
+
245
+ // Step 7: Additional Resources
246
+ {
247
+ id: 'additional-resources',
248
+ title: 'Additional Resources',
249
+ description: 'Select additional Kubernetes resources to generate',
250
+ execute: additionalResourcesStep,
251
+ },
252
+
253
+ // Step 8: Health Checks
254
+ {
255
+ id: 'health-checks',
256
+ title: 'Health Checks',
257
+ description: 'Configure liveness and readiness probes',
258
+ condition: ctx => !['job', 'cronjob'].includes(ctx.workloadType || ''),
259
+ execute: healthChecksStep,
260
+ },
261
+
262
+ // Step 9: Output Configuration
263
+ {
264
+ id: 'output',
265
+ title: 'Output Configuration',
266
+ description: 'Configure where and how to save the manifests',
267
+ execute: outputConfigStep,
268
+ },
269
+
270
+ // Step 10: Generate
271
+ {
272
+ id: 'generate',
273
+ title: 'Generate Manifests',
274
+ description: 'Generating your Kubernetes manifests...',
275
+ execute: generateStep,
276
+ },
277
+ ];
278
+ }
279
+
280
+ /**
281
+ * Step 1: Workload Type Selection
282
+ */
283
+ async function workloadTypeStep(ctx: K8sWizardContext): Promise<StepResult> {
284
+ const workloadType = await select<K8sWorkloadType>({
285
+ message: 'Select workload type:',
286
+ options: [
287
+ {
288
+ value: 'deployment',
289
+ label: 'Deployment',
290
+ description: 'Stateless application with rolling updates (most common)',
291
+ },
292
+ {
293
+ value: 'statefulset',
294
+ label: 'StatefulSet',
295
+ description: 'Stateful application with stable network identity and storage',
296
+ },
297
+ {
298
+ value: 'daemonset',
299
+ label: 'DaemonSet',
300
+ description: 'Run a pod on every node (e.g., monitoring agents)',
301
+ },
302
+ {
303
+ value: 'job',
304
+ label: 'Job',
305
+ description: 'Run a task to completion',
306
+ },
307
+ {
308
+ value: 'cronjob',
309
+ label: 'CronJob',
310
+ description: 'Run a job on a schedule',
311
+ },
312
+ ],
313
+ defaultValue: ctx.workloadType || 'deployment',
314
+ });
315
+
316
+ if (!workloadType) {
317
+ return { success: false, error: 'No workload type selected' };
318
+ }
319
+
320
+ return {
321
+ success: true,
322
+ data: { workloadType },
323
+ };
324
+ }
325
+
326
+ /**
327
+ * Step 2: Basic Configuration
328
+ */
329
+ async function basicConfigStep(ctx: K8sWizardContext): Promise<StepResult> {
330
+ // Application name
331
+ const name = await input({
332
+ message: 'Application name:',
333
+ defaultValue: ctx.name || 'my-app',
334
+ validate: value => {
335
+ if (!value) {
336
+ return 'Name is required';
337
+ }
338
+ if (!/^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/.test(value)) {
339
+ return 'Name must be lowercase alphanumeric with dashes only';
340
+ }
341
+ return true;
342
+ },
343
+ });
344
+
345
+ if (!name) {
346
+ return { success: false, error: 'Name is required' };
347
+ }
348
+
349
+ // Namespace
350
+ ui.newLine();
351
+ const namespace = await input({
352
+ message: 'Namespace:',
353
+ defaultValue: ctx.namespace || 'default',
354
+ validate: value => {
355
+ if (!value) {
356
+ return 'Namespace is required';
357
+ }
358
+ if (!/^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/.test(value)) {
359
+ return 'Namespace must be lowercase alphanumeric with dashes only';
360
+ }
361
+ return true;
362
+ },
363
+ });
364
+
365
+ if (!namespace) {
366
+ return { success: false, error: 'Namespace is required' };
367
+ }
368
+
369
+ // Container image
370
+ ui.newLine();
371
+ const image = await input({
372
+ message: 'Container image (e.g., nginx:latest, myregistry/myapp):',
373
+ defaultValue: ctx.image || '',
374
+ validate: value => {
375
+ if (!value) {
376
+ return 'Image is required';
377
+ }
378
+ return true;
379
+ },
380
+ });
381
+
382
+ if (!image) {
383
+ return { success: false, error: 'Image is required' };
384
+ }
385
+
386
+ // Parse image and tag
387
+ const imageParts = image.split(':');
388
+ const imageBase = imageParts[0];
389
+ const imageTag = imageParts[1] || 'latest';
390
+
391
+ return {
392
+ success: true,
393
+ data: {
394
+ name,
395
+ namespace,
396
+ image: imageBase,
397
+ imageTag,
398
+ },
399
+ };
400
+ }
401
+
402
+ /**
403
+ * Step 3: Replica Configuration
404
+ */
405
+ async function replicaConfigStep(ctx: K8sWizardContext): Promise<StepResult> {
406
+ const replicas = await input({
407
+ message: 'Number of replicas:',
408
+ defaultValue: String(ctx.replicas || 2),
409
+ validate: value => {
410
+ const num = parseInt(value, 10);
411
+ if (isNaN(num) || num < 1) {
412
+ return 'Must be a positive number';
413
+ }
414
+ return true;
415
+ },
416
+ });
417
+
418
+ if (!replicas) {
419
+ return { success: false, error: 'Replicas required' };
420
+ }
421
+
422
+ // Ask about HPA
423
+ ui.newLine();
424
+ const includeHpa =
425
+ ctx.includeHpa ??
426
+ (await confirm({
427
+ message: 'Include Horizontal Pod Autoscaler (HPA)?',
428
+ defaultValue: false,
429
+ }));
430
+
431
+ let minReplicas: number | undefined;
432
+ let maxReplicas: number | undefined;
433
+ let targetCPUUtilization: number | undefined;
434
+
435
+ if (includeHpa) {
436
+ ui.newLine();
437
+ const min = await input({
438
+ message: 'Minimum replicas:',
439
+ defaultValue: String(ctx.minReplicas || Math.max(1, parseInt(replicas, 10) - 1)),
440
+ validate: value => {
441
+ const num = parseInt(value, 10);
442
+ if (isNaN(num) || num < 1) {
443
+ return 'Must be a positive number';
444
+ }
445
+ return true;
446
+ },
447
+ });
448
+ minReplicas = parseInt(min || '1', 10);
449
+
450
+ const max = await input({
451
+ message: 'Maximum replicas:',
452
+ defaultValue: String(ctx.maxReplicas || parseInt(replicas, 10) * 2),
453
+ validate: value => {
454
+ const num = parseInt(value, 10);
455
+ if (isNaN(num) || num < minReplicas!) {
456
+ return `Must be >= ${minReplicas}`;
457
+ }
458
+ return true;
459
+ },
460
+ });
461
+ maxReplicas = parseInt(max || '4', 10);
462
+
463
+ const cpu = await input({
464
+ message: 'Target CPU utilization (%):',
465
+ defaultValue: String(ctx.targetCPUUtilization || 70),
466
+ validate: value => {
467
+ const num = parseInt(value, 10);
468
+ if (isNaN(num) || num < 1 || num > 100) {
469
+ return 'Must be between 1 and 100';
470
+ }
471
+ return true;
472
+ },
473
+ });
474
+ targetCPUUtilization = parseInt(cpu || '70', 10);
475
+ }
476
+
477
+ return {
478
+ success: true,
479
+ data: {
480
+ replicas: parseInt(replicas, 10),
481
+ includeHpa,
482
+ minReplicas,
483
+ maxReplicas,
484
+ targetCPUUtilization,
485
+ },
486
+ };
487
+ }
488
+
489
+ /**
490
+ * Step 4: Job/CronJob Configuration
491
+ */
492
+ async function jobConfigStep(ctx: K8sWizardContext): Promise<StepResult> {
493
+ let schedule: string | undefined;
494
+
495
+ if (ctx.workloadType === 'cronjob') {
496
+ const scheduleInput = await input({
497
+ message: 'Cron schedule (e.g., "*/5 * * * *" for every 5 minutes):',
498
+ defaultValue: ctx.schedule || '0 * * * *',
499
+ validate: value => {
500
+ if (!value) {
501
+ return 'Schedule is required for CronJob';
502
+ }
503
+ // Basic cron validation (5 fields)
504
+ const parts = value.trim().split(/\s+/);
505
+ if (parts.length !== 5) {
506
+ return 'Schedule must have 5 fields (min hour day month weekday)';
507
+ }
508
+ return true;
509
+ },
510
+ });
511
+ schedule = scheduleInput;
512
+ }
513
+
514
+ // Backoff limit
515
+ ui.newLine();
516
+ const backoffInput = await input({
517
+ message: 'Backoff limit (retries on failure):',
518
+ defaultValue: String(ctx.backoffLimit || 6),
519
+ validate: value => {
520
+ const num = parseInt(value, 10);
521
+ if (isNaN(num) || num < 0) {
522
+ return 'Must be a non-negative number';
523
+ }
524
+ return true;
525
+ },
526
+ });
527
+ const backoffLimit = parseInt(backoffInput || '6', 10);
528
+
529
+ // Completions
530
+ ui.newLine();
531
+ const completionsInput = await input({
532
+ message: 'Number of completions (total successful pods):',
533
+ defaultValue: String(ctx.completions || 1),
534
+ validate: value => {
535
+ const num = parseInt(value, 10);
536
+ if (isNaN(num) || num < 1) {
537
+ return 'Must be a positive number';
538
+ }
539
+ return true;
540
+ },
541
+ });
542
+ const completions = parseInt(completionsInput || '1', 10);
543
+
544
+ // Parallelism
545
+ ui.newLine();
546
+ const parallelismInput = await input({
547
+ message: 'Parallelism (concurrent pods):',
548
+ defaultValue: String(ctx.parallelism || 1),
549
+ validate: value => {
550
+ const num = parseInt(value, 10);
551
+ if (isNaN(num) || num < 1) {
552
+ return 'Must be a positive number';
553
+ }
554
+ return true;
555
+ },
556
+ });
557
+ const parallelism = parseInt(parallelismInput || '1', 10);
558
+
559
+ return {
560
+ success: true,
561
+ data: {
562
+ schedule,
563
+ backoffLimit,
564
+ completions,
565
+ parallelism,
566
+ },
567
+ };
568
+ }
569
+
570
+ /**
571
+ * Step 5: Port & Service Configuration
572
+ */
573
+ async function serviceConfigStep(ctx: K8sWizardContext): Promise<StepResult> {
574
+ // Container port
575
+ const portInput = await input({
576
+ message: 'Container port:',
577
+ defaultValue: String(ctx.containerPort || 8080),
578
+ validate: value => {
579
+ const num = parseInt(value, 10);
580
+ if (isNaN(num) || num < 1 || num > 65535) {
581
+ return 'Must be between 1 and 65535';
582
+ }
583
+ return true;
584
+ },
585
+ });
586
+
587
+ if (!portInput) {
588
+ return { success: false, error: 'Port is required' };
589
+ }
590
+
591
+ const containerPort = parseInt(portInput, 10);
592
+
593
+ // Service exposure (not for Jobs)
594
+ let includeService = false;
595
+ let serviceType: K8sServiceType = 'ClusterIP';
596
+ let servicePort = containerPort;
597
+
598
+ if (!['job', 'cronjob'].includes(ctx.workloadType || '')) {
599
+ ui.newLine();
600
+ includeService = await confirm({
601
+ message: 'Create a Service to expose this workload?',
602
+ defaultValue: true,
603
+ });
604
+
605
+ if (includeService) {
606
+ ui.newLine();
607
+ serviceType =
608
+ (await select<K8sServiceType>({
609
+ message: 'Service type:',
610
+ options: [
611
+ {
612
+ value: 'ClusterIP',
613
+ label: 'ClusterIP',
614
+ description: 'Internal cluster access only (default)',
615
+ },
616
+ {
617
+ value: 'NodePort',
618
+ label: 'NodePort',
619
+ description: "Expose on each node's IP at a static port",
620
+ },
621
+ {
622
+ value: 'LoadBalancer',
623
+ label: 'LoadBalancer',
624
+ description: 'External load balancer (cloud provider)',
625
+ },
626
+ ],
627
+ defaultValue: ctx.serviceType || 'ClusterIP',
628
+ })) || 'ClusterIP';
629
+
630
+ const servicePortInput = await input({
631
+ message: 'Service port:',
632
+ defaultValue: String(ctx.servicePort || containerPort),
633
+ validate: value => {
634
+ const num = parseInt(value, 10);
635
+ if (isNaN(num) || num < 1 || num > 65535) {
636
+ return 'Must be between 1 and 65535';
637
+ }
638
+ return true;
639
+ },
640
+ });
641
+ servicePort = parseInt(servicePortInput || String(containerPort), 10);
642
+ }
643
+ }
644
+
645
+ return {
646
+ success: true,
647
+ data: {
648
+ containerPort,
649
+ includeService,
650
+ serviceType,
651
+ servicePort,
652
+ },
653
+ };
654
+ }
655
+
656
+ /**
657
+ * Step 6: Resource Limits
658
+ */
659
+ async function resourceLimitsStep(ctx: K8sWizardContext): Promise<StepResult> {
660
+ const setLimits = await confirm({
661
+ message: 'Configure resource requests and limits? (recommended for production)',
662
+ defaultValue: true,
663
+ });
664
+
665
+ if (!setLimits) {
666
+ return { success: true, data: {} };
667
+ }
668
+
669
+ ui.newLine();
670
+ ui.info('Resource requests (guaranteed resources):');
671
+
672
+ const cpuRequest = await input({
673
+ message: 'CPU request (e.g., 100m, 0.5):',
674
+ defaultValue: ctx.cpuRequest || '100m',
675
+ });
676
+
677
+ const memoryRequest = await input({
678
+ message: 'Memory request (e.g., 128Mi, 1Gi):',
679
+ defaultValue: ctx.memoryRequest || '128Mi',
680
+ });
681
+
682
+ ui.newLine();
683
+ ui.info('Resource limits (maximum allowed):');
684
+
685
+ const cpuLimit = await input({
686
+ message: 'CPU limit (e.g., 500m, 1):',
687
+ defaultValue: ctx.cpuLimit || '500m',
688
+ });
689
+
690
+ const memoryLimit = await input({
691
+ message: 'Memory limit (e.g., 256Mi, 2Gi):',
692
+ defaultValue: ctx.memoryLimit || '256Mi',
693
+ });
694
+
695
+ return {
696
+ success: true,
697
+ data: {
698
+ cpuRequest,
699
+ cpuLimit,
700
+ memoryRequest,
701
+ memoryLimit,
702
+ },
703
+ };
704
+ }
705
+
706
+ /**
707
+ * Step 7: Additional Resources
708
+ */
709
+ async function additionalResourcesStep(ctx: K8sWizardContext): Promise<StepResult> {
710
+ const resourceOptions = [
711
+ { value: 'configmap', label: 'ConfigMap', description: 'Environment configuration' },
712
+ { value: 'secret', label: 'Secret', description: 'Sensitive data (passwords, tokens)' },
713
+ ];
714
+
715
+ // Only show Ingress for services
716
+ if (ctx.includeService) {
717
+ resourceOptions.push({
718
+ value: 'ingress',
719
+ label: 'Ingress',
720
+ description: 'HTTP/HTTPS routing (requires ingress controller)',
721
+ });
722
+ }
723
+
724
+ // Only show PDB for Deployments/StatefulSets
725
+ if (['deployment', 'statefulset'].includes(ctx.workloadType || '')) {
726
+ resourceOptions.push({
727
+ value: 'pdb',
728
+ label: 'PodDisruptionBudget',
729
+ description: 'Ensure availability during disruptions',
730
+ });
731
+ }
732
+
733
+ const selectedResources = (await multiSelect({
734
+ message: 'Select additional resources to generate:',
735
+ options: resourceOptions,
736
+ required: false,
737
+ })) as string[];
738
+
739
+ const includeConfigMap = selectedResources.includes('configmap') || ctx.includeConfigMap;
740
+ const includeSecret = selectedResources.includes('secret') || ctx.includeSecret;
741
+ const includeIngress = selectedResources.includes('ingress') || ctx.includeIngress;
742
+ const includePdb = selectedResources.includes('pdb') || ctx.includePdb;
743
+
744
+ // Ingress configuration
745
+ let ingressHost: string | undefined;
746
+ let ingressPath: string | undefined;
747
+ let ingressTls = false;
748
+
749
+ if (includeIngress) {
750
+ ui.newLine();
751
+ ui.info('Ingress Configuration:');
752
+
753
+ ingressHost = await input({
754
+ message: 'Hostname (e.g., app.example.com):',
755
+ defaultValue: ctx.ingressHost || `${ctx.name}.example.com`,
756
+ });
757
+
758
+ ingressPath = await input({
759
+ message: 'Path:',
760
+ defaultValue: ctx.ingressPath || '/',
761
+ });
762
+
763
+ ingressTls = await confirm({
764
+ message: 'Enable TLS?',
765
+ defaultValue: ctx.ingressTls ?? true,
766
+ });
767
+ }
768
+
769
+ // PDB configuration
770
+ let minAvailable: number | string | undefined;
771
+
772
+ if (includePdb) {
773
+ ui.newLine();
774
+ const minAvailableInput = await input({
775
+ message: 'Minimum available pods (number or percentage like "50%"):',
776
+ defaultValue: String(ctx.minAvailable || 1),
777
+ });
778
+ minAvailable = minAvailableInput?.includes('%')
779
+ ? minAvailableInput
780
+ : parseInt(minAvailableInput || '1', 10);
781
+ }
782
+
783
+ return {
784
+ success: true,
785
+ data: {
786
+ includeConfigMap,
787
+ includeSecret,
788
+ includeIngress,
789
+ includePdb,
790
+ ingressHost,
791
+ ingressPath,
792
+ ingressTls,
793
+ minAvailable,
794
+ },
795
+ };
796
+ }
797
+
798
+ /**
799
+ * Step 8: Health Checks
800
+ */
801
+ async function healthChecksStep(ctx: K8sWizardContext): Promise<StepResult> {
802
+ const includeProbes = await confirm({
803
+ message: 'Configure health check probes? (recommended for production)',
804
+ defaultValue: true,
805
+ });
806
+
807
+ if (!includeProbes) {
808
+ return { success: true, data: { includeProbes: false } };
809
+ }
810
+
811
+ ui.newLine();
812
+ const livenessPath = await input({
813
+ message: 'Liveness probe path (e.g., /healthz):',
814
+ defaultValue: ctx.livenessPath || '/healthz',
815
+ });
816
+
817
+ const readinessPath = await input({
818
+ message: 'Readiness probe path (e.g., /ready):',
819
+ defaultValue: ctx.readinessPath || '/ready',
820
+ });
821
+
822
+ return {
823
+ success: true,
824
+ data: {
825
+ includeProbes,
826
+ livenessPath,
827
+ readinessPath,
828
+ },
829
+ };
830
+ }
831
+
832
+ /**
833
+ * Step 9: Output Configuration
834
+ */
835
+ async function outputConfigStep(ctx: K8sWizardContext): Promise<StepResult> {
836
+ const outputFormat = await select<'multiple' | 'single' | 'kustomize'>({
837
+ message: 'Output format:',
838
+ options: [
839
+ {
840
+ value: 'multiple',
841
+ label: 'Multiple files',
842
+ description: 'One file per resource (deployment.yaml, service.yaml, etc.)',
843
+ },
844
+ {
845
+ value: 'single',
846
+ label: 'Single file',
847
+ description: 'All resources in one file with document separators',
848
+ },
849
+ {
850
+ value: 'kustomize',
851
+ label: 'Kustomize structure',
852
+ description: 'Base with kustomization.yaml for overlays',
853
+ },
854
+ ],
855
+ defaultValue: ctx.outputFormat || 'multiple',
856
+ });
857
+
858
+ ui.newLine();
859
+ const outputPath = await pathInput('Output directory:', ctx.outputPath || `./${ctx.name}-k8s`);
860
+
861
+ if (!outputPath) {
862
+ return { success: false, error: 'Output path is required' };
863
+ }
864
+
865
+ return {
866
+ success: true,
867
+ data: {
868
+ outputFormat,
869
+ outputPath,
870
+ },
871
+ };
872
+ }
873
+
874
+ /**
875
+ * Step 10: Generate Manifests
876
+ */
877
+ async function generateStep(ctx: K8sWizardContext): Promise<StepResult> {
878
+ ui.startSpinner({ message: 'Generating Kubernetes manifests...' });
879
+
880
+ try {
881
+ // Build the generation request
882
+ const request = buildGenerationRequest(ctx);
883
+
884
+ // Call generator service
885
+ const response = await generatorClient.post<{
886
+ success: boolean;
887
+ files: Array<{ name: string; content: string; path: string }>;
888
+ error?: string;
889
+ }>('/api/generate/k8s', request);
890
+
891
+ if (!response.success || !response.data?.success) {
892
+ // If generator service is not available, generate locally
893
+ ui.stopSpinnerFail('Generator service unavailable');
894
+ ui.info('Generating manifests locally...');
895
+
896
+ const files = generateManifestsLocally(ctx);
897
+ await writeFilesToDisk(files, ctx.outputPath!);
898
+
899
+ ui.newLine();
900
+ ui.success(`Generated ${files.length} manifest(s)`);
901
+
902
+ return {
903
+ success: true,
904
+ data: {
905
+ generatedFiles: files.map(f => f.path),
906
+ },
907
+ };
908
+ }
909
+
910
+ // Write files from generator service response
911
+ await writeFilesToDisk(response.data.files, ctx.outputPath!);
912
+
913
+ ui.stopSpinnerSuccess(`Generated ${response.data.files.length} manifest(s)`);
914
+
915
+ return {
916
+ success: true,
917
+ data: {
918
+ generatedFiles: response.data.files.map(f => f.path),
919
+ },
920
+ };
921
+ } catch (error: any) {
922
+ // Fall back to local generation
923
+ ui.stopSpinnerFail('Generator service error');
924
+ ui.info('Generating manifests locally...');
925
+
926
+ try {
927
+ const files = generateManifestsLocally(ctx);
928
+ await writeFilesToDisk(files, ctx.outputPath!);
929
+
930
+ ui.newLine();
931
+ ui.success(`Generated ${files.length} manifest(s)`);
932
+
933
+ return {
934
+ success: true,
935
+ data: {
936
+ generatedFiles: files.map(f => f.path),
937
+ },
938
+ };
939
+ } catch (localError: any) {
940
+ return {
941
+ success: false,
942
+ error: localError.message,
943
+ };
944
+ }
945
+ }
946
+ }
947
+
948
+ /**
949
+ * Build the generation request from context
950
+ */
951
+ function buildGenerationRequest(ctx: K8sWizardContext): Record<string, unknown> {
952
+ return {
953
+ workloadType: ctx.workloadType,
954
+ name: ctx.name,
955
+ namespace: ctx.namespace,
956
+ image: ctx.image,
957
+ imageTag: ctx.imageTag,
958
+ replicas: ctx.replicas,
959
+ containerPort: ctx.containerPort,
960
+ serviceType: ctx.serviceType,
961
+ servicePort: ctx.servicePort,
962
+ cpuRequest: ctx.cpuRequest,
963
+ cpuLimit: ctx.cpuLimit,
964
+ memoryRequest: ctx.memoryRequest,
965
+ memoryLimit: ctx.memoryLimit,
966
+ includeService: ctx.includeService,
967
+ includeIngress: ctx.includeIngress,
968
+ includeHpa: ctx.includeHpa,
969
+ includePdb: ctx.includePdb,
970
+ includeConfigMap: ctx.includeConfigMap,
971
+ includeSecret: ctx.includeSecret,
972
+ includeProbes: ctx.includeProbes,
973
+ livenessPath: ctx.livenessPath,
974
+ readinessPath: ctx.readinessPath,
975
+ ingressHost: ctx.ingressHost,
976
+ ingressPath: ctx.ingressPath,
977
+ ingressTls: ctx.ingressTls,
978
+ minAvailable: ctx.minAvailable,
979
+ minReplicas: ctx.minReplicas,
980
+ maxReplicas: ctx.maxReplicas,
981
+ targetCPUUtilization: ctx.targetCPUUtilization,
982
+ schedule: ctx.schedule,
983
+ backoffLimit: ctx.backoffLimit,
984
+ completions: ctx.completions,
985
+ parallelism: ctx.parallelism,
986
+ outputFormat: ctx.outputFormat,
987
+ outputPath: ctx.outputPath,
988
+ };
989
+ }
990
+
991
+ /**
992
+ * Generate manifests locally when service is unavailable
993
+ */
994
+ function generateManifestsLocally(
995
+ ctx: K8sWizardContext
996
+ ): Array<{ name: string; content: string; path: string }> {
997
+ const files: Array<{ name: string; content: string; path: string }> = [];
998
+ const labels: Record<string, string> = {
999
+ 'app.kubernetes.io/name': ctx.name || 'unnamed',
1000
+ 'app.kubernetes.io/instance': ctx.name || 'unnamed',
1001
+ 'app.kubernetes.io/managed-by': 'nimbus',
1002
+ };
1003
+
1004
+ // Generate main workload
1005
+ const workloadManifest = generateWorkloadManifest(ctx, labels);
1006
+ files.push({
1007
+ name: `${ctx.workloadType}.yaml`,
1008
+ content: workloadManifest,
1009
+ path: `${ctx.outputPath}/${ctx.workloadType}.yaml`,
1010
+ });
1011
+
1012
+ // Generate Service
1013
+ if (ctx.includeService) {
1014
+ files.push({
1015
+ name: 'service.yaml',
1016
+ content: generateServiceManifest(ctx, labels),
1017
+ path: `${ctx.outputPath}/service.yaml`,
1018
+ });
1019
+ }
1020
+
1021
+ // Generate Ingress
1022
+ if (ctx.includeIngress) {
1023
+ files.push({
1024
+ name: 'ingress.yaml',
1025
+ content: generateIngressManifest(ctx, labels),
1026
+ path: `${ctx.outputPath}/ingress.yaml`,
1027
+ });
1028
+ }
1029
+
1030
+ // Generate HPA
1031
+ if (ctx.includeHpa) {
1032
+ files.push({
1033
+ name: 'hpa.yaml',
1034
+ content: generateHpaManifest(ctx, labels),
1035
+ path: `${ctx.outputPath}/hpa.yaml`,
1036
+ });
1037
+ }
1038
+
1039
+ // Generate PDB
1040
+ if (ctx.includePdb) {
1041
+ files.push({
1042
+ name: 'pdb.yaml',
1043
+ content: generatePdbManifest(ctx, labels),
1044
+ path: `${ctx.outputPath}/pdb.yaml`,
1045
+ });
1046
+ }
1047
+
1048
+ // Generate ConfigMap
1049
+ if (ctx.includeConfigMap) {
1050
+ files.push({
1051
+ name: 'configmap.yaml',
1052
+ content: generateConfigMapManifest(ctx, labels),
1053
+ path: `${ctx.outputPath}/configmap.yaml`,
1054
+ });
1055
+ }
1056
+
1057
+ // Generate Secret
1058
+ if (ctx.includeSecret) {
1059
+ files.push({
1060
+ name: 'secret.yaml',
1061
+ content: generateSecretManifest(ctx, labels),
1062
+ path: `${ctx.outputPath}/secret.yaml`,
1063
+ });
1064
+ }
1065
+
1066
+ return files;
1067
+ }
1068
+
1069
+ /**
1070
+ * Generate main workload manifest
1071
+ */
1072
+ function generateWorkloadManifest(ctx: K8sWizardContext, labels: Record<string, string>): string {
1073
+ const { workloadType, name, namespace, image, imageTag, replicas, containerPort } = ctx;
1074
+
1075
+ // Build container spec
1076
+ const containerSpec: Record<string, unknown> = {
1077
+ name,
1078
+ image: `${image}:${imageTag || 'latest'}`,
1079
+ ports: containerPort ? [{ containerPort }] : undefined,
1080
+ resources:
1081
+ ctx.cpuRequest || ctx.memoryRequest
1082
+ ? {
1083
+ requests: {
1084
+ ...(ctx.cpuRequest && { cpu: ctx.cpuRequest }),
1085
+ ...(ctx.memoryRequest && { memory: ctx.memoryRequest }),
1086
+ },
1087
+ limits: {
1088
+ ...(ctx.cpuLimit && { cpu: ctx.cpuLimit }),
1089
+ ...(ctx.memoryLimit && { memory: ctx.memoryLimit }),
1090
+ },
1091
+ }
1092
+ : undefined,
1093
+ };
1094
+
1095
+ // Add probes if configured
1096
+ if (ctx.includeProbes) {
1097
+ containerSpec.livenessProbe = {
1098
+ httpGet: {
1099
+ path: ctx.livenessPath || '/healthz',
1100
+ port: containerPort,
1101
+ },
1102
+ initialDelaySeconds: 10,
1103
+ periodSeconds: 10,
1104
+ };
1105
+ containerSpec.readinessProbe = {
1106
+ httpGet: {
1107
+ path: ctx.readinessPath || '/ready',
1108
+ port: containerPort,
1109
+ },
1110
+ initialDelaySeconds: 5,
1111
+ periodSeconds: 5,
1112
+ };
1113
+ }
1114
+
1115
+ // Add envFrom if ConfigMap or Secret
1116
+ const envFrom = [];
1117
+ if (ctx.includeConfigMap) {
1118
+ envFrom.push({ configMapRef: { name: `${name}-config` } });
1119
+ }
1120
+ if (ctx.includeSecret) {
1121
+ envFrom.push({ secretRef: { name: `${name}-secret` } });
1122
+ }
1123
+ if (envFrom.length > 0) {
1124
+ containerSpec.envFrom = envFrom;
1125
+ }
1126
+
1127
+ // Clean up undefined values
1128
+ Object.keys(containerSpec).forEach(key => {
1129
+ if (containerSpec[key] === undefined) {
1130
+ delete containerSpec[key];
1131
+ }
1132
+ });
1133
+
1134
+ const podSpec = {
1135
+ containers: [containerSpec],
1136
+ };
1137
+
1138
+ let manifest: Record<string, unknown>;
1139
+
1140
+ switch (workloadType) {
1141
+ case 'deployment':
1142
+ manifest = {
1143
+ apiVersion: 'apps/v1',
1144
+ kind: 'Deployment',
1145
+ metadata: { name, namespace, labels },
1146
+ spec: {
1147
+ replicas,
1148
+ selector: { matchLabels: { 'app.kubernetes.io/name': name } },
1149
+ template: {
1150
+ metadata: { labels },
1151
+ spec: podSpec,
1152
+ },
1153
+ },
1154
+ };
1155
+ break;
1156
+
1157
+ case 'statefulset':
1158
+ manifest = {
1159
+ apiVersion: 'apps/v1',
1160
+ kind: 'StatefulSet',
1161
+ metadata: { name, namespace, labels },
1162
+ spec: {
1163
+ replicas,
1164
+ serviceName: name,
1165
+ selector: { matchLabels: { 'app.kubernetes.io/name': name } },
1166
+ template: {
1167
+ metadata: { labels },
1168
+ spec: podSpec,
1169
+ },
1170
+ },
1171
+ };
1172
+ break;
1173
+
1174
+ case 'daemonset':
1175
+ manifest = {
1176
+ apiVersion: 'apps/v1',
1177
+ kind: 'DaemonSet',
1178
+ metadata: { name, namespace, labels },
1179
+ spec: {
1180
+ selector: { matchLabels: { 'app.kubernetes.io/name': name } },
1181
+ template: {
1182
+ metadata: { labels },
1183
+ spec: podSpec,
1184
+ },
1185
+ },
1186
+ };
1187
+ break;
1188
+
1189
+ case 'job':
1190
+ manifest = {
1191
+ apiVersion: 'batch/v1',
1192
+ kind: 'Job',
1193
+ metadata: { name, namespace, labels },
1194
+ spec: {
1195
+ backoffLimit: ctx.backoffLimit,
1196
+ completions: ctx.completions,
1197
+ parallelism: ctx.parallelism,
1198
+ template: {
1199
+ metadata: { labels },
1200
+ spec: {
1201
+ ...podSpec,
1202
+ restartPolicy: 'Never',
1203
+ },
1204
+ },
1205
+ },
1206
+ };
1207
+ break;
1208
+
1209
+ case 'cronjob':
1210
+ manifest = {
1211
+ apiVersion: 'batch/v1',
1212
+ kind: 'CronJob',
1213
+ metadata: { name, namespace, labels },
1214
+ spec: {
1215
+ schedule: ctx.schedule,
1216
+ jobTemplate: {
1217
+ spec: {
1218
+ backoffLimit: ctx.backoffLimit,
1219
+ template: {
1220
+ metadata: { labels },
1221
+ spec: {
1222
+ ...podSpec,
1223
+ restartPolicy: 'Never',
1224
+ },
1225
+ },
1226
+ },
1227
+ },
1228
+ },
1229
+ };
1230
+ break;
1231
+
1232
+ default:
1233
+ manifest = {};
1234
+ }
1235
+
1236
+ return toYaml(manifest);
1237
+ }
1238
+
1239
+ /**
1240
+ * Generate Service manifest
1241
+ */
1242
+ function generateServiceManifest(ctx: K8sWizardContext, labels: Record<string, string>): string {
1243
+ const manifest = {
1244
+ apiVersion: 'v1',
1245
+ kind: 'Service',
1246
+ metadata: {
1247
+ name: ctx.name,
1248
+ namespace: ctx.namespace,
1249
+ labels,
1250
+ },
1251
+ spec: {
1252
+ type: ctx.serviceType,
1253
+ selector: { 'app.kubernetes.io/name': ctx.name },
1254
+ ports: [
1255
+ {
1256
+ port: ctx.servicePort,
1257
+ targetPort: ctx.containerPort,
1258
+ protocol: 'TCP',
1259
+ },
1260
+ ],
1261
+ },
1262
+ };
1263
+
1264
+ return toYaml(manifest);
1265
+ }
1266
+
1267
+ /**
1268
+ * Generate Ingress manifest
1269
+ */
1270
+ function generateIngressManifest(ctx: K8sWizardContext, labels: Record<string, string>): string {
1271
+ const manifest: Record<string, unknown> = {
1272
+ apiVersion: 'networking.k8s.io/v1',
1273
+ kind: 'Ingress',
1274
+ metadata: {
1275
+ name: ctx.name,
1276
+ namespace: ctx.namespace,
1277
+ labels,
1278
+ annotations: {
1279
+ 'kubernetes.io/ingress.class': 'nginx',
1280
+ },
1281
+ },
1282
+ spec: {
1283
+ rules: [
1284
+ {
1285
+ host: ctx.ingressHost,
1286
+ http: {
1287
+ paths: [
1288
+ {
1289
+ path: ctx.ingressPath || '/',
1290
+ pathType: 'Prefix',
1291
+ backend: {
1292
+ service: {
1293
+ name: ctx.name,
1294
+ port: { number: ctx.servicePort },
1295
+ },
1296
+ },
1297
+ },
1298
+ ],
1299
+ },
1300
+ },
1301
+ ],
1302
+ },
1303
+ };
1304
+
1305
+ if (ctx.ingressTls) {
1306
+ (manifest.spec as Record<string, unknown>).tls = [
1307
+ {
1308
+ hosts: [ctx.ingressHost],
1309
+ secretName: `${ctx.name}-tls`,
1310
+ },
1311
+ ];
1312
+ }
1313
+
1314
+ return toYaml(manifest);
1315
+ }
1316
+
1317
+ /**
1318
+ * Generate HPA manifest
1319
+ */
1320
+ function generateHpaManifest(ctx: K8sWizardContext, labels: Record<string, string>): string {
1321
+ const manifest = {
1322
+ apiVersion: 'autoscaling/v2',
1323
+ kind: 'HorizontalPodAutoscaler',
1324
+ metadata: {
1325
+ name: ctx.name,
1326
+ namespace: ctx.namespace,
1327
+ labels,
1328
+ },
1329
+ spec: {
1330
+ scaleTargetRef: {
1331
+ apiVersion: 'apps/v1',
1332
+ kind: ctx.workloadType === 'statefulset' ? 'StatefulSet' : 'Deployment',
1333
+ name: ctx.name,
1334
+ },
1335
+ minReplicas: ctx.minReplicas,
1336
+ maxReplicas: ctx.maxReplicas,
1337
+ metrics: [
1338
+ {
1339
+ type: 'Resource',
1340
+ resource: {
1341
+ name: 'cpu',
1342
+ target: {
1343
+ type: 'Utilization',
1344
+ averageUtilization: ctx.targetCPUUtilization,
1345
+ },
1346
+ },
1347
+ },
1348
+ ],
1349
+ },
1350
+ };
1351
+
1352
+ return toYaml(manifest);
1353
+ }
1354
+
1355
+ /**
1356
+ * Generate PDB manifest
1357
+ */
1358
+ function generatePdbManifest(ctx: K8sWizardContext, labels: Record<string, string>): string {
1359
+ const manifest = {
1360
+ apiVersion: 'policy/v1',
1361
+ kind: 'PodDisruptionBudget',
1362
+ metadata: {
1363
+ name: ctx.name,
1364
+ namespace: ctx.namespace,
1365
+ labels,
1366
+ },
1367
+ spec: {
1368
+ minAvailable: ctx.minAvailable,
1369
+ selector: { matchLabels: { 'app.kubernetes.io/name': ctx.name } },
1370
+ },
1371
+ };
1372
+
1373
+ return toYaml(manifest);
1374
+ }
1375
+
1376
+ /**
1377
+ * Generate ConfigMap manifest
1378
+ */
1379
+ function generateConfigMapManifest(ctx: K8sWizardContext, labels: Record<string, string>): string {
1380
+ const manifest = {
1381
+ apiVersion: 'v1',
1382
+ kind: 'ConfigMap',
1383
+ metadata: {
1384
+ name: `${ctx.name}-config`,
1385
+ namespace: ctx.namespace,
1386
+ labels,
1387
+ },
1388
+ data: {
1389
+ // Placeholder values - user should customize
1390
+ APP_ENV: 'production',
1391
+ LOG_LEVEL: 'info',
1392
+ },
1393
+ };
1394
+
1395
+ return toYaml(manifest);
1396
+ }
1397
+
1398
+ /**
1399
+ * Generate Secret manifest
1400
+ */
1401
+ function generateSecretManifest(ctx: K8sWizardContext, labels: Record<string, string>): string {
1402
+ const manifest = {
1403
+ apiVersion: 'v1',
1404
+ kind: 'Secret',
1405
+ metadata: {
1406
+ name: `${ctx.name}-secret`,
1407
+ namespace: ctx.namespace,
1408
+ labels,
1409
+ },
1410
+ type: 'Opaque',
1411
+ stringData: {
1412
+ // Placeholder values - user should customize
1413
+ 'example-key': 'example-value',
1414
+ },
1415
+ };
1416
+
1417
+ return toYaml(manifest);
1418
+ }
1419
+
1420
+ /**
1421
+ * Convert object to YAML string
1422
+ */
1423
+ function toYaml(obj: Record<string, unknown>, indent = 0): string {
1424
+ const lines: string[] = [];
1425
+ const spaces = ' '.repeat(indent);
1426
+
1427
+ for (const [key, value] of Object.entries(obj)) {
1428
+ if (value === undefined || value === null) {
1429
+ continue;
1430
+ }
1431
+
1432
+ if (Array.isArray(value)) {
1433
+ if (value.length === 0) {
1434
+ continue;
1435
+ }
1436
+ lines.push(`${spaces}${key}:`);
1437
+ for (const item of value) {
1438
+ if (typeof item === 'object' && item !== null) {
1439
+ const itemYaml = toYaml(item as Record<string, unknown>, indent + 1);
1440
+ const itemLines = itemYaml.split('\n').filter(l => l.trim());
1441
+ lines.push(`${spaces}- ${itemLines[0].trim()}`);
1442
+ for (let i = 1; i < itemLines.length; i++) {
1443
+ lines.push(`${spaces} ${itemLines[i].trim()}`);
1444
+ }
1445
+ } else {
1446
+ lines.push(`${spaces}- ${item}`);
1447
+ }
1448
+ }
1449
+ } else if (typeof value === 'object') {
1450
+ lines.push(`${spaces}${key}:`);
1451
+ lines.push(toYaml(value as Record<string, unknown>, indent + 1));
1452
+ } else if (
1453
+ typeof value === 'string' &&
1454
+ (value.includes(':') || value.includes('#') || value.includes('\n'))
1455
+ ) {
1456
+ lines.push(`${spaces}${key}: "${value}"`);
1457
+ } else {
1458
+ lines.push(`${spaces}${key}: ${value}`);
1459
+ }
1460
+ }
1461
+
1462
+ return lines.join('\n');
1463
+ }
1464
+
1465
+ /**
1466
+ * Write files to disk
1467
+ */
1468
+ async function writeFilesToDisk(
1469
+ files: Array<{ name: string; content: string; path: string }>,
1470
+ outputPath: string
1471
+ ): Promise<void> {
1472
+ const fs = await import('fs/promises');
1473
+ const path = await import('path');
1474
+
1475
+ // Create output directory
1476
+ await fs.mkdir(outputPath, { recursive: true });
1477
+
1478
+ // Write each file
1479
+ for (const file of files) {
1480
+ const filePath = path.join(outputPath, file.name);
1481
+ await fs.writeFile(filePath, file.content, 'utf-8');
1482
+ }
1483
+ }
1484
+
1485
+ /**
1486
+ * Run in non-interactive mode
1487
+ */
1488
+ async function runNonInteractive(options: GenerateK8sOptions): Promise<void> {
1489
+ ui.header('nimbus generate k8s', 'Non-interactive mode');
1490
+
1491
+ // Validate required options
1492
+ if (!options.name) {
1493
+ ui.error('Name is required in non-interactive mode (--name)');
1494
+ process.exit(1);
1495
+ }
1496
+
1497
+ if (!options.image) {
1498
+ ui.error('Image is required in non-interactive mode (--image)');
1499
+ process.exit(1);
1500
+ }
1501
+
1502
+ ui.info(`Workload type: ${options.workloadType || 'deployment'}`);
1503
+ ui.info(`Name: ${options.name}`);
1504
+ ui.info(`Namespace: ${options.namespace || 'default'}`);
1505
+ ui.info(`Image: ${options.image}`);
1506
+ ui.info(`Output: ${options.output || `./${options.name}-k8s`}`);
1507
+
1508
+ // Build context from options
1509
+ const ctx: K8sWizardContext = {
1510
+ workloadType: options.workloadType || 'deployment',
1511
+ name: options.name,
1512
+ namespace: options.namespace || 'default',
1513
+ image: options.image.split(':')[0],
1514
+ imageTag: options.image.split(':')[1] || 'latest',
1515
+ replicas: options.replicas ?? 2,
1516
+ containerPort: options.port ?? 8080,
1517
+ serviceType: options.serviceType || 'ClusterIP',
1518
+ includeService: true,
1519
+ includeIngress: options.includeIngress ?? false,
1520
+ includeHpa: options.includeHpa ?? false,
1521
+ includePdb: options.includePdb ?? false,
1522
+ includeConfigMap: options.includeConfigMap ?? false,
1523
+ includeSecret: options.includeSecret ?? false,
1524
+ cpuRequest: options.cpuRequest,
1525
+ cpuLimit: options.cpuLimit,
1526
+ memoryRequest: options.memoryRequest,
1527
+ memoryLimit: options.memoryLimit,
1528
+ outputPath: options.output || `./${options.name}-k8s`,
1529
+ outputFormat: 'multiple',
1530
+ };
1531
+
1532
+ ui.newLine();
1533
+ ui.startSpinner({ message: 'Generating manifests...' });
1534
+
1535
+ try {
1536
+ const files = generateManifestsLocally(ctx);
1537
+ await writeFilesToDisk(files, ctx.outputPath!);
1538
+
1539
+ ui.stopSpinnerSuccess(`Generated ${files.length} manifest(s)`);
1540
+
1541
+ ui.newLine();
1542
+ ui.box({
1543
+ title: 'Complete!',
1544
+ content: [
1545
+ `Generated ${files.length} manifest(s) in ${ctx.outputPath}:`,
1546
+ ...files.map(f => ` - ${f.name}`),
1547
+ ],
1548
+ style: 'rounded',
1549
+ borderColor: 'green',
1550
+ padding: 1,
1551
+ });
1552
+ } catch (error: any) {
1553
+ ui.stopSpinnerFail('Generation failed');
1554
+ ui.error(error.message);
1555
+ process.exit(1);
1556
+ }
1557
+ }
1558
+
1559
+ // Export as default command
1560
+ export default generateK8sCommand;