@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,473 @@
1
+ /**
2
+ * Apply Helm Command
3
+ *
4
+ * Install or upgrade Helm releases
5
+ *
6
+ * Usage: nimbus apply helm <release> <chart> [options]
7
+ */
8
+
9
+ import { logger } from '../../utils';
10
+ import { ui, confirm, input } from '../../wizard';
11
+ import { helmClient } from '../../clients';
12
+ import {
13
+ loadSafetyPolicy,
14
+ evaluateSafety,
15
+ type SafetyContext,
16
+ type SafetyCheckResult,
17
+ } from '../../config/safety-policy';
18
+ import { promptForApproval, displaySafetySummary } from '../../wizard/approval';
19
+
20
+ /**
21
+ * Command options
22
+ */
23
+ export interface ApplyHelmOptions {
24
+ releaseName?: string;
25
+ chart?: string;
26
+ namespace?: string;
27
+ dryRun?: boolean;
28
+ wait?: boolean;
29
+ timeout?: string;
30
+ values?: string;
31
+ valuesFiles?: string[];
32
+ set?: Record<string, string>;
33
+ version?: string;
34
+ createNamespace?: boolean;
35
+ install?: boolean;
36
+ atomic?: boolean;
37
+ force?: boolean;
38
+ /** Skip safety checks */
39
+ skipSafety?: boolean;
40
+ /** Environment name (for safety policy) */
41
+ environment?: string;
42
+ }
43
+
44
+ /**
45
+ * Run helm install/upgrade command
46
+ */
47
+ export async function applyHelmCommand(options: ApplyHelmOptions = {}): Promise<void> {
48
+ logger.info('Running helm apply', { options });
49
+
50
+ // Validate required options
51
+ let releaseName = options.releaseName;
52
+ let chart = options.chart;
53
+
54
+ if (!releaseName) {
55
+ releaseName = await input({
56
+ message: 'Release name:',
57
+ validate: value => {
58
+ if (!value) {
59
+ return 'Release name is required';
60
+ }
61
+ return true;
62
+ },
63
+ });
64
+
65
+ if (!releaseName) {
66
+ ui.error('Release name is required');
67
+ process.exit(1);
68
+ }
69
+ }
70
+
71
+ if (!chart) {
72
+ chart = await input({
73
+ message: 'Chart (path or name):',
74
+ defaultValue: '.',
75
+ validate: value => {
76
+ if (!value) {
77
+ return 'Chart is required';
78
+ }
79
+ return true;
80
+ },
81
+ });
82
+
83
+ if (!chart) {
84
+ ui.error('Chart is required');
85
+ process.exit(1);
86
+ }
87
+ }
88
+
89
+ ui.header('Helm Apply');
90
+ ui.info(`Release: ${releaseName}`);
91
+ ui.info(`Chart: ${chart}`);
92
+ if (options.namespace) {
93
+ ui.info(`Namespace: ${options.namespace}`);
94
+ }
95
+ ui.newLine();
96
+
97
+ // Check if helm client is available
98
+ const clientAvailable = await helmClient.isAvailable();
99
+
100
+ if (clientAvailable) {
101
+ // Use Helm tools service
102
+ await applyWithService(releaseName, chart, options);
103
+ } else {
104
+ // Fall back to local helm CLI
105
+ await applyWithLocalCLI(releaseName, chart, options);
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Apply using Helm Tools Service
111
+ */
112
+ async function applyWithService(
113
+ releaseName: string,
114
+ chart: string,
115
+ options: ApplyHelmOptions
116
+ ): Promise<void> {
117
+ // Check if release already exists
118
+ ui.startSpinner({ message: 'Checking existing releases...' });
119
+
120
+ const listResult = await helmClient.list({
121
+ namespace: options.namespace,
122
+ });
123
+
124
+ const existingRelease = listResult.releases.find(r => r.name === releaseName);
125
+ ui.stopSpinnerSuccess('');
126
+
127
+ const isUpgrade = !!existingRelease;
128
+
129
+ if (isUpgrade) {
130
+ ui.info(`Upgrading existing release (revision ${existingRelease.revision})`);
131
+ } else {
132
+ ui.info('Installing new release');
133
+ }
134
+ ui.newLine();
135
+
136
+ // Dry run mode
137
+ if (options.dryRun) {
138
+ ui.startSpinner({ message: 'Running dry-run...' });
139
+
140
+ if (isUpgrade) {
141
+ const result = await helmClient.upgrade(releaseName, chart, {
142
+ namespace: options.namespace,
143
+ valuesFile: options.values,
144
+ version: options.version,
145
+ dryRun: true,
146
+ });
147
+
148
+ ui.stopSpinnerSuccess('Dry-run complete');
149
+
150
+ if (!result.success) {
151
+ ui.error(result.error || 'Unknown error');
152
+ process.exit(1);
153
+ }
154
+
155
+ if (result.output) {
156
+ ui.newLine();
157
+ ui.print(result.output);
158
+ }
159
+ } else {
160
+ const result = await helmClient.install(releaseName, chart, {
161
+ namespace: options.namespace,
162
+ valuesFile: options.values,
163
+ version: options.version,
164
+ createNamespace: options.createNamespace,
165
+ dryRun: true,
166
+ });
167
+
168
+ ui.stopSpinnerSuccess('Dry-run complete');
169
+
170
+ if (!result.success) {
171
+ ui.error(result.error || 'Unknown error');
172
+ process.exit(1);
173
+ }
174
+
175
+ if (result.output) {
176
+ ui.newLine();
177
+ ui.print(result.output);
178
+ }
179
+ }
180
+
181
+ ui.newLine();
182
+ ui.info('No changes applied (dry-run mode)');
183
+ return;
184
+ }
185
+
186
+ // Run safety checks if not skipped
187
+ if (!options.skipSafety) {
188
+ const operation = isUpgrade ? 'upgrade' : 'install';
189
+ const safetyResult = await runHelmSafetyChecks(operation, releaseName, chart, options);
190
+
191
+ if (!safetyResult.passed) {
192
+ ui.newLine();
193
+ ui.error('Safety checks failed - operation blocked');
194
+ for (const blocker of safetyResult.blockers) {
195
+ ui.print(` ${ui.color('✗', 'red')} ${blocker.message}`);
196
+ }
197
+ process.exit(1);
198
+ }
199
+
200
+ // If safety requires approval, prompt for it
201
+ if (safetyResult.requiresApproval) {
202
+ const approvalResult = await promptForApproval({
203
+ title: `Helm ${isUpgrade ? 'Upgrade' : 'Install'}`,
204
+ operation: `helm ${operation}`,
205
+ risks: safetyResult.risks,
206
+ environment: options.environment,
207
+ affectedResources: [`${releaseName} (${chart})`],
208
+ });
209
+
210
+ if (!approvalResult.approved) {
211
+ ui.newLine();
212
+ ui.info(`Apply cancelled: ${approvalResult.reason || 'User declined'}`);
213
+ return;
214
+ }
215
+ } else {
216
+ // Show safety summary and simple confirm
217
+ displaySafetySummary({
218
+ operation: `helm ${operation}`,
219
+ risks: safetyResult.risks,
220
+ passed: safetyResult.passed,
221
+ });
222
+
223
+ ui.newLine();
224
+ const proceed = await confirm({
225
+ message: isUpgrade
226
+ ? `Upgrade release '${releaseName}'?`
227
+ : `Install release '${releaseName}'?`,
228
+ defaultValue: true,
229
+ });
230
+
231
+ if (!proceed) {
232
+ ui.info('Apply cancelled');
233
+ return;
234
+ }
235
+ }
236
+ } else {
237
+ // Simple confirmation when safety is skipped
238
+ const proceed = await confirm({
239
+ message: isUpgrade
240
+ ? `Upgrade release '${releaseName}'?`
241
+ : `Install release '${releaseName}'?`,
242
+ defaultValue: true,
243
+ });
244
+
245
+ if (!proceed) {
246
+ ui.info('Apply cancelled');
247
+ return;
248
+ }
249
+ }
250
+
251
+ // Install or upgrade
252
+ ui.newLine();
253
+ ui.startSpinner({
254
+ message: isUpgrade ? 'Upgrading release...' : 'Installing release...',
255
+ });
256
+
257
+ let result;
258
+
259
+ if (isUpgrade) {
260
+ result = await helmClient.upgrade(releaseName, chart, {
261
+ namespace: options.namespace,
262
+ valuesFile: options.values,
263
+ version: options.version,
264
+ wait: options.wait,
265
+ timeout: options.timeout,
266
+ });
267
+ } else {
268
+ result = await helmClient.install(releaseName, chart, {
269
+ namespace: options.namespace,
270
+ valuesFile: options.values,
271
+ version: options.version,
272
+ createNamespace: options.createNamespace ?? true,
273
+ wait: options.wait,
274
+ timeout: options.timeout,
275
+ });
276
+ }
277
+
278
+ if (!result.success) {
279
+ ui.stopSpinnerFail(isUpgrade ? 'Upgrade failed' : 'Installation failed');
280
+ ui.error(result.error || 'Unknown error');
281
+ process.exit(1);
282
+ }
283
+
284
+ ui.stopSpinnerSuccess(isUpgrade ? 'Upgrade complete!' : 'Installation complete!');
285
+
286
+ // Track successful helm apply
287
+ try {
288
+ const { trackGeneration } = await import('../../telemetry');
289
+ trackGeneration('helm-apply', ['helm']);
290
+ } catch {
291
+ /* telemetry failure is non-critical */
292
+ }
293
+
294
+ // Display release info
295
+ ui.newLine();
296
+ ui.print('Release Info:');
297
+ ui.print(` Name: ${result.release.name}`);
298
+ ui.print(` Namespace: ${result.release.namespace}`);
299
+ ui.print(` Revision: ${result.release.revision}`);
300
+ ui.print(` Status: ${result.release.status}`);
301
+ ui.print(` Chart: ${result.release.chart}`);
302
+
303
+ if (result.output) {
304
+ ui.newLine();
305
+ ui.print('Notes:');
306
+ ui.print(result.output);
307
+ }
308
+ }
309
+
310
+ /**
311
+ * Apply using local helm CLI
312
+ */
313
+ async function applyWithLocalCLI(
314
+ releaseName: string,
315
+ chart: string,
316
+ options: ApplyHelmOptions
317
+ ): Promise<void> {
318
+ const { spawn, execFileSync } = await import('child_process');
319
+
320
+ // Check if release exists (use execFileSync with args array to prevent shell injection)
321
+ let isUpgrade = false;
322
+ try {
323
+ const statusArgs = ['status', releaseName];
324
+ if (options.namespace) {
325
+ statusArgs.push('-n', options.namespace);
326
+ }
327
+ execFileSync('helm', statusArgs, { stdio: 'pipe' });
328
+ isUpgrade = true;
329
+ ui.info('Upgrading existing release');
330
+ } catch {
331
+ ui.info('Installing new release');
332
+ }
333
+ ui.newLine();
334
+
335
+ // Build helm command
336
+ const command = isUpgrade ? 'upgrade' : 'install';
337
+ const args = [command, releaseName, chart];
338
+
339
+ if (options.namespace) {
340
+ args.push('-n', options.namespace);
341
+ }
342
+
343
+ if (options.values) {
344
+ args.push('-f', options.values);
345
+ }
346
+
347
+ if (options.valuesFiles) {
348
+ for (const vf of options.valuesFiles) {
349
+ args.push('-f', vf);
350
+ }
351
+ }
352
+
353
+ if (options.set) {
354
+ for (const [key, value] of Object.entries(options.set)) {
355
+ args.push('--set', `${key}=${value}`);
356
+ }
357
+ }
358
+
359
+ if (options.version) {
360
+ args.push('--version', options.version);
361
+ }
362
+
363
+ if (!isUpgrade && (options.createNamespace ?? true)) {
364
+ args.push('--create-namespace');
365
+ }
366
+
367
+ if (options.wait) {
368
+ args.push('--wait');
369
+ }
370
+
371
+ if (options.timeout) {
372
+ args.push('--timeout', options.timeout);
373
+ }
374
+
375
+ if (options.dryRun) {
376
+ args.push('--dry-run');
377
+ }
378
+
379
+ if (options.atomic) {
380
+ args.push('--atomic');
381
+ }
382
+
383
+ if (options.force) {
384
+ args.push('--force');
385
+ }
386
+
387
+ // For upgrade, always use --install flag
388
+ if (isUpgrade) {
389
+ args.push('--install');
390
+ }
391
+
392
+ // Redact sensitive values from --set flags before logging
393
+ const redactedArgs = [...args];
394
+ for (let i = 0; i < redactedArgs.length; i++) {
395
+ if (
396
+ (redactedArgs[i] === '--set' ||
397
+ redactedArgs[i] === '--set-string' ||
398
+ redactedArgs[i] === '--set-file') &&
399
+ redactedArgs[i + 1]
400
+ ) {
401
+ const raw = redactedArgs[i + 1];
402
+ const eq = raw.indexOf('=');
403
+ redactedArgs[i + 1] = eq >= 0 ? `${raw.slice(0, eq + 1)}<REDACTED>` : '<REDACTED>';
404
+ }
405
+ }
406
+ ui.info(`Running: helm ${redactedArgs.join(' ')}`);
407
+ ui.newLine();
408
+
409
+ // Run helm
410
+ return new Promise((resolve, _reject) => {
411
+ const proc = spawn('helm', args, {
412
+ stdio: 'inherit',
413
+ });
414
+
415
+ proc.on('error', error => {
416
+ ui.error(`Failed to run helm: ${error.message}`);
417
+ ui.info('Make sure helm is installed and in your PATH');
418
+ process.exit(1);
419
+ });
420
+
421
+ proc.on('close', code => {
422
+ if (code === 0) {
423
+ ui.newLine();
424
+ ui.success(`Helm ${isUpgrade ? 'upgrade' : 'install'} completed successfully`);
425
+
426
+ // Track successful helm apply
427
+ try {
428
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
429
+ const { trackGeneration } = require('../../telemetry');
430
+ trackGeneration('helm-apply', ['helm']);
431
+ } catch {
432
+ /* telemetry failure is non-critical */
433
+ }
434
+
435
+ resolve();
436
+ } else {
437
+ ui.newLine();
438
+ ui.error(`Helm ${isUpgrade ? 'upgrade' : 'install'} failed with exit code ${code}`);
439
+ process.exit(code || 1);
440
+ }
441
+ });
442
+ });
443
+ }
444
+
445
+ /**
446
+ * Run safety checks for the operation
447
+ */
448
+ async function runHelmSafetyChecks(
449
+ operation: string,
450
+ releaseName: string,
451
+ chart: string,
452
+ options: ApplyHelmOptions
453
+ ): Promise<SafetyCheckResult> {
454
+ const policy = loadSafetyPolicy();
455
+
456
+ const context: SafetyContext = {
457
+ operation,
458
+ type: 'helm',
459
+ environment: options.environment,
460
+ resources: [`${releaseName}:${chart}`],
461
+ metadata: {
462
+ releaseName,
463
+ chart,
464
+ namespace: options.namespace,
465
+ version: options.version,
466
+ },
467
+ };
468
+
469
+ return evaluateSafety(context, policy);
470
+ }
471
+
472
+ // Export as default
473
+ export default applyHelmCommand;
@@ -0,0 +1,213 @@
1
+ /**
2
+ * Apply Command
3
+ *
4
+ * Apply infrastructure changes for Terraform, Kubernetes, and Helm
5
+ *
6
+ * Usage: nimbus apply <type> [target] [options]
7
+ */
8
+
9
+ import { logger } from '../../utils';
10
+ import { ui } from '../../wizard';
11
+ import { applyTerraformCommand } from './terraform';
12
+ import { applyK8sCommand } from './k8s';
13
+ import { applyHelmCommand } from './helm';
14
+
15
+ // Re-export subcommand types
16
+ export { type ApplyTerraformOptions } from './terraform';
17
+ export { type ApplyK8sOptions } from './k8s';
18
+ export { type ApplyHelmOptions } from './helm';
19
+
20
+ /**
21
+ * Common apply options
22
+ */
23
+ export interface ApplyOptions {
24
+ dryRun?: boolean;
25
+ autoApprove?: boolean;
26
+ target?: string;
27
+ var?: Record<string, string>;
28
+ varFile?: string;
29
+ namespace?: string;
30
+ wait?: boolean;
31
+ timeout?: string;
32
+ }
33
+
34
+ /**
35
+ * Apply type
36
+ */
37
+ export type ApplyType = 'terraform' | 'k8s' | 'helm';
38
+
39
+ /**
40
+ * Parse common apply options from args
41
+ */
42
+ export function parseApplyOptions(args: string[]): ApplyOptions {
43
+ const options: ApplyOptions = {};
44
+
45
+ for (let i = 0; i < args.length; i++) {
46
+ const arg = args[i];
47
+
48
+ if (arg === '--dry-run') {
49
+ options.dryRun = true;
50
+ } else if (arg === '--auto-approve' || arg === '-y' || arg === '--yes') {
51
+ options.autoApprove = true;
52
+ } else if ((arg === '--target' || arg === '-t') && args[i + 1]) {
53
+ options.target = args[++i];
54
+ } else if (arg === '--var' && args[i + 1]) {
55
+ const varArg = args[++i];
56
+ const [key, ...valueParts] = varArg.split('=');
57
+ options.var = options.var || {};
58
+ options.var[key] = valueParts.join('=');
59
+ } else if (arg === '--var-file' && args[i + 1]) {
60
+ options.varFile = args[++i];
61
+ } else if ((arg === '--namespace' || arg === '-n') && args[i + 1]) {
62
+ options.namespace = args[++i];
63
+ } else if (arg === '--wait') {
64
+ options.wait = true;
65
+ } else if (arg === '--timeout' && args[i + 1]) {
66
+ options.timeout = args[++i];
67
+ }
68
+ }
69
+
70
+ return options;
71
+ }
72
+
73
+ /**
74
+ * Detect infrastructure type from current directory
75
+ */
76
+ async function detectInfraType(): Promise<ApplyType | null> {
77
+ const fs = await import('fs/promises');
78
+
79
+ // Check for Terraform files
80
+ try {
81
+ const files = await fs.readdir('.');
82
+ if (files.some(f => f.endsWith('.tf'))) {
83
+ return 'terraform';
84
+ }
85
+ } catch {
86
+ // Ignore
87
+ }
88
+
89
+ // Check for Kubernetes manifests
90
+ try {
91
+ const files = await fs.readdir('.');
92
+ const yamlFiles = files.filter(f => f.endsWith('.yaml') || f.endsWith('.yml'));
93
+ for (const file of yamlFiles) {
94
+ const content = await fs.readFile(file, 'utf-8');
95
+ if (content.includes('apiVersion:') && content.includes('kind:')) {
96
+ return 'k8s';
97
+ }
98
+ }
99
+ } catch {
100
+ // Ignore
101
+ }
102
+
103
+ // Check for Helm chart
104
+ try {
105
+ await fs.access('./Chart.yaml');
106
+ return 'helm';
107
+ } catch {
108
+ // Ignore
109
+ }
110
+
111
+ // Check for values files (Helm)
112
+ try {
113
+ const files = await fs.readdir('.');
114
+ if (files.some(f => f.startsWith('values') && (f.endsWith('.yaml') || f.endsWith('.yml')))) {
115
+ return 'helm';
116
+ }
117
+ } catch {
118
+ // Ignore
119
+ }
120
+
121
+ return null;
122
+ }
123
+
124
+ /**
125
+ * Run the apply command
126
+ */
127
+ export async function applyCommand(type: string | undefined, args: string[]): Promise<void> {
128
+ logger.info('Running apply command', { type, args });
129
+
130
+ // Parse common options
131
+ const options = parseApplyOptions(args);
132
+
133
+ // Get positional arguments (after type)
134
+ const positionalArgs = args.filter(arg => !arg.startsWith('-') && !arg.includes('='));
135
+
136
+ // Auto-detect type if not provided
137
+ if (!type || type.startsWith('-')) {
138
+ ui.startSpinner({ message: 'Detecting infrastructure type...' });
139
+ const detectedType = await detectInfraType();
140
+ ui.stopSpinnerSuccess('');
141
+
142
+ if (!detectedType) {
143
+ ui.error('Could not detect infrastructure type');
144
+ ui.newLine();
145
+ ui.info('Usage: nimbus apply <type> [target] [options]');
146
+ ui.info('');
147
+ ui.info('Types:');
148
+ ui.info(' terraform Apply Terraform configuration');
149
+ ui.info(' k8s Apply Kubernetes manifests');
150
+ ui.info(' helm Install/upgrade Helm release');
151
+ process.exit(1);
152
+ }
153
+
154
+ type = detectedType;
155
+ ui.info(`Detected infrastructure type: ${type}`);
156
+ ui.newLine();
157
+ }
158
+
159
+ switch (type) {
160
+ case 'terraform':
161
+ case 'tf':
162
+ await applyTerraformCommand({
163
+ directory: positionalArgs[0] || options.target || '.',
164
+ dryRun: options.dryRun,
165
+ autoApprove: options.autoApprove,
166
+ var: options.var,
167
+ varFile: options.varFile,
168
+ target: options.target,
169
+ });
170
+ break;
171
+
172
+ case 'k8s':
173
+ case 'kubernetes':
174
+ await applyK8sCommand({
175
+ manifests: positionalArgs[0] || options.target || '.',
176
+ namespace: options.namespace,
177
+ dryRun: options.dryRun,
178
+ wait: options.wait,
179
+ });
180
+ break;
181
+
182
+ case 'helm': {
183
+ // For Helm, we need release name and chart
184
+ const releaseName = positionalArgs[0];
185
+ const chartPath = positionalArgs[1] || '.';
186
+
187
+ await applyHelmCommand({
188
+ releaseName,
189
+ chart: chartPath,
190
+ namespace: options.namespace,
191
+ dryRun: options.dryRun,
192
+ wait: options.wait,
193
+ timeout: options.timeout,
194
+ values: options.varFile,
195
+ });
196
+ break;
197
+ }
198
+
199
+ default:
200
+ ui.error(`Unknown apply type: ${type}`);
201
+ ui.newLine();
202
+ ui.info('Supported types: terraform, k8s, helm');
203
+ process.exit(1);
204
+ }
205
+ }
206
+
207
+ // Export subcommands
208
+ export { applyTerraformCommand } from './terraform';
209
+ export { applyK8sCommand } from './k8s';
210
+ export { applyHelmCommand } from './helm';
211
+
212
+ // Export as default
213
+ export default applyCommand;