@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,116 @@
1
+ /**
2
+ * Azure CLI Commands
3
+ *
4
+ * Wrapper for Azure CLI operations with enhanced output and safety checks
5
+ *
6
+ * Usage:
7
+ * nimbus azure vm list
8
+ * nimbus azure storage account list
9
+ * nimbus azure aks list
10
+ * nimbus azure functions list
11
+ */
12
+
13
+ import { logger } from '../../utils';
14
+ import { ui } from '../../wizard/ui';
15
+ import { vmCommand } from './vm';
16
+ import { storageCommand } from './storage';
17
+ import { aksCommand } from './aks';
18
+ import { functionsCommand } from './functions';
19
+
20
+ export interface AzureCommandOptions {
21
+ subscription?: string;
22
+ resourceGroup?: string;
23
+ output?: 'json' | 'table' | 'tsv';
24
+ }
25
+
26
+ /**
27
+ * Parse common Azure options from args
28
+ */
29
+ export function parseAzureOptions(args: string[]): AzureCommandOptions {
30
+ const options: AzureCommandOptions = {};
31
+
32
+ for (let i = 0; i < args.length; i++) {
33
+ const arg = args[i];
34
+
35
+ if ((arg === '--subscription' || arg === '-s') && args[i + 1]) {
36
+ options.subscription = args[++i];
37
+ } else if ((arg === '--resource-group' || arg === '-g') && args[i + 1]) {
38
+ options.resourceGroup = args[++i];
39
+ } else if ((arg === '--output' || arg === '-o') && args[i + 1]) {
40
+ options.output = args[++i] as 'json' | 'table' | 'tsv';
41
+ }
42
+ }
43
+
44
+ return options;
45
+ }
46
+
47
+ /**
48
+ * Main Azure command router
49
+ */
50
+ export async function azureCommand(subcommand: string, args: string[]): Promise<void> {
51
+ logger.info('Running Azure command', { subcommand, args });
52
+
53
+ const options = parseAzureOptions(args);
54
+ const positionalArgs = args.filter(arg => !arg.startsWith('-') && !arg.startsWith('--'));
55
+
56
+ switch (subcommand) {
57
+ case 'vm':
58
+ await vmCommand(positionalArgs[0], positionalArgs.slice(1), options);
59
+ break;
60
+
61
+ case 'storage':
62
+ await storageCommand(positionalArgs[0], positionalArgs.slice(1), options);
63
+ break;
64
+
65
+ case 'aks':
66
+ await aksCommand(positionalArgs[0], positionalArgs.slice(1), options);
67
+ break;
68
+
69
+ case 'functions':
70
+ await functionsCommand(positionalArgs[0], positionalArgs.slice(1), options);
71
+ break;
72
+
73
+ default:
74
+ showAzureHelp();
75
+ break;
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Show Azure command help
81
+ */
82
+ function showAzureHelp(): void {
83
+ ui.header('Nimbus Azure Commands');
84
+ ui.newLine();
85
+
86
+ ui.print('Usage: nimbus azure <service> <action> [options]');
87
+ ui.newLine();
88
+
89
+ ui.print(ui.bold('Services:'));
90
+ ui.print(' vm Virtual Machine operations');
91
+ ui.print(' storage Storage account and blob operations');
92
+ ui.print(' aks Azure Kubernetes Service operations');
93
+ ui.print(' functions Azure Functions operations');
94
+ ui.newLine();
95
+
96
+ ui.print(ui.bold('Common Options:'));
97
+ ui.print(' --subscription, -s Azure subscription ID');
98
+ ui.print(' --resource-group, -g Resource group name');
99
+ ui.print(' --output, -o Output format (json, table, tsv)');
100
+ ui.newLine();
101
+
102
+ ui.print(ui.bold('Examples:'));
103
+ ui.print(' nimbus azure vm list List all VMs');
104
+ ui.print(' nimbus azure vm show my-vm -g my-rg Show VM details');
105
+ ui.print(' nimbus azure storage account list List storage accounts');
106
+ ui.print(' nimbus azure aks list List AKS clusters');
107
+ ui.print(' nimbus azure functions list List function apps');
108
+ }
109
+
110
+ // Re-export subcommands
111
+ export { vmCommand } from './vm';
112
+ export { storageCommand } from './storage';
113
+ export { aksCommand } from './aks';
114
+ export { functionsCommand } from './functions';
115
+
116
+ export default azureCommand;
@@ -0,0 +1,478 @@
1
+ /**
2
+ * Azure Storage CLI Commands
3
+ *
4
+ * Operations for Azure Storage accounts and blobs
5
+ */
6
+
7
+ import { execFile } from 'child_process';
8
+ import { promisify } from 'util';
9
+ import { logger } from '../../utils';
10
+ import { ui } from '../../wizard/ui';
11
+ import {
12
+ loadSafetyPolicy,
13
+ evaluateSafety,
14
+ type SafetyContext,
15
+ type SafetyCheckResult,
16
+ } from '../../config/safety-policy';
17
+ import { promptForApproval, displaySafetySummary } from '../../wizard/approval';
18
+ import type { AzureCommandOptions } from './index';
19
+
20
+ const execFileAsync = promisify(execFile);
21
+
22
+ /**
23
+ * Run storage safety checks
24
+ */
25
+ async function runStorageSafetyChecks(
26
+ action: string,
27
+ target: string,
28
+ options: AzureCommandOptions
29
+ ): Promise<SafetyCheckResult> {
30
+ const safetyPolicy = loadSafetyPolicy();
31
+
32
+ const context: SafetyContext = {
33
+ operation: action,
34
+ type: 'azure',
35
+ environment: options.subscription || 'default',
36
+ resources: [target],
37
+ metadata: {
38
+ resourceType: 'storage',
39
+ resourceGroup: options.resourceGroup,
40
+ },
41
+ };
42
+
43
+ return evaluateSafety(context, safetyPolicy);
44
+ }
45
+
46
+ /**
47
+ * Main Storage command router
48
+ */
49
+ export async function storageCommand(
50
+ action: string,
51
+ args: string[],
52
+ options: AzureCommandOptions
53
+ ): Promise<void> {
54
+ logger.info('Running Azure Storage command', { action, args, options });
55
+
56
+ switch (action) {
57
+ case 'account':
58
+ await accountCommand(args[0], args.slice(1), options);
59
+ break;
60
+
61
+ case 'container':
62
+ await containerCommand(args[0], args.slice(1), options);
63
+ break;
64
+
65
+ case 'blob':
66
+ await blobCommand(args[0], args.slice(1), options);
67
+ break;
68
+
69
+ default:
70
+ showStorageHelp();
71
+ break;
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Storage account subcommand
77
+ */
78
+ async function accountCommand(
79
+ action: string,
80
+ args: string[],
81
+ options: AzureCommandOptions
82
+ ): Promise<void> {
83
+ switch (action) {
84
+ case 'list':
85
+ await listAccounts(options);
86
+ break;
87
+
88
+ case 'show':
89
+ if (!args[0]) {
90
+ ui.error('Account name required');
91
+ return;
92
+ }
93
+ await showAccount(args[0], options);
94
+ break;
95
+
96
+ default:
97
+ ui.print(ui.bold('Storage Account Commands:'));
98
+ ui.print(' list List all storage accounts');
99
+ ui.print(' show <name> -g <rg> Show account details');
100
+ break;
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Container subcommand
106
+ */
107
+ async function containerCommand(
108
+ action: string,
109
+ args: string[],
110
+ options: AzureCommandOptions
111
+ ): Promise<void> {
112
+ switch (action) {
113
+ case 'list':
114
+ if (!args[0]) {
115
+ ui.error('Account name required');
116
+ return;
117
+ }
118
+ await listContainers(args[0], options);
119
+ break;
120
+
121
+ case 'delete':
122
+ if (!args[0] || !args[1]) {
123
+ ui.error('Account name and container name required');
124
+ return;
125
+ }
126
+ await deleteContainer(args[0], args[1], options);
127
+ break;
128
+
129
+ default:
130
+ ui.print(ui.bold('Container Commands:'));
131
+ ui.print(' list <account> List containers in account');
132
+ ui.print(' delete <account> <container> Delete container (requires approval)');
133
+ break;
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Blob subcommand
139
+ */
140
+ async function blobCommand(
141
+ action: string,
142
+ args: string[],
143
+ options: AzureCommandOptions
144
+ ): Promise<void> {
145
+ switch (action) {
146
+ case 'list':
147
+ if (!args[0] || !args[1]) {
148
+ ui.error('Account name and container name required');
149
+ return;
150
+ }
151
+ await listBlobs(args[0], args[1], options);
152
+ break;
153
+
154
+ default:
155
+ ui.print(ui.bold('Blob Commands:'));
156
+ ui.print(' list <account> <container> List blobs in container');
157
+ break;
158
+ }
159
+ }
160
+
161
+ /**
162
+ * List storage accounts
163
+ */
164
+ async function listAccounts(options: AzureCommandOptions): Promise<void> {
165
+ ui.header('Azure Storage Accounts');
166
+ ui.newLine();
167
+
168
+ const azArgs = ['storage', 'account', 'list', '-o', 'json'];
169
+ if (options.subscription) {
170
+ azArgs.push('--subscription', options.subscription);
171
+ }
172
+ if (options.resourceGroup) {
173
+ azArgs.push('-g', options.resourceGroup);
174
+ }
175
+
176
+ try {
177
+ ui.startSpinner({ message: 'Fetching storage accounts...' });
178
+ const { stdout } = await execFileAsync('az', azArgs);
179
+ ui.stopSpinnerSuccess('Accounts fetched');
180
+
181
+ const accounts = JSON.parse(stdout || '[]');
182
+
183
+ if (accounts.length === 0) {
184
+ ui.info('No storage accounts found');
185
+ return;
186
+ }
187
+
188
+ ui.print(`Found ${accounts.length} account(s)\n`);
189
+
190
+ // Display table
191
+ ui.print(
192
+ ui.color(
193
+ `${
194
+ 'Name'.padEnd(30) +
195
+ 'Resource Group'.padEnd(25) +
196
+ 'Location'.padEnd(15) +
197
+ 'Kind'.padEnd(15)
198
+ }Status`,
199
+ 'cyan'
200
+ )
201
+ );
202
+ ui.print('─'.repeat(100));
203
+
204
+ for (const account of accounts) {
205
+ const name = account.name?.substring(0, 29) || '';
206
+ const rg = account.resourceGroup?.substring(0, 24) || '';
207
+ const location = account.location?.substring(0, 14) || '';
208
+ const kind = account.kind?.substring(0, 14) || '';
209
+ const status = account.provisioningState || '';
210
+
211
+ const statusColor = status === 'Succeeded' ? 'green' : status === 'Failed' ? 'red' : 'white';
212
+
213
+ ui.print(
214
+ `${name.padEnd(30)}${rg.padEnd(25)}${location.padEnd(15)}${kind.padEnd(15)}${ui.color(status, statusColor as 'green' | 'red' | 'white')}`
215
+ );
216
+ }
217
+ } catch (error: unknown) {
218
+ ui.stopSpinnerFail('Failed to fetch accounts');
219
+ const message = error instanceof Error ? error.message : 'Unknown error';
220
+ logger.error('Failed to list storage accounts', { error: message });
221
+ ui.error(`Failed to list accounts: ${message}`);
222
+ }
223
+ }
224
+
225
+ /**
226
+ * Show storage account details
227
+ */
228
+ async function showAccount(accountName: string, options: AzureCommandOptions): Promise<void> {
229
+ ui.header(`Storage Account: ${accountName}`);
230
+ ui.newLine();
231
+
232
+ if (!options.resourceGroup) {
233
+ ui.error('Resource group required (use -g)');
234
+ return;
235
+ }
236
+
237
+ const azArgs = [
238
+ 'storage',
239
+ 'account',
240
+ 'show',
241
+ '-n',
242
+ accountName,
243
+ '-g',
244
+ options.resourceGroup,
245
+ '-o',
246
+ 'json',
247
+ ];
248
+ if (options.subscription) {
249
+ azArgs.push('--subscription', options.subscription);
250
+ }
251
+
252
+ try {
253
+ ui.startSpinner({ message: 'Fetching account details...' });
254
+ const { stdout } = await execFileAsync('az', azArgs);
255
+ ui.stopSpinnerSuccess('Details fetched');
256
+
257
+ const account = JSON.parse(stdout);
258
+
259
+ ui.print(ui.bold('Basic Information:'));
260
+ ui.print(` Name: ${account.name}`);
261
+ ui.print(` Resource Group: ${account.resourceGroup}`);
262
+ ui.print(` Location: ${account.location}`);
263
+ ui.print(` Kind: ${account.kind}`);
264
+ ui.print(` Status: ${account.provisioningState}`);
265
+ ui.newLine();
266
+
267
+ ui.print(ui.bold('Properties:'));
268
+ ui.print(` SKU: ${account.sku?.name}`);
269
+ ui.print(` Tier: ${account.sku?.tier}`);
270
+ ui.print(` Access Tier: ${account.accessTier || 'N/A'}`);
271
+ ui.print(` HTTPS Only: ${account.enableHttpsTrafficOnly ? 'Yes' : 'No'}`);
272
+ ui.newLine();
273
+
274
+ ui.print(ui.bold('Encryption:'));
275
+ ui.print(` Services: ${Object.keys(account.encryption?.services || {}).join(', ')}`);
276
+ ui.print(` Key Source: ${account.encryption?.keySource}`);
277
+ ui.newLine();
278
+
279
+ ui.print(ui.bold('Endpoints:'));
280
+ const endpoints = account.primaryEndpoints || {};
281
+ if (endpoints.blob) {
282
+ ui.print(` Blob: ${endpoints.blob}`);
283
+ }
284
+ if (endpoints.file) {
285
+ ui.print(` File: ${endpoints.file}`);
286
+ }
287
+ if (endpoints.queue) {
288
+ ui.print(` Queue: ${endpoints.queue}`);
289
+ }
290
+ if (endpoints.table) {
291
+ ui.print(` Table: ${endpoints.table}`);
292
+ }
293
+ } catch (error: unknown) {
294
+ ui.stopSpinnerFail('Failed to fetch details');
295
+ const message = error instanceof Error ? error.message : 'Unknown error';
296
+ logger.error('Failed to show account', { error: message });
297
+ ui.error(`Failed to show account: ${message}`);
298
+ }
299
+ }
300
+
301
+ /**
302
+ * List containers in a storage account
303
+ */
304
+ async function listContainers(accountName: string, options: AzureCommandOptions): Promise<void> {
305
+ ui.header(`Containers in ${accountName}`);
306
+ ui.newLine();
307
+
308
+ const azArgs = [
309
+ 'storage',
310
+ 'container',
311
+ 'list',
312
+ '--account-name',
313
+ accountName,
314
+ '-o',
315
+ 'json',
316
+ '--auth-mode',
317
+ 'login',
318
+ ];
319
+ if (options.subscription) {
320
+ azArgs.push('--subscription', options.subscription);
321
+ }
322
+
323
+ try {
324
+ ui.startSpinner({ message: 'Fetching containers...' });
325
+ const { stdout } = await execFileAsync('az', azArgs);
326
+ ui.stopSpinnerSuccess('Containers fetched');
327
+
328
+ const containers = JSON.parse(stdout || '[]');
329
+
330
+ if (containers.length === 0) {
331
+ ui.info('No containers found');
332
+ return;
333
+ }
334
+
335
+ ui.print(`Found ${containers.length} container(s)\n`);
336
+
337
+ for (const container of containers) {
338
+ ui.print(` ${ui.color(container.name, 'cyan')}`);
339
+ if (container.properties?.lastModified) {
340
+ ui.print(ui.dim(` Last Modified: ${container.properties.lastModified}`));
341
+ }
342
+ }
343
+ } catch (error: unknown) {
344
+ ui.stopSpinnerFail('Failed to fetch containers');
345
+ const message = error instanceof Error ? error.message : 'Unknown error';
346
+ logger.error('Failed to list containers', { error: message });
347
+ ui.error(`Failed to list containers: ${message}`);
348
+ }
349
+ }
350
+
351
+ /**
352
+ * Delete a container (requires safety approval)
353
+ */
354
+ async function deleteContainer(
355
+ accountName: string,
356
+ containerName: string,
357
+ options: AzureCommandOptions
358
+ ): Promise<void> {
359
+ // Run safety checks
360
+ const safetyResult = await runStorageSafetyChecks(
361
+ 'delete',
362
+ `${accountName}/${containerName}`,
363
+ options
364
+ );
365
+
366
+ displaySafetySummary({
367
+ operation: `delete container ${containerName}`,
368
+ risks: safetyResult.risks,
369
+ passed: safetyResult.passed,
370
+ });
371
+
372
+ if (safetyResult.requiresApproval) {
373
+ const result = await promptForApproval({
374
+ title: 'Delete Storage Container',
375
+ operation: `az storage container delete --name ${containerName} --account-name ${accountName}`,
376
+ risks: safetyResult.risks,
377
+ });
378
+
379
+ if (!result.approved) {
380
+ ui.warning('Operation cancelled');
381
+ return;
382
+ }
383
+ }
384
+
385
+ const azArgs = [
386
+ 'storage',
387
+ 'container',
388
+ 'delete',
389
+ '--name',
390
+ containerName,
391
+ '--account-name',
392
+ accountName,
393
+ '--auth-mode',
394
+ 'login',
395
+ ];
396
+ if (options.subscription) {
397
+ azArgs.push('--subscription', options.subscription);
398
+ }
399
+
400
+ try {
401
+ ui.startSpinner({ message: `Deleting container ${containerName}...` });
402
+ await execFileAsync('az', azArgs);
403
+ ui.stopSpinnerSuccess(`Container ${containerName} deleted`);
404
+ } catch (error: unknown) {
405
+ ui.stopSpinnerFail('Failed to delete container');
406
+ const message = error instanceof Error ? error.message : 'Unknown error';
407
+ logger.error('Failed to delete container', { error: message });
408
+ ui.error(`Failed to delete container: ${message}`);
409
+ }
410
+ }
411
+
412
+ /**
413
+ * List blobs in a container
414
+ */
415
+ async function listBlobs(
416
+ accountName: string,
417
+ containerName: string,
418
+ options: AzureCommandOptions
419
+ ): Promise<void> {
420
+ ui.header(`Blobs in ${accountName}/${containerName}`);
421
+ ui.newLine();
422
+
423
+ const azArgs = [
424
+ 'storage',
425
+ 'blob',
426
+ 'list',
427
+ '--container-name',
428
+ containerName,
429
+ '--account-name',
430
+ accountName,
431
+ '-o',
432
+ 'json',
433
+ '--auth-mode',
434
+ 'login',
435
+ ];
436
+ if (options.subscription) {
437
+ azArgs.push('--subscription', options.subscription);
438
+ }
439
+
440
+ try {
441
+ ui.startSpinner({ message: 'Fetching blobs...' });
442
+ const { stdout } = await execFileAsync('az', azArgs);
443
+ ui.stopSpinnerSuccess('Blobs fetched');
444
+
445
+ const blobs = JSON.parse(stdout || '[]');
446
+
447
+ if (blobs.length === 0) {
448
+ ui.info('No blobs found');
449
+ return;
450
+ }
451
+
452
+ ui.print(`Found ${blobs.length} blob(s)\n`);
453
+
454
+ for (const blob of blobs) {
455
+ const size = blob.properties?.contentLength
456
+ ? `${Math.round(blob.properties.contentLength / 1024)} KB`
457
+ : '';
458
+ ui.print(` ${blob.name} ${ui.dim(size)}`);
459
+ }
460
+ } catch (error: unknown) {
461
+ ui.stopSpinnerFail('Failed to fetch blobs');
462
+ const message = error instanceof Error ? error.message : 'Unknown error';
463
+ logger.error('Failed to list blobs', { error: message });
464
+ ui.error(`Failed to list blobs: ${message}`);
465
+ }
466
+ }
467
+
468
+ /**
469
+ * Show Storage help
470
+ */
471
+ function showStorageHelp(): void {
472
+ ui.print(ui.bold('Storage Commands:'));
473
+ ui.print(' account list List storage accounts');
474
+ ui.print(' account show <name> -g <rg> Show account details');
475
+ ui.print(' container list <account> List containers');
476
+ ui.print(' container delete <account> <name> Delete container (requires approval)');
477
+ ui.print(' blob list <account> <container> List blobs');
478
+ }