@build-astron-co/nimbus 0.2.0 → 0.4.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 (469) hide show
  1. package/bin/nimbus +26 -10
  2. package/bin/nimbus.cmd +41 -0
  3. package/bin/nimbus.mjs +70 -0
  4. package/completions/nimbus.bash +38 -0
  5. package/completions/nimbus.fish +48 -0
  6. package/completions/nimbus.zsh +81 -0
  7. package/dist/src/agent/compaction-agent.js +215 -0
  8. package/dist/src/agent/context-manager.js +385 -0
  9. package/dist/src/agent/context.js +322 -0
  10. package/dist/src/agent/deploy-preview.js +395 -0
  11. package/dist/src/agent/expand-files.js +95 -0
  12. package/dist/src/agent/index.js +18 -0
  13. package/dist/src/agent/loop.js +1535 -0
  14. package/dist/src/agent/modes.js +347 -0
  15. package/dist/src/agent/permissions.js +396 -0
  16. package/dist/src/agent/subagents/base.js +67 -0
  17. package/dist/src/agent/subagents/cost.js +45 -0
  18. package/dist/src/agent/subagents/explore.js +36 -0
  19. package/dist/src/agent/subagents/general.js +41 -0
  20. package/dist/src/agent/subagents/index.js +88 -0
  21. package/dist/src/agent/subagents/infra.js +52 -0
  22. package/dist/src/agent/subagents/security.js +60 -0
  23. package/dist/src/agent/system-prompt.js +860 -0
  24. package/dist/src/app.js +152 -0
  25. package/dist/src/audit/activity-log.js +209 -0
  26. package/dist/src/audit/compliance-checker.js +419 -0
  27. package/dist/src/audit/cost-tracker.js +231 -0
  28. package/dist/src/audit/index.js +10 -0
  29. package/dist/src/audit/security-scanner.js +490 -0
  30. package/dist/src/auth/guard.js +64 -0
  31. package/dist/src/auth/index.js +19 -0
  32. package/dist/src/auth/keychain.js +79 -0
  33. package/dist/src/auth/oauth.js +389 -0
  34. package/dist/src/auth/providers.js +415 -0
  35. package/dist/src/auth/sso.js +87 -0
  36. package/dist/src/auth/store.js +424 -0
  37. package/dist/src/auth/types.js +5 -0
  38. package/dist/src/cli/index.js +8 -0
  39. package/dist/src/cli/init.js +1048 -0
  40. package/dist/src/cli/openapi-spec.js +346 -0
  41. package/dist/src/cli/run.js +505 -0
  42. package/dist/src/cli/serve-auth.js +56 -0
  43. package/dist/src/cli/serve.js +432 -0
  44. package/dist/src/cli/web.js +50 -0
  45. package/dist/src/cli.js +1574 -0
  46. package/dist/src/clients/core-engine-client.js +156 -0
  47. package/dist/src/clients/enterprise-client.js +246 -0
  48. package/dist/src/clients/generator-client.js +219 -0
  49. package/dist/src/clients/git-client.js +367 -0
  50. package/dist/src/clients/github-client.js +229 -0
  51. package/dist/src/clients/helm-client.js +299 -0
  52. package/dist/src/clients/index.js +18 -0
  53. package/dist/src/clients/k8s-client.js +270 -0
  54. package/dist/src/clients/llm-client.js +119 -0
  55. package/dist/src/clients/rest-client.js +104 -0
  56. package/dist/src/clients/service-discovery.js +35 -0
  57. package/dist/src/clients/terraform-client.js +302 -0
  58. package/dist/src/clients/tools-client.js +1227 -0
  59. package/dist/src/clients/ws-client.js +93 -0
  60. package/dist/src/commands/alias.js +91 -0
  61. package/dist/src/commands/analyze/index.js +313 -0
  62. package/dist/src/commands/apply/helm.js +375 -0
  63. package/dist/src/commands/apply/index.js +176 -0
  64. package/dist/src/commands/apply/k8s.js +350 -0
  65. package/dist/src/commands/apply/terraform.js +465 -0
  66. package/dist/src/commands/ask.js +137 -0
  67. package/dist/src/commands/audit/index.js +322 -0
  68. package/dist/src/commands/auth-cloud.js +345 -0
  69. package/dist/src/commands/auth-list.js +112 -0
  70. package/dist/src/commands/auth-profile.js +104 -0
  71. package/dist/src/commands/auth-refresh.js +161 -0
  72. package/dist/src/commands/auth-status.js +122 -0
  73. package/dist/src/commands/aws/ec2.js +402 -0
  74. package/dist/src/commands/aws/iam.js +304 -0
  75. package/dist/src/commands/aws/index.js +108 -0
  76. package/dist/src/commands/aws/lambda.js +317 -0
  77. package/dist/src/commands/aws/rds.js +345 -0
  78. package/dist/src/commands/aws/s3.js +346 -0
  79. package/dist/src/commands/aws/vpc.js +302 -0
  80. package/dist/src/commands/aws-discover.js +413 -0
  81. package/dist/src/commands/aws-terraform.js +618 -0
  82. package/dist/src/commands/azure/aks.js +305 -0
  83. package/dist/src/commands/azure/functions.js +200 -0
  84. package/dist/src/commands/azure/index.js +93 -0
  85. package/dist/src/commands/azure/storage.js +378 -0
  86. package/dist/src/commands/azure/vm.js +291 -0
  87. package/dist/src/commands/billing/index.js +224 -0
  88. package/dist/src/commands/chat.js +259 -0
  89. package/dist/src/commands/completions.js +255 -0
  90. package/dist/src/commands/config.js +291 -0
  91. package/dist/src/commands/cost/cloud-cost-estimator.js +211 -0
  92. package/dist/src/commands/cost/estimator.js +73 -0
  93. package/dist/src/commands/cost/index.js +625 -0
  94. package/dist/src/commands/cost/parsers/terraform.js +234 -0
  95. package/dist/src/commands/cost/parsers/types.js +4 -0
  96. package/dist/src/commands/cost/pricing/aws.js +501 -0
  97. package/dist/src/commands/cost/pricing/azure.js +462 -0
  98. package/dist/src/commands/cost/pricing/gcp.js +359 -0
  99. package/dist/src/commands/cost/pricing/index.js +24 -0
  100. package/dist/src/commands/demo.js +196 -0
  101. package/dist/src/commands/deploy.js +215 -0
  102. package/dist/src/commands/doctor.js +1291 -0
  103. package/dist/src/commands/drift/index.js +674 -0
  104. package/dist/src/commands/explain.js +235 -0
  105. package/dist/src/commands/export.js +120 -0
  106. package/dist/src/commands/feedback.js +319 -0
  107. package/dist/src/commands/fix.js +263 -0
  108. package/dist/src/commands/fs/index.js +338 -0
  109. package/dist/src/commands/gcp/compute.js +266 -0
  110. package/dist/src/commands/gcp/functions.js +221 -0
  111. package/dist/src/commands/gcp/gke.js +357 -0
  112. package/dist/src/commands/gcp/iam.js +295 -0
  113. package/dist/src/commands/gcp/index.js +105 -0
  114. package/dist/src/commands/gcp/storage.js +232 -0
  115. package/dist/src/commands/generate-helm.js +1026 -0
  116. package/dist/src/commands/generate-k8s.js +1263 -0
  117. package/dist/src/commands/generate-terraform.js +1058 -0
  118. package/dist/src/commands/gh/index.js +663 -0
  119. package/dist/src/commands/git/index.js +1208 -0
  120. package/dist/src/commands/helm/index.js +985 -0
  121. package/dist/src/commands/help.js +639 -0
  122. package/dist/src/commands/history.js +120 -0
  123. package/dist/src/commands/import.js +782 -0
  124. package/dist/src/commands/incident.js +144 -0
  125. package/dist/src/commands/index.js +109 -0
  126. package/dist/src/commands/init.js +955 -0
  127. package/dist/src/commands/k8s/index.js +979 -0
  128. package/dist/src/commands/login.js +588 -0
  129. package/dist/src/commands/logout.js +61 -0
  130. package/dist/src/commands/logs.js +160 -0
  131. package/dist/src/commands/onboarding.js +382 -0
  132. package/dist/src/commands/pipeline.js +153 -0
  133. package/dist/src/commands/plan/display.js +216 -0
  134. package/dist/src/commands/plan/index.js +525 -0
  135. package/dist/src/commands/plugin.js +325 -0
  136. package/dist/src/commands/preview.js +356 -0
  137. package/dist/src/commands/profile.js +297 -0
  138. package/dist/src/commands/questionnaire.js +1021 -0
  139. package/dist/src/commands/resume.js +35 -0
  140. package/dist/src/commands/rollback.js +259 -0
  141. package/dist/src/commands/rollout.js +74 -0
  142. package/dist/src/commands/runbook.js +307 -0
  143. package/dist/src/commands/schedule.js +202 -0
  144. package/dist/src/commands/status.js +213 -0
  145. package/dist/src/commands/team/index.js +309 -0
  146. package/dist/src/commands/team-context.js +200 -0
  147. package/dist/src/commands/template.js +204 -0
  148. package/dist/src/commands/tf/index.js +989 -0
  149. package/dist/src/commands/upgrade.js +515 -0
  150. package/dist/src/commands/usage/index.js +118 -0
  151. package/dist/src/commands/version.js +145 -0
  152. package/dist/src/commands/watch.js +127 -0
  153. package/dist/src/compat/index.js +2 -0
  154. package/dist/src/compat/runtime.js +10 -0
  155. package/dist/src/compat/sqlite.js +144 -0
  156. package/dist/src/config/index.js +6 -0
  157. package/dist/src/config/manager.js +469 -0
  158. package/dist/src/config/mode-store.js +57 -0
  159. package/dist/src/config/profiles.js +66 -0
  160. package/dist/src/config/safety-policy.js +251 -0
  161. package/dist/src/config/schema.js +107 -0
  162. package/dist/src/config/types.js +311 -0
  163. package/dist/src/config/workspace-state.js +38 -0
  164. package/dist/src/context/context-db.js +138 -0
  165. package/dist/src/demo/index.js +295 -0
  166. package/dist/src/demo/scenarios/full-journey.js +226 -0
  167. package/dist/src/demo/scenarios/getting-started.js +124 -0
  168. package/dist/src/demo/scenarios/helm-release.js +334 -0
  169. package/dist/src/demo/scenarios/k8s-deployment.js +190 -0
  170. package/dist/src/demo/scenarios/terraform-vpc.js +167 -0
  171. package/dist/src/demo/types.js +6 -0
  172. package/dist/src/engine/cost-estimator.js +334 -0
  173. package/dist/src/engine/diagram-generator.js +192 -0
  174. package/dist/src/engine/drift-detector.js +688 -0
  175. package/dist/src/engine/executor.js +832 -0
  176. package/dist/src/engine/index.js +39 -0
  177. package/dist/src/engine/orchestrator.js +436 -0
  178. package/dist/src/engine/planner.js +616 -0
  179. package/dist/src/engine/safety.js +609 -0
  180. package/dist/src/engine/verifier.js +664 -0
  181. package/dist/src/enterprise/audit.js +241 -0
  182. package/dist/src/enterprise/auth.js +189 -0
  183. package/dist/src/enterprise/billing.js +512 -0
  184. package/dist/src/enterprise/index.js +16 -0
  185. package/dist/src/enterprise/teams.js +315 -0
  186. package/dist/src/generator/best-practices.js +1375 -0
  187. package/dist/src/generator/helm.js +495 -0
  188. package/dist/src/generator/index.js +11 -0
  189. package/dist/src/generator/intent-parser.js +420 -0
  190. package/dist/src/generator/kubernetes.js +773 -0
  191. package/dist/src/generator/terraform.js +1472 -0
  192. package/dist/src/history/index.js +6 -0
  193. package/dist/src/history/manager.js +199 -0
  194. package/dist/src/history/types.js +6 -0
  195. package/dist/src/hooks/config.js +318 -0
  196. package/dist/src/hooks/engine.js +317 -0
  197. package/dist/src/hooks/index.js +2 -0
  198. package/dist/src/llm/auth-bridge.js +157 -0
  199. package/dist/src/llm/circuit-breaker.js +116 -0
  200. package/dist/src/llm/config-loader.js +172 -0
  201. package/dist/src/llm/cost-calculator.js +137 -0
  202. package/dist/src/llm/index.js +7 -0
  203. package/dist/src/llm/model-aliases.js +99 -0
  204. package/dist/src/llm/provider-registry.js +57 -0
  205. package/dist/src/llm/providers/anthropic.js +430 -0
  206. package/dist/src/llm/providers/bedrock.js +409 -0
  207. package/dist/src/llm/providers/google.js +344 -0
  208. package/dist/src/llm/providers/ollama.js +661 -0
  209. package/dist/src/llm/providers/openai-compatible.js +289 -0
  210. package/dist/src/llm/providers/openai.js +284 -0
  211. package/dist/src/llm/providers/openrouter.js +293 -0
  212. package/dist/src/llm/router.js +844 -0
  213. package/dist/src/llm/types.js +69 -0
  214. package/dist/src/lsp/client.js +239 -0
  215. package/dist/src/lsp/languages.js +95 -0
  216. package/dist/src/lsp/manager.js +243 -0
  217. package/dist/src/mcp/client.js +289 -0
  218. package/dist/src/mcp/index.js +5 -0
  219. package/dist/src/mcp/manager.js +113 -0
  220. package/dist/src/nimbus.js +212 -0
  221. package/dist/src/plugins/index.js +13 -0
  222. package/dist/src/plugins/loader.js +280 -0
  223. package/dist/src/plugins/manager.js +282 -0
  224. package/dist/src/plugins/types.js +23 -0
  225. package/dist/src/scanners/cicd-scanner.js +230 -0
  226. package/dist/src/scanners/cloud-scanner.js +415 -0
  227. package/dist/src/scanners/framework-scanner.js +430 -0
  228. package/dist/src/scanners/iac-scanner.js +350 -0
  229. package/dist/src/scanners/index.js +454 -0
  230. package/dist/src/scanners/language-scanner.js +258 -0
  231. package/dist/src/scanners/package-manager-scanner.js +252 -0
  232. package/dist/src/scanners/types.js +6 -0
  233. package/dist/src/sessions/manager.js +395 -0
  234. package/dist/src/sessions/types.js +4 -0
  235. package/dist/src/sharing/sync.js +238 -0
  236. package/dist/src/sharing/viewer.js +131 -0
  237. package/dist/src/snapshots/index.js +1 -0
  238. package/dist/src/snapshots/manager.js +432 -0
  239. package/dist/src/state/artifacts.js +94 -0
  240. package/dist/src/state/audit.js +73 -0
  241. package/dist/src/state/billing.js +126 -0
  242. package/dist/src/state/checkpoints.js +81 -0
  243. package/dist/src/state/config.js +58 -0
  244. package/dist/src/state/conversations.js +7 -0
  245. package/dist/src/state/credentials.js +96 -0
  246. package/dist/src/state/db.js +53 -0
  247. package/dist/src/state/index.js +23 -0
  248. package/dist/src/state/messages.js +76 -0
  249. package/dist/src/state/projects.js +92 -0
  250. package/dist/src/state/schema.js +233 -0
  251. package/dist/src/state/sessions.js +79 -0
  252. package/dist/src/state/teams.js +131 -0
  253. package/dist/src/telemetry.js +91 -0
  254. package/dist/src/tools/aws-ops.js +747 -0
  255. package/dist/src/tools/azure-ops.js +491 -0
  256. package/dist/src/tools/file-ops.js +451 -0
  257. package/dist/src/tools/gcp-ops.js +559 -0
  258. package/dist/src/tools/git-ops.js +557 -0
  259. package/dist/src/tools/github-ops.js +460 -0
  260. package/dist/src/tools/helm-ops.js +634 -0
  261. package/dist/src/tools/index.js +16 -0
  262. package/dist/src/tools/k8s-ops.js +579 -0
  263. package/dist/src/tools/schemas/converter.js +129 -0
  264. package/dist/src/tools/schemas/devops.js +3319 -0
  265. package/dist/src/tools/schemas/index.js +19 -0
  266. package/dist/src/tools/schemas/standard.js +966 -0
  267. package/dist/src/tools/schemas/types.js +409 -0
  268. package/dist/src/tools/spawn-exec.js +109 -0
  269. package/dist/src/tools/terraform-ops.js +627 -0
  270. package/dist/src/types/config.js +1 -0
  271. package/dist/src/types/drift.js +4 -0
  272. package/dist/src/types/enterprise.js +5 -0
  273. package/dist/src/types/index.js +14 -0
  274. package/dist/src/types/plan.js +1 -0
  275. package/dist/src/types/request.js +1 -0
  276. package/dist/src/types/response.js +1 -0
  277. package/dist/src/types/service.js +1 -0
  278. package/dist/src/ui/App.js +1672 -0
  279. package/dist/src/ui/DeployPreview.js +60 -0
  280. package/dist/src/ui/FileDiffModal.js +108 -0
  281. package/dist/src/ui/Header.js +46 -0
  282. package/dist/src/ui/HelpModal.js +9 -0
  283. package/dist/src/ui/InputBox.js +408 -0
  284. package/dist/src/ui/MessageList.js +795 -0
  285. package/dist/src/ui/PermissionPrompt.js +72 -0
  286. package/dist/src/ui/StatusBar.js +109 -0
  287. package/dist/src/ui/TerminalPane.js +31 -0
  288. package/dist/src/ui/ToolCallDisplay.js +303 -0
  289. package/dist/src/ui/TreePane.js +83 -0
  290. package/dist/src/ui/chat-ui.js +721 -0
  291. package/dist/src/ui/index.js +11 -0
  292. package/dist/src/ui/ink/index.js +1325 -0
  293. package/dist/src/ui/streaming.js +137 -0
  294. package/dist/src/ui/theme.js +78 -0
  295. package/dist/src/ui/types.js +7 -0
  296. package/dist/src/utils/analytics.js +61 -0
  297. package/dist/src/utils/cost-warning.js +25 -0
  298. package/dist/src/utils/env.js +42 -0
  299. package/dist/src/utils/errors.js +54 -0
  300. package/dist/src/utils/event-bus.js +22 -0
  301. package/dist/src/utils/index.js +16 -0
  302. package/dist/src/utils/logger.js +150 -0
  303. package/dist/src/utils/rate-limiter.js +90 -0
  304. package/dist/src/utils/service-auth.js +36 -0
  305. package/dist/src/utils/validation.js +39 -0
  306. package/dist/src/version.js +3 -0
  307. package/dist/src/watcher/index.js +192 -0
  308. package/dist/src/wizard/approval.js +275 -0
  309. package/dist/src/wizard/index.js +13 -0
  310. package/dist/src/wizard/prompts.js +273 -0
  311. package/dist/src/wizard/types.js +4 -0
  312. package/dist/src/wizard/ui.js +453 -0
  313. package/dist/src/wizard/wizard.js +227 -0
  314. package/package.json +31 -23
  315. package/src/__tests__/alias.test.ts +133 -0
  316. package/src/__tests__/app.test.ts +1 -1
  317. package/src/__tests__/audit.test.ts +1 -1
  318. package/src/__tests__/circuit-breaker.test.ts +1 -1
  319. package/src/__tests__/cli-run.test.ts +237 -1
  320. package/src/__tests__/compat-sqlite.test.ts +68 -0
  321. package/src/__tests__/context-manager.test.ts +131 -1
  322. package/src/__tests__/context.test.ts +1 -1
  323. package/src/__tests__/devops-terminal-gaps.test.ts +718 -0
  324. package/src/__tests__/doctor.test.ts +48 -0
  325. package/src/__tests__/enterprise.test.ts +1 -1
  326. package/src/__tests__/export.test.ts +236 -0
  327. package/src/__tests__/gap-11-18-20.test.ts +958 -0
  328. package/src/__tests__/generator.test.ts +1 -1
  329. package/src/__tests__/helm-streaming.test.ts +127 -0
  330. package/src/__tests__/hooks.test.ts +1 -1
  331. package/src/__tests__/incident.test.ts +179 -0
  332. package/src/__tests__/init.test.ts +55 -4
  333. package/src/__tests__/intent-parser.test.ts +1 -1
  334. package/src/__tests__/llm-router.test.ts +1 -1
  335. package/src/__tests__/logs.test.ts +107 -0
  336. package/src/__tests__/loop-errors.test.ts +244 -0
  337. package/src/__tests__/lsp.test.ts +1 -1
  338. package/src/__tests__/modes.test.ts +1 -1
  339. package/src/__tests__/perf-optimizations.test.ts +847 -0
  340. package/src/__tests__/permissions.test.ts +1 -1
  341. package/src/__tests__/pipeline.test.ts +50 -0
  342. package/src/__tests__/polish-phase3.test.ts +340 -0
  343. package/src/__tests__/profile.test.ts +237 -0
  344. package/src/__tests__/rollback.test.ts +83 -0
  345. package/src/__tests__/runbook.test.ts +219 -0
  346. package/src/__tests__/schedule.test.ts +206 -0
  347. package/src/__tests__/serve.test.ts +1 -1
  348. package/src/__tests__/sessions.test.ts +96 -1
  349. package/src/__tests__/sharing.test.ts +53 -1
  350. package/src/__tests__/snapshots.test.ts +1 -1
  351. package/src/__tests__/standalone-migration.test.ts +199 -0
  352. package/src/__tests__/state-db.test.ts +1 -1
  353. package/src/__tests__/status.test.ts +158 -0
  354. package/src/__tests__/stream-with-tools.test.ts +71 -25
  355. package/src/__tests__/subagents.test.ts +1 -1
  356. package/src/__tests__/system-prompt.test.ts +82 -3
  357. package/src/__tests__/terminal-gap-v2.test.ts +395 -0
  358. package/src/__tests__/terminal-parity.test.ts +393 -0
  359. package/src/__tests__/tf-apply.test.ts +187 -0
  360. package/src/__tests__/tool-converter.test.ts +1 -1
  361. package/src/__tests__/tool-schemas.test.ts +209 -4
  362. package/src/__tests__/tools.test.ts +4 -3
  363. package/src/__tests__/version-json.test.ts +184 -0
  364. package/src/__tests__/version.test.ts +1 -1
  365. package/src/__tests__/watch.test.ts +129 -0
  366. package/src/agent/compaction-agent.ts +40 -1
  367. package/src/agent/context-manager.ts +67 -3
  368. package/src/agent/deploy-preview.ts +62 -1
  369. package/src/agent/expand-files.ts +108 -0
  370. package/src/agent/loop.ts +1312 -31
  371. package/src/agent/permissions.ts +51 -4
  372. package/src/agent/system-prompt.ts +573 -19
  373. package/src/app.ts +58 -0
  374. package/src/audit/security-scanner.ts +45 -0
  375. package/src/auth/keychain.ts +82 -0
  376. package/src/auth/oauth.ts +15 -5
  377. package/src/cli/init.ts +378 -5
  378. package/src/cli/run.ts +407 -16
  379. package/src/cli/serve.ts +78 -1
  380. package/src/cli/web.ts +10 -6
  381. package/src/cli.ts +312 -1
  382. package/src/clients/service-discovery.ts +30 -25
  383. package/src/commands/alias.ts +100 -0
  384. package/src/commands/audit/index.ts +121 -2
  385. package/src/commands/auth-cloud.ts +113 -0
  386. package/src/commands/auth-refresh.ts +187 -0
  387. package/src/commands/aws-discover.ts +144 -251
  388. package/src/commands/aws-terraform.ts +68 -118
  389. package/src/commands/chat.ts +9 -3
  390. package/src/commands/completions.ts +268 -0
  391. package/src/commands/config.ts +26 -0
  392. package/src/commands/cost/index.ts +218 -2
  393. package/src/commands/deploy.ts +260 -0
  394. package/src/commands/doctor.ts +744 -152
  395. package/src/commands/drift/index.ts +371 -23
  396. package/src/commands/export.ts +146 -0
  397. package/src/commands/generate-k8s.ts +9 -61
  398. package/src/commands/generate-terraform.ts +191 -449
  399. package/src/commands/help.ts +212 -36
  400. package/src/commands/history.ts +8 -1
  401. package/src/commands/incident.ts +166 -0
  402. package/src/commands/init.ts +5 -0
  403. package/src/commands/login.ts +86 -1
  404. package/src/commands/logs.ts +167 -0
  405. package/src/commands/onboarding.ts +211 -34
  406. package/src/commands/pipeline.ts +186 -0
  407. package/src/commands/plugin.ts +398 -0
  408. package/src/commands/profile.ts +342 -0
  409. package/src/commands/questionnaire.ts +0 -98
  410. package/src/commands/resume.ts +26 -34
  411. package/src/commands/rollback.ts +315 -0
  412. package/src/commands/rollout.ts +88 -0
  413. package/src/commands/runbook.ts +346 -0
  414. package/src/commands/schedule.ts +236 -0
  415. package/src/commands/status.ts +252 -0
  416. package/src/commands/team-context.ts +220 -0
  417. package/src/commands/template.ts +58 -57
  418. package/src/commands/tf/index.ts +70 -11
  419. package/src/commands/upgrade.ts +57 -0
  420. package/src/commands/version.ts +54 -50
  421. package/src/commands/watch.ts +153 -0
  422. package/src/compat/runtime.ts +1 -1
  423. package/src/compat/sqlite.ts +75 -5
  424. package/src/config/mode-store.ts +62 -0
  425. package/src/config/profiles.ts +84 -0
  426. package/src/config/types.ts +83 -1
  427. package/src/config/workspace-state.ts +53 -0
  428. package/src/engine/cost-estimator.ts +52 -10
  429. package/src/engine/executor.ts +33 -2
  430. package/src/engine/planner.ts +68 -1
  431. package/src/generator/terraform.ts +8 -0
  432. package/src/history/manager.ts +2 -74
  433. package/src/hooks/engine.ts +5 -4
  434. package/src/llm/cost-calculator.ts +2 -2
  435. package/src/llm/providers/anthropic.ts +50 -21
  436. package/src/llm/router.ts +76 -7
  437. package/src/lsp/languages.ts +3 -0
  438. package/src/lsp/manager.ts +21 -5
  439. package/src/nimbus.ts +37 -18
  440. package/src/sessions/manager.ts +108 -1
  441. package/src/sharing/sync.ts +4 -0
  442. package/src/sharing/viewer.ts +66 -0
  443. package/src/tools/file-ops.ts +22 -0
  444. package/src/tools/schemas/devops.ts +3007 -117
  445. package/src/tools/schemas/standard.ts +5 -1
  446. package/src/tools/schemas/types.ts +31 -1
  447. package/src/tools/spawn-exec.ts +148 -0
  448. package/src/ui/App.tsx +1183 -66
  449. package/src/ui/DeployPreview.tsx +62 -57
  450. package/src/ui/FileDiffModal.tsx +162 -0
  451. package/src/ui/Header.tsx +87 -24
  452. package/src/ui/HelpModal.tsx +57 -0
  453. package/src/ui/InputBox.tsx +163 -10
  454. package/src/ui/MessageList.tsx +487 -40
  455. package/src/ui/PermissionPrompt.tsx +17 -5
  456. package/src/ui/StatusBar.tsx +122 -3
  457. package/src/ui/TerminalPane.tsx +84 -0
  458. package/src/ui/ToolCallDisplay.tsx +252 -18
  459. package/src/ui/TreePane.tsx +132 -0
  460. package/src/ui/chat-ui.ts +41 -44
  461. package/src/ui/ink/index.ts +771 -38
  462. package/src/ui/streaming.ts +1 -1
  463. package/src/ui/theme.ts +104 -0
  464. package/src/ui/types.ts +18 -0
  465. package/src/version.ts +1 -1
  466. package/src/watcher/index.ts +66 -15
  467. package/src/wizard/types.ts +1 -0
  468. package/src/wizard/ui.ts +1 -1
  469. package/tsconfig.json +2 -2
