@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,439 @@
1
+ /**
2
+ * Drift Commands
3
+ *
4
+ * Commands for detecting and fixing infrastructure drift
5
+ */
6
+
7
+ import { ui } from '../../wizard/ui';
8
+ import { select, confirm } from '../../wizard/prompts';
9
+ import { CoreEngineClient } from '../../clients/core-engine-client';
10
+ import type { DriftReport, DriftRemediationResult, DriftProvider } from '../../types';
11
+
12
+ // ==========================================
13
+ // Types
14
+ // ==========================================
15
+
16
+ export interface DriftDetectOptions {
17
+ /** Provider to check: terraform, kubernetes, helm */
18
+ provider?: DriftProvider;
19
+ /** Directory containing infrastructure code */
20
+ directory?: string;
21
+ /** Output format */
22
+ json?: boolean;
23
+ /** Show verbose output */
24
+ verbose?: boolean;
25
+ }
26
+
27
+ export interface DriftFixOptions {
28
+ /** Provider to fix: terraform, kubernetes, helm */
29
+ provider?: DriftProvider;
30
+ /** Directory containing infrastructure code */
31
+ directory?: string;
32
+ /** Auto-approve all changes */
33
+ autoApprove?: boolean;
34
+ /** Dry run - show what would be fixed */
35
+ dryRun?: boolean;
36
+ /** Output format */
37
+ json?: boolean;
38
+ }
39
+
40
+ // ==========================================
41
+ // Parsers
42
+ // ==========================================
43
+
44
+ /**
45
+ * Parse drift detect options
46
+ */
47
+ export function parseDriftDetectOptions(args: string[]): DriftDetectOptions {
48
+ const options: DriftDetectOptions = {};
49
+
50
+ for (let i = 0; i < args.length; i++) {
51
+ const arg = args[i];
52
+ if (arg === '--provider' && args[i + 1]) {
53
+ options.provider = args[++i] as DriftProvider;
54
+ } else if (arg === '--directory' && args[i + 1]) {
55
+ options.directory = args[++i];
56
+ } else if (arg === '-d' && args[i + 1]) {
57
+ options.directory = args[++i];
58
+ } else if (arg === '--json') {
59
+ options.json = true;
60
+ } else if (arg === '--verbose' || arg === '-v') {
61
+ options.verbose = true;
62
+ } else if (!arg.startsWith('-') && !options.provider) {
63
+ options.provider = arg as DriftProvider;
64
+ }
65
+ }
66
+
67
+ return options;
68
+ }
69
+
70
+ /**
71
+ * Parse drift fix options
72
+ */
73
+ export function parseDriftFixOptions(args: string[]): DriftFixOptions {
74
+ const options: DriftFixOptions = {};
75
+
76
+ for (let i = 0; i < args.length; i++) {
77
+ const arg = args[i];
78
+ if (arg === '--provider' && args[i + 1]) {
79
+ options.provider = args[++i] as DriftProvider;
80
+ } else if (arg === '--directory' && args[i + 1]) {
81
+ options.directory = args[++i];
82
+ } else if (arg === '-d' && args[i + 1]) {
83
+ options.directory = args[++i];
84
+ } else if (arg === '--auto-approve' || arg === '-y') {
85
+ options.autoApprove = true;
86
+ } else if (arg === '--dry-run') {
87
+ options.dryRun = true;
88
+ } else if (arg === '--json') {
89
+ options.json = true;
90
+ } else if (!arg.startsWith('-') && !options.provider) {
91
+ options.provider = arg as DriftProvider;
92
+ }
93
+ }
94
+
95
+ return options;
96
+ }
97
+
98
+ // ==========================================
99
+ // Display Functions
100
+ // ==========================================
101
+
102
+ /**
103
+ * Format drift severity with color
104
+ */
105
+ function _formatSeverity(severity: 'critical' | 'high' | 'medium' | 'low'): string {
106
+ switch (severity) {
107
+ case 'critical':
108
+ return ui.color('CRITICAL', 'red');
109
+ case 'high':
110
+ return ui.color('HIGH', 'red');
111
+ case 'medium':
112
+ return ui.color('MEDIUM', 'yellow');
113
+ case 'low':
114
+ default:
115
+ return ui.color('LOW', 'blue');
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Format drift type with color
121
+ */
122
+ function formatDriftType(type: 'added' | 'removed' | 'modified'): string {
123
+ switch (type) {
124
+ case 'added':
125
+ return ui.color('+', 'green');
126
+ case 'removed':
127
+ return ui.color('-', 'red');
128
+ case 'modified':
129
+ return ui.color('~', 'yellow');
130
+ default:
131
+ return '?';
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Display drift report
137
+ */
138
+ function displayDriftReport(report: DriftReport): void {
139
+ ui.newLine();
140
+ ui.section(`Drift Report - ${report.provider.toUpperCase()}`);
141
+
142
+ ui.print(` ${ui.dim('Detected at:')} ${new Date(report.detectedAt).toLocaleString()}`);
143
+ ui.print(` ${ui.dim('Total items:')} ${report.summary.total}`);
144
+ ui.print(
145
+ ` ${ui.dim('Has drift:')} ${report.hasDrift ? ui.color('Yes', 'yellow') : ui.color('No', 'green')}`
146
+ );
147
+ ui.newLine();
148
+
149
+ if (!report.hasDrift) {
150
+ ui.success('No drift detected. Infrastructure is in sync.');
151
+ return;
152
+ }
153
+
154
+ // Summary
155
+ ui.print(' Changes:');
156
+ if (report.summary.added > 0) {
157
+ ui.print(` ${ui.color('+', 'green')} Added: ${report.summary.added}`);
158
+ }
159
+ if (report.summary.removed > 0) {
160
+ ui.print(` ${ui.color('-', 'red')} Removed: ${report.summary.removed}`);
161
+ }
162
+ if (report.summary.modified > 0) {
163
+ ui.print(` ${ui.color('~', 'yellow')} Modified: ${report.summary.modified}`);
164
+ }
165
+ ui.newLine();
166
+
167
+ // Resource Details
168
+ ui.section('Resources with Drift');
169
+
170
+ for (const resource of report.resources) {
171
+ ui.newLine();
172
+ ui.print(` ${formatDriftType(resource.driftType)} ${ui.bold(resource.resourceId)}`);
173
+ ui.print(` ${ui.dim('Type:')} ${resource.resourceType}`);
174
+ if (resource.name) {
175
+ ui.print(` ${ui.dim('Name:')} ${resource.name}`);
176
+ }
177
+
178
+ if (resource.changes.length > 0) {
179
+ ui.print(` ${ui.dim('Changes:')}`);
180
+ for (const change of resource.changes.slice(0, 5)) {
181
+ const expected = change.expected !== undefined ? JSON.stringify(change.expected) : 'null';
182
+ const actual = change.actual !== undefined ? JSON.stringify(change.actual) : 'null';
183
+ ui.print(
184
+ ` ${ui.dim(change.attribute)}: ${ui.color(expected, 'red')} -> ${ui.color(actual, 'green')}`
185
+ );
186
+ }
187
+ if (resource.changes.length > 5) {
188
+ ui.print(ui.dim(` ... and ${resource.changes.length - 5} more changes`));
189
+ }
190
+ }
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Display remediation result
196
+ */
197
+ function displayRemediationResult(result: DriftRemediationResult): void {
198
+ ui.newLine();
199
+ ui.section('Remediation Result');
200
+
201
+ const statusColor = result.success ? 'green' : 'red';
202
+ ui.print(
203
+ ` ${ui.dim('Status:')} ${ui.color(result.success ? 'Success' : 'Failed', statusColor)}`
204
+ );
205
+ ui.print(` ${ui.dim('Applied:')} ${result.appliedCount}`);
206
+ ui.print(` ${ui.dim('Failed:')} ${result.failedCount}`);
207
+ ui.print(` ${ui.dim('Skipped:')} ${result.skippedCount}`);
208
+ ui.newLine();
209
+
210
+ if (result.actions.length > 0) {
211
+ ui.section('Actions Taken');
212
+
213
+ for (const action of result.actions) {
214
+ const icon =
215
+ action.status === 'applied'
216
+ ? ui.color('✓', 'green')
217
+ : action.status === 'failed'
218
+ ? ui.color('✗', 'red')
219
+ : ui.color('○', 'dim');
220
+
221
+ ui.print(` ${icon} ${action.description}`);
222
+ if (action.error) {
223
+ ui.print(` ${ui.color('Error:', 'red')} ${action.error}`);
224
+ }
225
+ }
226
+ }
227
+
228
+ if (result.report) {
229
+ ui.newLine();
230
+ ui.print(ui.dim('Full report:'));
231
+ ui.print(result.report);
232
+ }
233
+ }
234
+
235
+ // ==========================================
236
+ // Commands
237
+ // ==========================================
238
+
239
+ /**
240
+ * Drift parent command
241
+ */
242
+ export async function driftCommand(args: string[]): Promise<void> {
243
+ if (args.length === 0) {
244
+ ui.header('Nimbus Drift', 'Infrastructure drift detection and remediation');
245
+ ui.newLine();
246
+ ui.print('Usage: nimbus drift <command> [options]');
247
+ ui.newLine();
248
+ ui.print('Commands:');
249
+ ui.print(` ${ui.bold('detect')} Detect infrastructure drift`);
250
+ ui.print(` ${ui.bold('fix')} Fix detected drift`);
251
+ ui.newLine();
252
+ ui.print('Examples:');
253
+ ui.print(' nimbus drift detect --provider terraform');
254
+ ui.print(' nimbus drift detect kubernetes -d ./manifests');
255
+ ui.print(' nimbus drift fix terraform --auto-approve');
256
+ ui.print(' nimbus drift fix --dry-run');
257
+ return;
258
+ }
259
+
260
+ const subcommand = args[0];
261
+ const subArgs = args.slice(1);
262
+
263
+ switch (subcommand) {
264
+ case 'detect':
265
+ await driftDetectCommand(parseDriftDetectOptions(subArgs));
266
+ break;
267
+ case 'fix':
268
+ await driftFixCommand(parseDriftFixOptions(subArgs));
269
+ break;
270
+ default:
271
+ ui.error(`Unknown drift command: ${subcommand}`);
272
+ ui.info('Run "nimbus drift" for usage');
273
+ }
274
+ }
275
+
276
+ /**
277
+ * Detect drift command
278
+ */
279
+ export async function driftDetectCommand(options: DriftDetectOptions): Promise<void> {
280
+ const directory = options.directory || process.cwd();
281
+ let provider = options.provider;
282
+
283
+ ui.header('Nimbus Drift Detect', directory);
284
+
285
+ // If no provider specified, try to detect or ask
286
+ if (!provider) {
287
+ const providerChoice = await select({
288
+ message: 'Select infrastructure provider to check:',
289
+ options: [
290
+ { label: 'Terraform', value: 'terraform', description: 'Check Terraform state drift' },
291
+ {
292
+ label: 'Kubernetes',
293
+ value: 'kubernetes',
294
+ description: 'Check Kubernetes manifest drift',
295
+ },
296
+ { label: 'Helm', value: 'helm', description: 'Check Helm release drift' },
297
+ ],
298
+ });
299
+ provider = providerChoice as DriftProvider;
300
+ }
301
+
302
+ ui.startSpinner({ message: `Detecting ${provider} drift...` });
303
+
304
+ try {
305
+ const client = new CoreEngineClient();
306
+ const report = await client.detectDrift({
307
+ provider,
308
+ directory,
309
+ });
310
+
311
+ ui.stopSpinnerSuccess('Drift detection complete');
312
+
313
+ if (options.json) {
314
+ console.log(JSON.stringify(report, null, 2));
315
+ return;
316
+ }
317
+
318
+ displayDriftReport(report);
319
+
320
+ if (report.hasDrift) {
321
+ ui.newLine();
322
+ ui.info('Run "nimbus drift fix" to remediate detected drift');
323
+ }
324
+ } catch (error) {
325
+ ui.stopSpinnerFail('Drift detection failed');
326
+ ui.error((error as Error).message);
327
+ }
328
+ }
329
+
330
+ /**
331
+ * Fix drift command
332
+ */
333
+ export async function driftFixCommand(options: DriftFixOptions): Promise<void> {
334
+ const directory = options.directory || process.cwd();
335
+ let provider = options.provider;
336
+
337
+ ui.header('Nimbus Drift Fix', directory);
338
+
339
+ // If no provider specified, ask
340
+ if (!provider) {
341
+ const providerChoice = await select({
342
+ message: 'Select infrastructure provider to fix:',
343
+ options: [
344
+ { label: 'Terraform', value: 'terraform', description: 'Fix Terraform state drift' },
345
+ { label: 'Kubernetes', value: 'kubernetes', description: 'Fix Kubernetes manifest drift' },
346
+ { label: 'Helm', value: 'helm', description: 'Fix Helm release drift' },
347
+ ],
348
+ });
349
+ provider = providerChoice as DriftProvider;
350
+ }
351
+
352
+ // First detect drift
353
+ ui.startSpinner({ message: `Detecting ${provider} drift...` });
354
+
355
+ let report: DriftReport;
356
+ try {
357
+ const client = new CoreEngineClient();
358
+ report = await client.detectDrift({
359
+ provider,
360
+ directory,
361
+ });
362
+ ui.stopSpinnerSuccess('Drift detection complete');
363
+ } catch (error) {
364
+ ui.stopSpinnerFail('Drift detection failed');
365
+ ui.error((error as Error).message);
366
+ return;
367
+ }
368
+
369
+ if (!report.hasDrift) {
370
+ ui.newLine();
371
+ ui.success('No drift detected. Nothing to fix.');
372
+ return;
373
+ }
374
+
375
+ // Show what will be fixed
376
+ displayDriftReport(report);
377
+
378
+ // Confirm before fixing (unless auto-approve or dry-run)
379
+ if (!options.autoApprove && !options.dryRun) {
380
+ ui.newLine();
381
+ const proceed = await confirm({
382
+ message: `Apply ${report.summary.total} remediation actions?`,
383
+ defaultValue: false,
384
+ });
385
+
386
+ if (!proceed) {
387
+ ui.info('Fix cancelled.');
388
+ return;
389
+ }
390
+ }
391
+
392
+ if (options.dryRun) {
393
+ ui.newLine();
394
+ ui.info('Dry run mode - no changes will be applied');
395
+ ui.newLine();
396
+
397
+ // Show what would be done
398
+ ui.section('Actions that would be taken:');
399
+ for (const resource of report.resources) {
400
+ ui.print(` ${formatDriftType(resource.driftType)} ${resource.resourceId}`);
401
+ if (resource.driftType === 'added') {
402
+ ui.print(` ${ui.dim('Would be removed from actual state')}`);
403
+ } else if (resource.driftType === 'removed') {
404
+ ui.print(` ${ui.dim('Would be recreated')}`);
405
+ } else {
406
+ ui.print(` ${ui.dim('Would be updated to match desired state')}`);
407
+ }
408
+ }
409
+ return;
410
+ }
411
+
412
+ // Apply fixes
413
+ ui.startSpinner({ message: 'Applying remediation...' });
414
+
415
+ try {
416
+ const client = new CoreEngineClient();
417
+ const result = await client.fixDrift({
418
+ provider,
419
+ directory,
420
+ dryRun: false,
421
+ });
422
+
423
+ if (result.success) {
424
+ ui.stopSpinnerSuccess('Remediation complete');
425
+ } else {
426
+ ui.stopSpinnerFail('Remediation partially failed');
427
+ }
428
+
429
+ if (options.json) {
430
+ console.log(JSON.stringify(result, null, 2));
431
+ return;
432
+ }
433
+
434
+ displayRemediationResult(result);
435
+ } catch (error) {
436
+ ui.stopSpinnerFail('Remediation failed');
437
+ ui.error((error as Error).message);
438
+ }
439
+ }
@@ -0,0 +1,277 @@
1
+ /**
2
+ * Explain Command
3
+ *
4
+ * Get AI explanations for code, infrastructure, or errors
5
+ *
6
+ * Usage: nimbus explain <target> [options]
7
+ */
8
+
9
+ import { logger } from '../utils';
10
+ import { ui } from '../wizard';
11
+ import { llmClient } from '../clients';
12
+
13
+ /**
14
+ * Content type for explanation
15
+ */
16
+ export type ExplainType = 'code' | 'infra' | 'error' | 'auto';
17
+
18
+ /**
19
+ * Command options
20
+ */
21
+ export interface ExplainOptions {
22
+ type?: ExplainType;
23
+ file?: string;
24
+ verbose?: boolean;
25
+ json?: boolean;
26
+ }
27
+
28
+ /**
29
+ * Detect content type from target
30
+ */
31
+ function detectExplainType(target: string, content?: string): ExplainType {
32
+ // Check for error patterns
33
+ const errorPatterns = [
34
+ /^error:/i,
35
+ /^exception:/i,
36
+ /failed/i,
37
+ /traceback/i,
38
+ /stack trace/i,
39
+ /undefined variable/i,
40
+ /cannot find/i,
41
+ /not found/i,
42
+ /invalid/i,
43
+ /permission denied/i,
44
+ ];
45
+
46
+ const targetLower = target.toLowerCase();
47
+
48
+ if (errorPatterns.some(p => p.test(target) || (content && p.test(content)))) {
49
+ return 'error';
50
+ }
51
+
52
+ // Check for infrastructure file extensions
53
+ const infraExtensions = ['.tf', '.yaml', '.yml', '.json', '.hcl', '.toml'];
54
+ if (infraExtensions.some(ext => targetLower.endsWith(ext))) {
55
+ return 'infra';
56
+ }
57
+
58
+ // Check content for infrastructure patterns
59
+ if (content) {
60
+ if (content.includes('apiVersion:') && content.includes('kind:')) {
61
+ return 'infra'; // Kubernetes
62
+ }
63
+ if (content.includes('resource ') && content.includes('provider ')) {
64
+ return 'infra'; // Terraform
65
+ }
66
+ if (content.includes('AWSTemplateFormatVersion')) {
67
+ return 'infra'; // CloudFormation
68
+ }
69
+ }
70
+
71
+ // Check for code file extensions
72
+ const codeExtensions = [
73
+ '.ts',
74
+ '.js',
75
+ '.py',
76
+ '.go',
77
+ '.java',
78
+ '.rs',
79
+ '.rb',
80
+ '.php',
81
+ '.c',
82
+ '.cpp',
83
+ '.cs',
84
+ ];
85
+ if (codeExtensions.some(ext => targetLower.endsWith(ext))) {
86
+ return 'code';
87
+ }
88
+
89
+ // Default to code for unknown content
90
+ return 'code';
91
+ }
92
+
93
+ /**
94
+ * Build the explanation prompt based on type
95
+ */
96
+ function buildPrompt(type: ExplainType, content: string, verbose: boolean): string {
97
+ const detailLevel = verbose ? 'detailed' : 'concise';
98
+
99
+ switch (type) {
100
+ case 'error':
101
+ return `Please explain this error and suggest how to fix it. Provide a ${detailLevel} explanation.
102
+
103
+ Error:
104
+ \`\`\`
105
+ ${content}
106
+ \`\`\`
107
+
108
+ Include:
109
+ 1. What the error means
110
+ 2. Why it might have occurred
111
+ 3. How to fix it
112
+ 4. How to prevent it in the future`;
113
+
114
+ case 'infra':
115
+ return `Please explain this infrastructure configuration. Provide a ${detailLevel} explanation.
116
+
117
+ Configuration:
118
+ \`\`\`
119
+ ${content}
120
+ \`\`\`
121
+
122
+ Include:
123
+ 1. What this configuration does
124
+ 2. Key components and their purpose
125
+ 3. Any potential issues or improvements
126
+ ${verbose ? '4. Security considerations\n5. Best practices recommendations' : ''}`;
127
+
128
+ case 'code':
129
+ default:
130
+ return `Please explain this code. Provide a ${detailLevel} explanation.
131
+
132
+ Code:
133
+ \`\`\`
134
+ ${content}
135
+ \`\`\`
136
+
137
+ Include:
138
+ 1. What the code does
139
+ 2. How it works
140
+ 3. Any potential issues
141
+ ${verbose ? '4. Suggestions for improvement\n5. Related best practices' : ''}`;
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Run the explain command
147
+ */
148
+ export async function explainCommand(target: string, options: ExplainOptions = {}): Promise<void> {
149
+ logger.info('Running explain command', { target, options });
150
+
151
+ let content: string;
152
+ let filePath: string | undefined;
153
+
154
+ // Get content to explain
155
+ if (options.file) {
156
+ // Read from specified file
157
+ filePath = options.file;
158
+ try {
159
+ const fs = await import('fs/promises');
160
+ content = await fs.readFile(filePath, 'utf-8');
161
+ } catch (error: any) {
162
+ ui.error(`Could not read file: ${error.message}`);
163
+ process.exit(1);
164
+ }
165
+ } else if (target) {
166
+ // Check if target is a file path
167
+ try {
168
+ const fs = await import('fs/promises');
169
+ const stat = await fs.stat(target);
170
+ if (stat.isFile()) {
171
+ filePath = target;
172
+ content = await fs.readFile(target, 'utf-8');
173
+ } else if (stat.isDirectory()) {
174
+ ui.error('Please specify a file, not a directory');
175
+ ui.info('Usage: nimbus explain <file> or nimbus explain "error message"');
176
+ process.exit(1);
177
+ } else {
178
+ content = target;
179
+ }
180
+ } catch {
181
+ // Not a file, use as content directly
182
+ content = target;
183
+ }
184
+ } else {
185
+ ui.error('Please provide something to explain');
186
+ ui.newLine();
187
+ ui.print('Usage: nimbus explain <target> [options]');
188
+ ui.newLine();
189
+ ui.print('Examples:');
190
+ ui.print(' nimbus explain ./main.tf');
191
+ ui.print(' nimbus explain "Error: resource not found" --type error');
192
+ ui.print(' nimbus explain --file ./deployment.yaml');
193
+ process.exit(1);
194
+ }
195
+
196
+ // Limit content size
197
+ const maxLength = 10000;
198
+ if (content.length > maxLength) {
199
+ ui.warning(`Content truncated to ${maxLength} characters`);
200
+ content = `${content.slice(0, maxLength)}\n... (content truncated)`;
201
+ }
202
+
203
+ // Detect or use specified type
204
+ const type =
205
+ options.type === 'auto' || !options.type ? detectExplainType(target, content) : options.type;
206
+
207
+ // Display header
208
+ ui.header('Nimbus Explain');
209
+ if (filePath) {
210
+ ui.info(`File: ${filePath}`);
211
+ }
212
+ ui.info(`Type: ${type}`);
213
+ ui.newLine();
214
+
215
+ // Check if LLM is available
216
+ const llmAvailable = await llmClient.isAvailable();
217
+
218
+ if (!llmAvailable) {
219
+ ui.error('LLM service is not available');
220
+ ui.info('Make sure you have configured an LLM provider with "nimbus login"');
221
+ process.exit(1);
222
+ }
223
+
224
+ // Build prompt
225
+ const prompt = buildPrompt(type, content, options.verbose ?? false);
226
+
227
+ ui.startSpinner({ message: 'Analyzing...' });
228
+
229
+ try {
230
+ let response = '';
231
+ let firstChunk = true;
232
+
233
+ for await (const chunk of llmClient.chat(prompt, [])) {
234
+ if (chunk.type === 'content' && chunk.content) {
235
+ if (firstChunk) {
236
+ ui.stopSpinnerSuccess('');
237
+ ui.newLine();
238
+ firstChunk = false;
239
+ }
240
+
241
+ response += chunk.content;
242
+ process.stdout.write(chunk.content);
243
+ } else if (chunk.type === 'error') {
244
+ ui.stopSpinnerFail('Error');
245
+ ui.error(chunk.message || chunk.error || 'Unknown error');
246
+ process.exit(1);
247
+ }
248
+ }
249
+
250
+ // Ensure newline at end
251
+ if (!response.endsWith('\n')) {
252
+ ui.newLine();
253
+ }
254
+
255
+ // JSON output mode
256
+ if (options.json) {
257
+ console.log(
258
+ JSON.stringify(
259
+ {
260
+ type,
261
+ file: filePath,
262
+ explanation: response,
263
+ },
264
+ null,
265
+ 2
266
+ )
267
+ );
268
+ }
269
+ } catch (error: any) {
270
+ ui.stopSpinnerFail('Failed');
271
+ ui.error(error.message);
272
+ process.exit(1);
273
+ }
274
+ }
275
+
276
+ // Export as default
277
+ export default explainCommand;