@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,228 @@
1
+ /**
2
+ * Onboarding Command
3
+ *
4
+ * First-run wizard that detects missing LLM credentials and guides
5
+ * the user through initial setup. Runs before full app init so it
6
+ * only depends on the auth store and wizard prompts.
7
+ */
8
+
9
+ import { AuthStore } from '../auth/store';
10
+ import { PROVIDER_REGISTRY, getDefaultModel, validateProviderApiKey } from '../auth/providers';
11
+ import type { LLMProviderName } from '../auth/types';
12
+ import { ui } from '../wizard/ui';
13
+ import { select, input } from '../wizard/prompts';
14
+
15
+ export interface OnboardingOptions {
16
+ /** Skip the wizard and exit immediately. */
17
+ skip?: boolean;
18
+ }
19
+
20
+ /**
21
+ * Check whether onboarding is needed.
22
+ *
23
+ * Returns `true` if no LLM credentials are found in the auth store
24
+ * and no provider API key environment variables are set.
25
+ */
26
+ export function needsOnboarding(): boolean {
27
+ const store = new AuthStore();
28
+ if (store.exists()) {
29
+ return false;
30
+ }
31
+
32
+ const hasEnvKey = !!(
33
+ process.env.ANTHROPIC_API_KEY ||
34
+ process.env.OPENAI_API_KEY ||
35
+ process.env.GOOGLE_API_KEY ||
36
+ process.env.OPENROUTER_API_KEY ||
37
+ process.env.GROQ_API_KEY ||
38
+ process.env.TOGETHER_API_KEY ||
39
+ process.env.DEEPSEEK_API_KEY ||
40
+ process.env.FIREWORKS_API_KEY ||
41
+ process.env.PERPLEXITY_API_KEY ||
42
+ process.env.AWS_ACCESS_KEY_ID
43
+ );
44
+
45
+ return !hasEnvKey;
46
+ }
47
+
48
+ /**
49
+ * Run the first-run onboarding wizard.
50
+ */
51
+ export async function onboardingCommand(options: OnboardingOptions = {}): Promise<void> {
52
+ if (options.skip) {
53
+ return;
54
+ }
55
+
56
+ const store = new AuthStore();
57
+
58
+ // Double-check — if credentials exist, skip silently
59
+ if (store.exists()) {
60
+ return;
61
+ }
62
+
63
+ // Non-TTY detection: if stdin is not a TTY (piped input, CI, etc.),
64
+ // skip the interactive wizard and show setup instructions instead.
65
+ if (!process.stdin.isTTY) {
66
+ ui.newLine();
67
+ ui.info('No LLM provider configured. Set an API key via environment variable:');
68
+ ui.newLine();
69
+ ui.print(' export ANTHROPIC_API_KEY=sk-ant-...');
70
+ ui.print(' export OPENAI_API_KEY=sk-...');
71
+ ui.print(' export GOOGLE_API_KEY=...');
72
+ ui.print(' export OPENROUTER_API_KEY=sk-or-...');
73
+ ui.print(' export GROQ_API_KEY=gsk_...');
74
+ ui.print(' export DEEPSEEK_API_KEY=sk-...');
75
+ ui.newLine();
76
+ ui.info('Or run "nimbus login" in an interactive terminal.');
77
+ ui.newLine();
78
+ return;
79
+ }
80
+
81
+ try {
82
+ // Welcome banner
83
+ ui.newLine();
84
+ ui.box({
85
+ title: 'Welcome to Nimbus',
86
+ content: [
87
+ 'AI-Powered Cloud Engineering Agent',
88
+ '',
89
+ "Let's get you set up with an LLM provider.",
90
+ 'This takes about 30 seconds.',
91
+ ],
92
+ style: 'rounded',
93
+ borderColor: 'cyan',
94
+ titleColor: 'brightCyan',
95
+ padding: 1,
96
+ });
97
+ ui.newLine();
98
+
99
+ // Provider selection
100
+ const providerOptions = Object.values(PROVIDER_REGISTRY).map(p => ({
101
+ label: p.displayName,
102
+ value: p.name,
103
+ description: p.description,
104
+ }));
105
+
106
+ const provider = await select<LLMProviderName>({
107
+ message: 'Which LLM provider would you like to use?',
108
+ options: providerOptions,
109
+ required: true,
110
+ });
111
+
112
+ if (!provider) {
113
+ ui.newLine();
114
+ ui.info('No provider selected. You can run "nimbus login" later to set up.');
115
+ ui.newLine();
116
+ return;
117
+ }
118
+
119
+ const providerInfo = PROVIDER_REGISTRY[provider];
120
+
121
+ // API key input (skip for Ollama)
122
+ let apiKey: string | undefined;
123
+ let baseUrl: string | undefined;
124
+
125
+ if (providerInfo.requiresApiKey) {
126
+ ui.newLine();
127
+ if (providerInfo.apiKeyUrl) {
128
+ ui.info(`Get your API key at: ${ui.color(providerInfo.apiKeyUrl, 'cyan')}`);
129
+ }
130
+ ui.newLine();
131
+
132
+ apiKey = await input({
133
+ message: `Enter your ${providerInfo.displayName} API key`,
134
+ });
135
+
136
+ if (!apiKey) {
137
+ ui.newLine();
138
+ ui.warning('No API key entered. You can run "nimbus login" later to set up.');
139
+ ui.newLine();
140
+ return;
141
+ }
142
+ } else if (providerInfo.supportsBaseUrl) {
143
+ // Ollama — ask for base URL
144
+ ui.newLine();
145
+ baseUrl = await input({
146
+ message: 'Ollama server URL',
147
+ defaultValue: providerInfo.defaultBaseUrl || 'http://localhost:11434',
148
+ });
149
+ }
150
+
151
+ // Validate credentials
152
+ ui.newLine();
153
+ ui.startSpinner({ message: `Validating ${providerInfo.displayName} credentials...` });
154
+
155
+ let validation: { valid: boolean; error?: string };
156
+ try {
157
+ validation = await validateProviderApiKey(provider, apiKey, baseUrl);
158
+ } catch (err: unknown) {
159
+ const msg = err instanceof Error ? err.message : String(err);
160
+ ui.stopSpinnerFail(`Validation error: ${msg}`);
161
+ ui.newLine();
162
+ ui.info('You can retry with "nimbus login" or set the API key via environment variable.');
163
+ ui.newLine();
164
+ return;
165
+ }
166
+
167
+ if (!validation.valid) {
168
+ ui.stopSpinnerFail(`Validation failed: ${validation.error}`);
169
+ ui.newLine();
170
+ ui.info('You can retry with "nimbus login" or set the API key via environment variable:');
171
+ if (providerInfo.envVarName) {
172
+ ui.info(` export ${providerInfo.envVarName}=your-key`);
173
+ }
174
+ ui.newLine();
175
+ return;
176
+ }
177
+
178
+ ui.stopSpinnerSuccess(`${providerInfo.displayName} credentials verified!`);
179
+
180
+ // Save credentials
181
+ const defaultModel = getDefaultModel(provider);
182
+
183
+ store.setProvider(provider, {
184
+ apiKey,
185
+ baseUrl,
186
+ model: defaultModel,
187
+ isDefault: true,
188
+ validatedAt: new Date().toISOString(),
189
+ });
190
+
191
+ // Create default config.yaml if it doesn't exist
192
+ try {
193
+ const { configManager } = await import('../config/manager');
194
+ if (!configManager.exists()) {
195
+ configManager.save();
196
+ }
197
+ } catch {
198
+ /* non-critical */
199
+ }
200
+
201
+ // Success message
202
+ ui.newLine();
203
+ ui.box({
204
+ title: 'Setup Complete',
205
+ content: [
206
+ `Provider: ${providerInfo.displayName}`,
207
+ `Model: ${defaultModel}`,
208
+ '',
209
+ "You're ready to go! Starting interactive chat...",
210
+ ],
211
+ style: 'rounded',
212
+ borderColor: 'green',
213
+ titleColor: 'brightGreen',
214
+ padding: 1,
215
+ });
216
+ ui.newLine();
217
+ } catch (err: unknown) {
218
+ // Catch-all for prompt failures (stdin closed, terminal not supported, etc.)
219
+ const msg = err instanceof Error ? err.message : String(err);
220
+ ui.newLine();
221
+ ui.warning(`Onboarding wizard failed: ${msg}`);
222
+ ui.newLine();
223
+ ui.info('Set up manually with "nimbus login" or via environment variables:');
224
+ ui.print(' export ANTHROPIC_API_KEY=sk-ant-...');
225
+ ui.print(' export OPENAI_API_KEY=sk-...');
226
+ ui.newLine();
227
+ }
228
+ }
@@ -0,0 +1,279 @@
1
+ /**
2
+ * Plan Display Utilities
3
+ *
4
+ * Format and display plan output for different infrastructure types
5
+ */
6
+
7
+ import { ui } from '../../wizard';
8
+
9
+ /**
10
+ * Resource change in plan
11
+ */
12
+ export interface ResourceChange {
13
+ action: string;
14
+ resource: string;
15
+ address: string;
16
+ before?: Record<string, unknown>;
17
+ after?: Record<string, unknown>;
18
+ }
19
+
20
+ /**
21
+ * Plan result structure
22
+ */
23
+ export interface PlanResult {
24
+ type: 'terraform' | 'k8s' | 'helm';
25
+ success: boolean;
26
+ error?: string;
27
+ changes?: {
28
+ add: number;
29
+ change: number;
30
+ destroy: number;
31
+ };
32
+ resources?: ResourceChange[];
33
+ raw?: string;
34
+ }
35
+
36
+ /**
37
+ * Action symbols for display
38
+ */
39
+ const ACTION_SYMBOLS: Record<
40
+ string,
41
+ { symbol: string; color: 'green' | 'yellow' | 'red' | 'blue' | 'cyan' }
42
+ > = {
43
+ create: { symbol: '+', color: 'green' },
44
+ add: { symbol: '+', color: 'green' },
45
+ apply: { symbol: '~', color: 'cyan' },
46
+ update: { symbol: '~', color: 'yellow' },
47
+ change: { symbol: '~', color: 'yellow' },
48
+ delete: { symbol: '-', color: 'red' },
49
+ destroy: { symbol: '-', color: 'red' },
50
+ read: { symbol: '<=', color: 'blue' },
51
+ 'no-op': { symbol: ' ', color: 'blue' },
52
+ };
53
+
54
+ /**
55
+ * Format action for display
56
+ */
57
+ function formatAction(action: string): string {
58
+ const config = ACTION_SYMBOLS[action.toLowerCase()] || { symbol: '?', color: 'yellow' as const };
59
+ return ui.color(config.symbol, config.color);
60
+ }
61
+
62
+ /**
63
+ * Format resource for display
64
+ */
65
+ function formatResource(resource: string): string {
66
+ return resource;
67
+ }
68
+
69
+ /**
70
+ * Display plan summary
71
+ */
72
+ function displaySummary(changes: { add: number; change: number; destroy: number }): void {
73
+ ui.print('Plan Summary:');
74
+ ui.newLine();
75
+
76
+ const { add, change, destroy } = changes;
77
+ const total = add + change + destroy;
78
+
79
+ if (total === 0) {
80
+ ui.print(' No changes. Infrastructure is up to date.');
81
+ return;
82
+ }
83
+
84
+ if (add > 0) {
85
+ ui.print(` ${ui.color(`+ ${add} to add`, 'green')}`);
86
+ }
87
+ if (change > 0) {
88
+ ui.print(` ${ui.color(`~ ${change} to change`, 'yellow')}`);
89
+ }
90
+ if (destroy > 0) {
91
+ ui.print(` ${ui.color(`- ${destroy} to destroy`, 'red')}`);
92
+ }
93
+
94
+ ui.newLine();
95
+ ui.print(` Total: ${total} resource(s)`);
96
+ }
97
+
98
+ /**
99
+ * Display resource changes
100
+ */
101
+ function displayResources(resources: ResourceChange[]): void {
102
+ if (!resources || resources.length === 0) {
103
+ return;
104
+ }
105
+
106
+ ui.newLine();
107
+ ui.print('Resource Changes:');
108
+ ui.newLine();
109
+
110
+ // Group by action
111
+ const grouped: Record<string, ResourceChange[]> = {};
112
+
113
+ for (const resource of resources) {
114
+ const action = resource.action.toLowerCase();
115
+ if (!grouped[action]) {
116
+ grouped[action] = [];
117
+ }
118
+ grouped[action].push(resource);
119
+ }
120
+
121
+ // Display in order: create, update, delete, other
122
+ const order = ['create', 'add', 'update', 'change', 'apply', 'delete', 'destroy', 'read'];
123
+
124
+ for (const action of order) {
125
+ const items = grouped[action];
126
+ if (!items || items.length === 0) {
127
+ continue;
128
+ }
129
+
130
+ for (const item of items) {
131
+ const symbol = formatAction(item.action);
132
+ const resource = formatResource(item.resource);
133
+ ui.print(` ${symbol} ${resource}`);
134
+
135
+ // Show address if different from resource name
136
+ if (item.address && item.address !== item.resource) {
137
+ ui.print(` ${ui.dim(item.address)}`);
138
+ }
139
+ }
140
+ }
141
+
142
+ // Display any remaining actions not in order
143
+ for (const [action, items] of Object.entries(grouped)) {
144
+ if (order.includes(action)) {
145
+ continue;
146
+ }
147
+
148
+ for (const item of items) {
149
+ const symbol = formatAction(item.action);
150
+ const resource = formatResource(item.resource);
151
+ ui.print(` ${symbol} ${resource}`);
152
+ }
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Display raw plan output (detailed mode)
158
+ */
159
+ function displayRawPlan(raw: string): void {
160
+ ui.newLine();
161
+ ui.print('Detailed Plan:');
162
+ ui.newLine();
163
+
164
+ // Limit output length
165
+ const maxLength = 5000;
166
+ if (raw.length > maxLength) {
167
+ ui.print(raw.slice(0, maxLength));
168
+ ui.newLine();
169
+ ui.print(ui.dim(`... (output truncated, ${raw.length - maxLength} more characters)`));
170
+ } else {
171
+ ui.print(raw);
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Display Terraform-specific plan
177
+ */
178
+ function displayTerraformPlan(plan: PlanResult, detailed: boolean): void {
179
+ if (plan.changes) {
180
+ displaySummary(plan.changes);
181
+ }
182
+
183
+ if (plan.resources && plan.resources.length > 0) {
184
+ displayResources(plan.resources);
185
+ }
186
+
187
+ if (detailed && plan.raw) {
188
+ displayRawPlan(plan.raw);
189
+ }
190
+
191
+ // Show next steps
192
+ ui.newLine();
193
+ ui.print('Next steps:');
194
+ ui.print(' - Review the changes above');
195
+ ui.print(' - Run "nimbus apply terraform" to apply');
196
+ ui.print(' - Or run "nimbus tf apply" for more options');
197
+ }
198
+
199
+ /**
200
+ * Display Kubernetes-specific plan
201
+ */
202
+ function displayK8sPlan(plan: PlanResult, detailed: boolean): void {
203
+ if (plan.changes) {
204
+ ui.print('Plan Summary:');
205
+ ui.newLine();
206
+ ui.print(` ${ui.color(`${plan.changes.add} resource(s) to apply`, 'cyan')}`);
207
+ if (plan.changes.change > 0) {
208
+ ui.print(` ${ui.color(`${plan.changes.change} resource(s) will be updated`, 'yellow')}`);
209
+ }
210
+ ui.newLine();
211
+ ui.info("Note: Kubernetes apply is idempotent - unchanged resources won't be modified");
212
+ }
213
+
214
+ if (plan.resources && plan.resources.length > 0) {
215
+ displayResources(plan.resources);
216
+ }
217
+
218
+ if (detailed && plan.raw) {
219
+ displayRawPlan(plan.raw);
220
+ }
221
+
222
+ // Show next steps
223
+ ui.newLine();
224
+ ui.print('Next steps:');
225
+ ui.print(' - Review the resources above');
226
+ ui.print(' - Run "nimbus apply k8s" to apply');
227
+ ui.print(' - Or run "nimbus k8s apply" for more options');
228
+ }
229
+
230
+ /**
231
+ * Display Helm-specific plan
232
+ */
233
+ function displayHelmPlan(plan: PlanResult, detailed: boolean): void {
234
+ if (plan.changes) {
235
+ ui.print('Plan Summary:');
236
+ ui.newLine();
237
+ if (plan.changes.add > 0) {
238
+ ui.print(` ${ui.color(`+ ${plan.changes.add} resource(s) to create`, 'green')}`);
239
+ }
240
+ if (plan.changes.change > 0) {
241
+ ui.print(` ${ui.color(`~ ${plan.changes.change} resource(s) to update`, 'yellow')}`);
242
+ }
243
+ if (plan.changes.destroy > 0) {
244
+ ui.print(` ${ui.color(`- ${plan.changes.destroy} resource(s) to remove`, 'red')}`);
245
+ }
246
+ }
247
+
248
+ if (plan.resources && plan.resources.length > 0) {
249
+ displayResources(plan.resources);
250
+ }
251
+
252
+ if (detailed && plan.raw) {
253
+ displayRawPlan(plan.raw);
254
+ }
255
+
256
+ // Show next steps
257
+ ui.newLine();
258
+ ui.print('Next steps:');
259
+ ui.print(' - Review the changes above');
260
+ ui.print(' - Run "nimbus apply helm" to install/upgrade');
261
+ ui.print(' - Or run "nimbus helm install/upgrade" for more options');
262
+ }
263
+
264
+ /**
265
+ * Display plan result
266
+ */
267
+ export function displayPlan(plan: PlanResult, detailed = false): void {
268
+ switch (plan.type) {
269
+ case 'terraform':
270
+ displayTerraformPlan(plan, detailed);
271
+ break;
272
+ case 'k8s':
273
+ displayK8sPlan(plan, detailed);
274
+ break;
275
+ case 'helm':
276
+ displayHelmPlan(plan, detailed);
277
+ break;
278
+ }
279
+ }