@@ -0,0 +1,465 @@
1
+ /**
2
+ * Apply Terraform Command
3
+ *
4
+ * Apply Terraform configuration to create/update infrastructure
5
+ *
6
+ * Usage: nimbus apply terraform [directory] [options]
7
+ */
8
+ import { logger } from '../../utils';
9
+ import { ui, confirm } from '../../wizard';
10
+ import { terraformClient } from '../../clients';
11
+ import { CostEstimator } from '../cost/estimator';
12
+ import { loadSafetyPolicy, evaluateSafety, } from '../../config/safety-policy';
13
+ import { promptForApproval, displaySafetySummary, confirmWithResourceName, } from '../../wizard/approval';
14
+ /**
15
+ * Display inline cost estimate for a terraform directory
16
+ */
17
+ async function displayCostEstimate(directory) {
18
+ try {
19
+ const estimate = await CostEstimator.estimateDirectory(directory);
20
+ if (estimate.totalMonthlyCost > 0) {
21
+ ui.newLine();
22
+ ui.print(` ${ui.color('$', 'yellow')} Estimated monthly cost: ${ui.bold(`$${estimate.totalMonthlyCost.toFixed(2)}/mo`)}`);
23
+ const projects = estimate.projects || [];
24
+ const costResources = projects.length > 0 ? projects[0].resources || [] : [];
25
+ if (costResources.length > 0) {
26
+ for (const resource of costResources.slice(0, 5)) {
27
+ ui.print(` ${resource.name}: $${resource.monthlyCost.toFixed(2)}/mo`);
28
+ }
29
+ if (costResources.length > 5) {
30
+ ui.print(ui.dim(` ... and ${costResources.length - 5} more resources`));
31
+ }
32
+ }
33
+ }
34
+ }
35
+ catch {
36
+ // Silently skip if cost estimation fails — don't block the apply
37
+ ui.print(ui.dim(' Cost estimation available: run "nimbus cost estimate"'));
38
+ }
39
+ }
40
+ /**
41
+ * Run terraform apply command
42
+ */
43
+ export async function applyTerraformCommand(options = {}) {
44
+ logger.info('Running terraform apply', { options });
45
+ const directory = options.directory || '.';
46
+ ui.header('Terraform Apply');
47
+ ui.info(`Directory: ${directory}`);
48
+ ui.newLine();
49
+ // Check if terraform client is available
50
+ const clientAvailable = await terraformClient.isAvailable();
51
+ if (clientAvailable) {
52
+ // Use terraform tools service
53
+ await applyWithService(options);
54
+ }
55
+ else {
56
+ // Fall back to local terraform CLI
57
+ await applyWithLocalCLI(options);
58
+ }
59
+ }
60
+ /**
61
+ * Apply using Terraform Tools Service
62
+ */
63
+ async function applyWithService(options) {
64
+ const directory = options.directory || '.';
65
+ // First, run plan if not auto-approved
66
+ if (!options.autoApprove) {
67
+ ui.startSpinner({ message: 'Creating execution plan...' });
68
+ const planResult = await terraformClient.plan(directory, {
69
+ vars: options.var,
70
+ varFile: options.varFile,
71
+ });
72
+ ui.stopSpinnerSuccess('Plan created');
73
+ ui.newLine();
74
+ if (!planResult.success) {
75
+ ui.error(`Plan failed: ${planResult.error}`);
76
+ process.exit(1);
77
+ }
78
+ // Display plan summary
79
+ displayPlanSummary(planResult);
80
+ // Check if there are changes
81
+ if (!planResult.hasChanges) {
82
+ ui.success('No changes. Infrastructure is up to date.');
83
+ return;
84
+ }
85
+ // Show inline cost estimate if resources are being created
86
+ if (planResult.hasChanges) {
87
+ await displayCostEstimate(directory);
88
+ }
89
+ // Dry run - don't apply
90
+ if (options.dryRun) {
91
+ ui.newLine();
92
+ ui.info('Dry run mode - no changes applied');
93
+ return;
94
+ }
95
+ // Parse destroy count to determine confirmation type
96
+ const destroyCountMatch = planResult.output.match(/(\d+) to destroy/);
97
+ const destroyCount = parseInt(destroyCountMatch?.[1] || '0', 10);
98
+ // Run safety checks if not skipped
99
+ if (!options.skipSafety) {
100
+ const safetyResult = await runSafetyChecks('apply', planResult.output, options);
101
+ if (!safetyResult.passed) {
102
+ ui.newLine();
103
+ ui.error('Safety checks failed - operation blocked');
104
+ for (const blocker of safetyResult.blockers) {
105
+ ui.print(` ${ui.color('✗', 'red')} ${blocker.message}`);
106
+ }
107
+ process.exit(1);
108
+ }
109
+ // If safety requires approval, prompt for it
110
+ if (safetyResult.requiresApproval) {
111
+ // Destructive plans require type-name confirmation first
112
+ if (destroyCount > 0) {
113
+ const confirmed = await confirmWithResourceName(directory, 'terraform directory');
114
+ if (!confirmed) {
115
+ ui.newLine();
116
+ ui.info('Apply cancelled');
117
+ return;
118
+ }
119
+ }
120
+ const approvalResult = await promptForApproval({
121
+ title: 'Terraform Apply',
122
+ operation: 'terraform apply',
123
+ risks: safetyResult.risks,
124
+ environment: options.environment,
125
+ affectedResources: safetyResult.affectedResources,
126
+ estimatedCost: safetyResult.estimatedCost,
127
+ });
128
+ if (!approvalResult.approved) {
129
+ ui.newLine();
130
+ ui.info(`Apply cancelled: ${approvalResult.reason || 'User declined'}`);
131
+ return;
132
+ }
133
+ }
134
+ else {
135
+ // Show safety summary and simple confirm (or type-name confirm for destroys)
136
+ displaySafetySummary({
137
+ operation: 'terraform apply',
138
+ risks: safetyResult.risks,
139
+ passed: safetyResult.passed,
140
+ });
141
+ ui.newLine();
142
+ if (destroyCount > 0) {
143
+ const confirmed = await confirmWithResourceName(directory, 'terraform directory');
144
+ if (!confirmed) {
145
+ ui.newLine();
146
+ ui.info('Apply cancelled');
147
+ return;
148
+ }
149
+ }
150
+ else {
151
+ const proceed = await confirm({
152
+ message: 'Do you want to apply these changes?',
153
+ defaultValue: false,
154
+ });
155
+ if (!proceed) {
156
+ ui.info('Apply cancelled');
157
+ return;
158
+ }
159
+ }
160
+ }
161
+ }
162
+ else {
163
+ // Simple confirmation when safety is skipped, but still enforce type-name for destroys
164
+ ui.newLine();
165
+ if (destroyCount > 0) {
166
+ const confirmed = await confirmWithResourceName(directory, 'terraform directory');
167
+ if (!confirmed) {
168
+ ui.newLine();
169
+ ui.info('Apply cancelled');
170
+ return;
171
+ }
172
+ }
173
+ else {
174
+ const proceed = await confirm({
175
+ message: 'Do you want to apply these changes?',
176
+ defaultValue: false,
177
+ });
178
+ if (!proceed) {
179
+ ui.info('Apply cancelled');
180
+ return;
181
+ }
182
+ }
183
+ }
184
+ }
185
+ // Run apply
186
+ ui.newLine();
187
+ ui.startSpinner({ message: 'Applying changes...' });
188
+ const applyResult = await terraformClient.apply(directory, {
189
+ autoApprove: true, // Already confirmed above
190
+ vars: options.var,
191
+ varFile: options.varFile,
192
+ });
193
+ if (!applyResult.success) {
194
+ ui.stopSpinnerFail('Apply failed');
195
+ ui.error(applyResult.error || 'Unknown error');
196
+ if (applyResult.output) {
197
+ ui.newLine();
198
+ ui.print(applyResult.output);
199
+ }
200
+ process.exit(1);
201
+ }
202
+ ui.stopSpinnerSuccess('Apply complete!');
203
+ // Track successful terraform apply
204
+ try {
205
+ const { trackGeneration } = await import('../../telemetry');
206
+ trackGeneration('terraform-apply', ['terraform']);
207
+ }
208
+ catch {
209
+ /* telemetry failure is non-critical */
210
+ }
211
+ // Display output
212
+ if (applyResult.output) {
213
+ ui.newLine();
214
+ ui.print(applyResult.output);
215
+ }
216
+ }
217
+ /**
218
+ * Apply using local Terraform CLI
219
+ */
220
+ async function applyWithLocalCLI(options) {
221
+ const { spawn } = await import('child_process');
222
+ const directory = options.directory || '.';
223
+ // First, run plan to get the output for safety checks (unless auto-approved)
224
+ if (!options.autoApprove && !options.skipSafety) {
225
+ ui.startSpinner({ message: 'Creating execution plan...' });
226
+ const planOutput = await runLocalTerraformPlan(directory, options);
227
+ ui.stopSpinnerSuccess('Plan created');
228
+ ui.newLine();
229
+ // Display plan summary
230
+ const hasChanges = planOutput.includes('to add') ||
231
+ planOutput.includes('to change') ||
232
+ planOutput.includes('to destroy');
233
+ displayPlanSummary({
234
+ success: true,
235
+ hasChanges,
236
+ output: planOutput,
237
+ });
238
+ if (!hasChanges) {
239
+ ui.success('No changes. Infrastructure is up to date.');
240
+ return;
241
+ }
242
+ // Dry run - don't apply
243
+ if (options.dryRun) {
244
+ ui.newLine();
245
+ ui.info('Dry run mode - no changes applied');
246
+ return;
247
+ }
248
+ // Parse destroy count to determine confirmation type
249
+ const destroyCountMatch = planOutput.match(/(\d+) to destroy/);
250
+ const destroyCount = parseInt(destroyCountMatch?.[1] || '0', 10);
251
+ // Run safety checks
252
+ const safetyResult = await runSafetyChecks('apply', planOutput, options);
253
+ if (!safetyResult.passed) {
254
+ ui.newLine();
255
+ ui.error('Safety checks failed - operation blocked');
256
+ for (const blocker of safetyResult.blockers) {
257
+ ui.print(` ${ui.color('✗', 'red')} ${blocker.message}`);
258
+ }
259
+ process.exit(1);
260
+ }
261
+ // If safety requires approval, prompt for it
262
+ if (safetyResult.requiresApproval) {
263
+ // Destructive plans require type-name confirmation first
264
+ if (destroyCount > 0) {
265
+ const confirmed = await confirmWithResourceName(directory, 'terraform directory');
266
+ if (!confirmed) {
267
+ ui.newLine();
268
+ ui.info('Apply cancelled');
269
+ return;
270
+ }
271
+ }
272
+ const approvalResult = await promptForApproval({
273
+ title: 'Terraform Apply',
274
+ operation: 'terraform apply',
275
+ risks: safetyResult.risks,
276
+ environment: options.environment,
277
+ affectedResources: safetyResult.affectedResources,
278
+ estimatedCost: safetyResult.estimatedCost,
279
+ });
280
+ if (!approvalResult.approved) {
281
+ ui.newLine();
282
+ ui.info(`Apply cancelled: ${approvalResult.reason || 'User declined'}`);
283
+ return;
284
+ }
285
+ }
286
+ else {
287
+ // Show safety summary and simple confirm (or type-name confirm for destroys)
288
+ displaySafetySummary({
289
+ operation: 'terraform apply',
290
+ risks: safetyResult.risks,
291
+ passed: safetyResult.passed,
292
+ });
293
+ ui.newLine();
294
+ if (destroyCount > 0) {
295
+ const confirmed = await confirmWithResourceName(directory, 'terraform directory');
296
+ if (!confirmed) {
297
+ ui.newLine();
298
+ ui.info('Apply cancelled');
299
+ return;
300
+ }
301
+ }
302
+ else {
303
+ const proceed = await confirm({
304
+ message: 'Do you want to apply these changes?',
305
+ defaultValue: false,
306
+ });
307
+ if (!proceed) {
308
+ ui.info('Apply cancelled');
309
+ return;
310
+ }
311
+ }
312
+ }
313
+ }
314
+ // Build terraform apply command
315
+ const args = ['apply', '-auto-approve']; // Auto-approve since we already confirmed
316
+ if (options.var) {
317
+ for (const [key, value] of Object.entries(options.var)) {
318
+ args.push('-var', `${key}=${value}`);
319
+ }
320
+ }
321
+ if (options.varFile) {
322
+ args.push('-var-file', options.varFile);
323
+ }
324
+ if (options.target) {
325
+ args.push('-target', options.target);
326
+ }
327
+ if (options.parallelism !== undefined) {
328
+ args.push('-parallelism', String(options.parallelism));
329
+ }
330
+ if (options.refresh === false) {
331
+ args.push('-refresh=false');
332
+ }
333
+ if (options.lock === false) {
334
+ args.push('-lock=false');
335
+ }
336
+ ui.newLine();
337
+ ui.info(`Running: terraform ${args.join(' ')}`);
338
+ ui.newLine();
339
+ // Run terraform
340
+ return new Promise(resolve => {
341
+ const proc = spawn('terraform', args, {
342
+ cwd: directory,
343
+ stdio: 'inherit',
344
+ });
345
+ proc.on('error', error => {
346
+ ui.error(`Failed to run terraform: ${error.message}`);
347
+ ui.info('Make sure terraform is installed and in your PATH');
348
+ process.exit(1);
349
+ });
350
+ proc.on('close', code => {
351
+ if (code === 0) {
352
+ ui.newLine();
353
+ ui.success('Terraform apply completed successfully');
354
+ // Track successful terraform apply
355
+ try {
356
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
357
+ const { trackGeneration } = require('../../telemetry');
358
+ trackGeneration('terraform-apply', ['terraform']);
359
+ }
360
+ catch {
361
+ /* telemetry failure is non-critical */
362
+ }
363
+ resolve();
364
+ }
365
+ else {
366
+ ui.newLine();
367
+ ui.error(`Terraform apply failed with exit code ${code}`);
368
+ process.exit(code || 1);
369
+ }
370
+ });
371
+ });
372
+ }
373
+ /**
374
+ * Run local terraform plan and capture output
375
+ */
376
+ async function runLocalTerraformPlan(directory, options) {
377
+ const { spawn } = await import('child_process');
378
+ const args = ['plan', '-no-color'];
379
+ if (options.var) {
380
+ for (const [key, value] of Object.entries(options.var)) {
381
+ args.push('-var', `${key}=${value}`);
382
+ }
383
+ }
384
+ if (options.varFile) {
385
+ args.push('-var-file', options.varFile);
386
+ }
387
+ if (options.target) {
388
+ args.push('-target', options.target);
389
+ }
390
+ return new Promise((resolve, reject) => {
391
+ let output = '';
392
+ const proc = spawn('terraform', args, {
393
+ cwd: directory,
394
+ stdio: ['inherit', 'pipe', 'pipe'],
395
+ });
396
+ proc.stdout?.on('data', data => {
397
+ output += data.toString();
398
+ });
399
+ proc.stderr?.on('data', data => {
400
+ output += data.toString();
401
+ });
402
+ proc.on('error', error => {
403
+ reject(new Error(`Failed to run terraform plan: ${error.message}`));
404
+ });
405
+ proc.on('close', code => {
406
+ if (code === 0) {
407
+ resolve(output);
408
+ }
409
+ else {
410
+ reject(new Error(`Terraform plan failed with exit code ${code}`));
411
+ }
412
+ });
413
+ });
414
+ }
415
+ /**
416
+ * Run safety checks for the operation
417
+ */
418
+ async function runSafetyChecks(operation, planOutput, options) {
419
+ const policy = loadSafetyPolicy();
420
+ const context = {
421
+ operation,
422
+ type: 'terraform',
423
+ environment: options.environment,
424
+ planOutput,
425
+ metadata: {
426
+ directory: options.directory,
427
+ target: options.target,
428
+ },
429
+ };
430
+ return evaluateSafety(context, policy);
431
+ }
432
+ /**
433
+ * Display plan summary
434
+ */
435
+ function displayPlanSummary(planResult) {
436
+ if (!planResult.hasChanges) {
437
+ ui.print('Plan Summary:');
438
+ ui.newLine();
439
+ ui.print(' No changes');
440
+ return;
441
+ }
442
+ // Parse changes from output
443
+ const addMatch = planResult.output.match(/(\d+) to add/);
444
+ const changeMatch = planResult.output.match(/(\d+) to change/);
445
+ const destroyMatch = planResult.output.match(/(\d+) to destroy/);
446
+ const add = parseInt(addMatch?.[1] || '0', 10);
447
+ const change = parseInt(changeMatch?.[1] || '0', 10);
448
+ const destroy = parseInt(destroyMatch?.[1] || '0', 10);
449
+ ui.print('Plan Summary:');
450
+ ui.newLine();
451
+ if (add > 0) {
452
+ ui.print(` ${ui.color(`+ ${add} to add`, 'green')}`);
453
+ }
454
+ if (change > 0) {
455
+ ui.print(` ${ui.color(`~ ${change} to change`, 'yellow')}`);
456
+ }
457
+ if (destroy > 0) {
458
+ ui.print(` ${ui.color(`- ${destroy} to destroy`, 'red')}`);
459
+ }
460
+ if (add === 0 && change === 0 && destroy === 0) {
461
+ ui.print(' Changes detected (see output)');
462
+ }
463
+ }
464
+ // Export as default
465
+ export default applyTerraformCommand;
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Ask Command
3
+ *
4
+ * Quick question and answer with AI
5
+ *
6
+ * Usage: nimbus ask "<question>" [options]
7
+ */
8
+ import { logger } from '../utils';
9
+ import { ui } from '../wizard';
10
+ import { llmClient } from '../clients';
11
+ /**
12
+ * Run the ask command
13
+ */
14
+ export async function askCommand(question, options = {}) {
15
+ logger.info('Running ask command', { question, options });
16
+ // Validate question
17
+ if (!question || question.trim() === '') {
18
+ ui.error('Please provide a question');
19
+ ui.newLine();
20
+ ui.print('Usage: nimbus ask "your question here"');
21
+ ui.newLine();
22
+ ui.print('Examples:');
23
+ ui.print(' nimbus ask "How do I create an S3 bucket with Terraform?"');
24
+ ui.print(' nimbus ask "What is the best practice for IAM roles?"');
25
+ ui.print(' nimbus ask "Explain kubernetes deployments" --context ./k8s/');
26
+ process.exit(1);
27
+ }
28
+ // Build context
29
+ let context = '';
30
+ // Add file context if provided
31
+ if (options.contextFile) {
32
+ try {
33
+ const fs = await import('fs/promises');
34
+ const fileContent = await fs.readFile(options.contextFile, 'utf-8');
35
+ context += `File: ${options.contextFile}\n\`\`\`\n${fileContent}\n\`\`\`\n\n`;
36
+ ui.info(`Including context from: ${options.contextFile}`);
37
+ }
38
+ catch (error) {
39
+ ui.warning(`Could not read context file: ${error.message}`);
40
+ }
41
+ }
42
+ // Add directory context if provided
43
+ if (options.context) {
44
+ try {
45
+ const fs = await import('fs/promises');
46
+ const path = await import('path');
47
+ const stat = await fs.stat(options.context);
48
+ if (stat.isDirectory()) {
49
+ // Read relevant files from directory
50
+ const files = await fs.readdir(options.context);
51
+ const relevantExtensions = ['.tf', '.yaml', '.yml', '.json', '.ts', '.js', '.py', '.go'];
52
+ const relevantFiles = files
53
+ .filter(f => relevantExtensions.some(ext => f.endsWith(ext)))
54
+ .slice(0, 5); // Limit to 5 files
55
+ for (const file of relevantFiles) {
56
+ try {
57
+ const filePath = path.join(options.context, file);
58
+ const content = await fs.readFile(filePath, 'utf-8');
59
+ // Limit content size
60
+ const truncated = content.length > 2000 ? `${content.slice(0, 2000)}\n... (truncated)` : content;
61
+ context += `File: ${file}\n\`\`\`\n${truncated}\n\`\`\`\n\n`;
62
+ }
63
+ catch {
64
+ // Skip unreadable files
65
+ }
66
+ }
67
+ if (relevantFiles.length > 0) {
68
+ ui.info(`Including context from ${relevantFiles.length} file(s) in: ${options.context}`);
69
+ }
70
+ }
71
+ else {
72
+ // Single file
73
+ const fileContent = await fs.readFile(options.context, 'utf-8');
74
+ context += `File: ${options.context}\n\`\`\`\n${fileContent}\n\`\`\`\n\n`;
75
+ ui.info(`Including context from: ${options.context}`);
76
+ }
77
+ }
78
+ catch (error) {
79
+ ui.warning(`Could not read context: ${error.message}`);
80
+ }
81
+ }
82
+ // Build the full question with context
83
+ let fullQuestion = question;
84
+ if (context) {
85
+ fullQuestion = `Context:\n${context}\n\nQuestion: ${question}`;
86
+ }
87
+ // Check if LLM is available
88
+ const llmAvailable = await llmClient.isAvailable();
89
+ if (!llmAvailable) {
90
+ ui.error('LLM service is not available');
91
+ ui.info('Make sure you have configured an LLM provider with "nimbus login"');
92
+ ui.info('Or set the ANTHROPIC_API_KEY or OPENAI_API_KEY environment variable');
93
+ process.exit(1);
94
+ }
95
+ ui.newLine();
96
+ ui.startSpinner({ message: 'Thinking...' });
97
+ try {
98
+ // Stream the response
99
+ let response = '';
100
+ let firstChunk = true;
101
+ for await (const chunk of llmClient.chat(fullQuestion, [], { model: options.model })) {
102
+ if (chunk.type === 'content' && chunk.content) {
103
+ if (firstChunk) {
104
+ ui.stopSpinnerSuccess('');
105
+ ui.newLine();
106
+ firstChunk = false;
107
+ }
108
+ response += chunk.content;
109
+ process.stdout.write(chunk.content);
110
+ }
111
+ else if (chunk.type === 'error') {
112
+ ui.stopSpinnerFail('Error');
113
+ ui.error(chunk.message || chunk.error || 'Unknown error');
114
+ process.exit(1);
115
+ }
116
+ }
117
+ // Ensure newline at end
118
+ if (!response.endsWith('\n')) {
119
+ ui.newLine();
120
+ }
121
+ // JSON output mode
122
+ if (options.json) {
123
+ console.log(JSON.stringify({
124
+ question,
125
+ answer: response,
126
+ context: context ? true : false,
127
+ }, null, 2));
128
+ }
129
+ }
130
+ catch (error) {
131
+ ui.stopSpinnerFail('Failed');
132
+ ui.error(error.message);
133
+ process.exit(1);
134
+ }
135
+ }
136
+ // Export as default
137
+ export default askCommand;