@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,167 @@
1
+ /**
2
+ * Logs Command — H1
3
+ *
4
+ * Shorthand for streaming Kubernetes pod logs directly.
5
+ *
6
+ * Usage:
7
+ * nimbus logs <pod> [-n namespace] [-f] [--previous] [--tail N] [--analyze]
8
+ */
9
+
10
+ import { execFileSync, spawn } from 'node:child_process';
11
+ import { ui } from '../wizard/ui';
12
+
13
+ export interface LogsOptions {
14
+ namespace?: string;
15
+ follow?: boolean;
16
+ previous?: boolean;
17
+ tail?: number;
18
+ analyze?: boolean;
19
+ container?: string;
20
+ context?: string;
21
+ quiet?: boolean;
22
+ }
23
+
24
+ /**
25
+ * Build the kubectl logs argument array.
26
+ */
27
+ function buildArgs(pod: string, options: LogsOptions): string[] {
28
+ const args = ['logs', pod];
29
+ if (options.namespace) args.push('-n', options.namespace);
30
+ if (options.follow) args.push('--follow');
31
+ if (options.previous) args.push('--previous');
32
+ if (options.tail !== undefined) args.push('--tail', String(options.tail));
33
+ if (options.container) args.push('-c', options.container);
34
+ if (options.context) args.push('--context', options.context);
35
+ return args;
36
+ }
37
+
38
+ /**
39
+ * Stream or fetch Kubernetes pod logs.
40
+ */
41
+ export async function logsCommand(pod: string, options: LogsOptions = {}): Promise<void> {
42
+ if (!pod) {
43
+ ui.error('Usage: nimbus logs <pod> [-n namespace] [-f] [--tail N]');
44
+ process.exit(1);
45
+ }
46
+
47
+ const args = buildArgs(pod, options);
48
+
49
+ // --analyze: capture output and pass to agent for analysis
50
+ if (options.analyze) {
51
+ if (!options.quiet) {
52
+ ui.startSpinner({ message: `Fetching logs for ${pod}...` });
53
+ }
54
+ try {
55
+ const output = execFileSync('kubectl', args, {
56
+ encoding: 'utf-8',
57
+ timeout: 30_000,
58
+ stdio: ['pipe', 'pipe', 'pipe'],
59
+ });
60
+ if (!options.quiet) {
61
+ ui.stopSpinnerSuccess('Logs fetched');
62
+ }
63
+
64
+ // Lazy-import agent loop to avoid circular deps at startup
65
+ const { runAgentLoop } = await import('../agent/loop');
66
+ const { getAppContext } = await import('../app');
67
+ const { defaultToolRegistry } = await import('../tools/schemas/types');
68
+ const { standardTools } = await import('../tools/schemas/standard');
69
+ const ctx = getAppContext();
70
+ if (!ctx) {
71
+ ui.error('App not initialized. Run `nimbus login` first.');
72
+ process.exit(1);
73
+ }
74
+
75
+ // Ensure tools are registered for the agent loop
76
+ if (defaultToolRegistry.size === 0) {
77
+ for (const tool of standardTools) {
78
+ try { defaultToolRegistry.register(tool); } catch { /* skip duplicates */ }
79
+ }
80
+ }
81
+
82
+ if (!options.quiet) {
83
+ ui.info('Analyzing logs...');
84
+ }
85
+ await runAgentLoop(
86
+ `Analyze these Kubernetes logs for pod "${pod}". Identify errors, anomalies, and crash patterns:\n\n${output}`,
87
+ [],
88
+ {
89
+ router: ctx.router,
90
+ toolRegistry: defaultToolRegistry,
91
+ mode: 'plan',
92
+ maxTurns: 5,
93
+ onText: text => { process.stdout.write(text); },
94
+ }
95
+ );
96
+ console.log('');
97
+ } catch (error: any) {
98
+ if (!options.quiet) {
99
+ ui.stopSpinnerFail('Failed to fetch logs');
100
+ }
101
+ ui.error(error.message);
102
+ process.exit(1);
103
+ }
104
+ return;
105
+ }
106
+
107
+ // --follow: stream via spawn
108
+ if (options.follow) {
109
+ const child = spawn('kubectl', args, {
110
+ stdio: ['ignore', 'inherit', 'inherit'],
111
+ });
112
+ await new Promise<void>((resolve, reject) => {
113
+ child.on('close', code => {
114
+ if (code !== 0) reject(new Error(`kubectl logs exited with code ${code}`));
115
+ else resolve();
116
+ });
117
+ child.on('error', reject);
118
+ });
119
+ return;
120
+ }
121
+
122
+ // Non-follow: capture and print
123
+ try {
124
+ const output = execFileSync('kubectl', args, {
125
+ encoding: 'utf-8',
126
+ timeout: 60_000,
127
+ stdio: ['pipe', 'pipe', 'pipe'],
128
+ });
129
+ process.stdout.write(output);
130
+ } catch (error: any) {
131
+ ui.error(`kubectl logs failed: ${error.message}`);
132
+ process.exit(1);
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Parse `nimbus logs <pod> [options]` arguments.
138
+ */
139
+ export function parseLogsArgs(args: string[]): { pod: string; options: LogsOptions } {
140
+ let pod = '';
141
+ const options: LogsOptions = {};
142
+
143
+ for (let i = 0; i < args.length; i++) {
144
+ const arg = args[i];
145
+ if (arg === '-n' || arg === '--namespace') {
146
+ options.namespace = args[++i];
147
+ } else if (arg === '-f' || arg === '--follow') {
148
+ options.follow = true;
149
+ } else if (arg === '-p' || arg === '--previous') {
150
+ options.previous = true;
151
+ } else if (arg === '--tail') {
152
+ options.tail = parseInt(args[++i] ?? '100', 10);
153
+ } else if (arg === '--analyze') {
154
+ options.analyze = true;
155
+ } else if (arg === '-c' || arg === '--container') {
156
+ options.container = args[++i];
157
+ } else if (arg === '--context') {
158
+ options.context = args[++i];
159
+ } else if (arg === '-q' || arg === '--quiet') {
160
+ options.quiet = true;
161
+ } else if (!arg.startsWith('-') && !pod) {
162
+ pod = arg;
163
+ }
164
+ }
165
+
166
+ return { pod, options };
167
+ }
@@ -79,15 +79,20 @@ export async function onboardingCommand(options: OnboardingOptions = {}): Promis
79
79
  }
80
80
 
81
81
  try {
82
- // Welcome banner
82
+ // Welcome banner — DevOps-first identity
83
83
  ui.newLine();
84
84
  ui.box({
85
- title: 'Welcome to Nimbus',
85
+ title: 'Welcome to Nimbus — AI-Powered DevOps Terminal',
86
86
  content: [
87
- 'AI-Powered Cloud Engineering Agent',
87
+ 'Plan, apply, and manage Terraform, Kubernetes, Helm,',
88
+ 'AWS, GCP, and Azure using natural language.',
88
89
  '',
89
- "Let's get you set up with an LLM provider.",
90
- 'This takes about 30 seconds.',
90
+ "Let's get you set up. This takes about 30 seconds.",
91
+ '',
92
+ 'After setup you can:',
93
+ ' nimbus Open interactive DevOps terminal',
94
+ ' nimbus run "tf plan --explain" Run agent non-interactively',
95
+ ' nimbus status Live infra health dashboard',
91
96
  ],
92
97
  style: 'rounded',
93
98
  borderColor: 'cyan',
@@ -96,6 +101,15 @@ export async function onboardingCommand(options: OnboardingOptions = {}): Promis
96
101
  });
97
102
  ui.newLine();
98
103
 
104
+ // Gap 12: Show env var shortcut for power users before the wizard
105
+ ui.print(ui.dim('─────────────────────────────────────────────────────'));
106
+ ui.print(ui.dim(' Quick setup: export an API key and restart nimbus.'));
107
+ ui.print(ui.dim(' export ANTHROPIC_API_KEY=sk-ant-...'));
108
+ ui.print(ui.dim(' export OPENAI_API_KEY=sk-...'));
109
+ ui.print(ui.dim(' export GOOGLE_API_KEY=...'));
110
+ ui.print(ui.dim('─────────────────────────────────────────────────────'));
111
+ ui.newLine();
112
+
99
113
  // Provider selection
100
114
  const providerOptions = Object.values(PROVIDER_REGISTRY).map(p => ({
101
115
  label: p.displayName,
@@ -129,14 +143,71 @@ export async function onboardingCommand(options: OnboardingOptions = {}): Promis
129
143
  }
130
144
  ui.newLine();
131
145
 
132
- apiKey = await input({
133
- message: `Enter your ${providerInfo.displayName} API key`,
134
- });
146
+ // Retry loop: re-prompt on invalid key (up to 3 attempts)
147
+ const MAX_ATTEMPTS = 3;
148
+ let attempt = 0;
149
+ let validated = false;
135
150
 
136
- if (!apiKey) {
137
- ui.newLine();
138
- ui.warning('No API key entered. You can run "nimbus login" later to set up.');
151
+ while (attempt < MAX_ATTEMPTS) {
152
+ attempt++;
153
+
154
+ apiKey = await input({
155
+ message:
156
+ attempt === 1
157
+ ? `Enter your ${providerInfo.displayName} API key`
158
+ : `Enter your ${providerInfo.displayName} API key (attempt ${attempt}/${MAX_ATTEMPTS})`,
159
+ });
160
+
161
+ if (!apiKey) {
162
+ ui.newLine();
163
+ ui.warning('No API key entered. You can run "nimbus login" later to set up.');
164
+ ui.newLine();
165
+ return;
166
+ }
167
+
168
+ // Validate credentials
139
169
  ui.newLine();
170
+ ui.startSpinner({ message: `Validating ${providerInfo.displayName} credentials...` });
171
+
172
+ let validation: { valid: boolean; error?: string };
173
+ try {
174
+ validation = await validateProviderApiKey(provider, apiKey, baseUrl);
175
+ } catch (err: unknown) {
176
+ const msg = err instanceof Error ? err.message : String(err);
177
+ ui.stopSpinnerFail(`Validation error: ${msg}`);
178
+ if (attempt < MAX_ATTEMPTS) {
179
+ ui.warning('Please check your key and try again.');
180
+ ui.newLine();
181
+ continue;
182
+ }
183
+ ui.newLine();
184
+ ui.info('You can retry with "nimbus login" or set the API key via environment variable.');
185
+ ui.newLine();
186
+ return;
187
+ }
188
+
189
+ if (!validation.valid) {
190
+ ui.stopSpinnerFail(`Invalid key: ${validation.error}`);
191
+ if (attempt < MAX_ATTEMPTS) {
192
+ ui.warning('Please check your key and try again.');
193
+ ui.newLine();
194
+ continue;
195
+ }
196
+ ui.newLine();
197
+ ui.info('You can retry with "nimbus login" or set the API key via environment variable:');
198
+ if (providerInfo.envVarName) {
199
+ ui.info(` export ${providerInfo.envVarName}=your-key`);
200
+ }
201
+ ui.newLine();
202
+ return;
203
+ }
204
+
205
+ ui.stopSpinnerSuccess(`${providerInfo.displayName} credentials verified!`);
206
+ validated = true;
207
+ break;
208
+ }
209
+
210
+ if (!validated) {
140
211
  return;
141
212
  }
142
213
  } else if (providerInfo.supportsBaseUrl) {
@@ -148,34 +219,49 @@ export async function onboardingCommand(options: OnboardingOptions = {}): Promis
148
219
  });
149
220
  }
150
221
 
151
- // Validate credentials
152
- ui.newLine();
153
- ui.startSpinner({ message: `Validating ${providerInfo.displayName} credentials...` });
154
-
155
- let validation: { valid: boolean; error?: string };
222
+ // Detect live infra stack from current directory (Terraform, K8s, Helm, etc.)
223
+ let detectedInfraStack: string[] = [];
156
224
  try {
157
- validation = await validateProviderApiKey(provider, apiKey, baseUrl);
158
- } catch (err: unknown) {
159
- const msg = err instanceof Error ? err.message : String(err);
160
- ui.stopSpinnerFail(`Validation error: ${msg}`);
161
- ui.newLine();
162
- ui.info('You can retry with "nimbus login" or set the API key via environment variable.');
163
- ui.newLine();
164
- return;
165
- }
166
-
167
- if (!validation.valid) {
168
- ui.stopSpinnerFail(`Validation failed: ${validation.error}`);
169
- ui.newLine();
170
- ui.info('You can retry with "nimbus login" or set the API key via environment variable:');
171
- if (providerInfo.envVarName) {
172
- ui.info(` export ${providerInfo.envVarName}=your-key`);
225
+ const { existsSync, readdirSync } = await import('node:fs');
226
+ const cwd = process.cwd();
227
+ const files = readdirSync(cwd);
228
+ if (files.some(f => f.endsWith('.tf') || f === 'terraform')) detectedInfraStack.push('terraform');
229
+ if (files.some(f => f === 'Chart.yaml' || f === 'helmfile.yaml')) detectedInfraStack.push('helm');
230
+ if (files.some(f => f.endsWith('.yaml') || f.endsWith('.yml'))) {
231
+ // Check if any yaml looks like K8s
232
+ const yamls = files.filter(f => f.endsWith('.yaml') || f.endsWith('.yml')).slice(0, 5);
233
+ for (const y of yamls) {
234
+ try {
235
+ const txt = require('node:fs').readFileSync(require('node:path').join(cwd, y), 'utf-8');
236
+ if (txt.includes('apiVersion:') && txt.includes('kind:')) { detectedInfraStack.push('kubernetes'); break; }
237
+ } catch { /* skip */ }
238
+ }
239
+ }
240
+ if (existsSync(require('node:path').join(cwd, 'docker-compose.yaml')) || existsSync(require('node:path').join(cwd, 'docker-compose.yml')) || existsSync(require('node:path').join(cwd, 'Dockerfile'))) {
241
+ detectedInfraStack.push('docker');
173
242
  }
243
+ } catch { /* non-critical */ }
244
+
245
+ if (detectedInfraStack.length > 0) {
174
246
  ui.newLine();
175
- return;
247
+ ui.print(ui.color(`Detected infrastructure: ${detectedInfraStack.join(', ')}`, 'green'));
248
+ ui.print(ui.dim('Nimbus will use this context to give better DevOps assistance.'));
176
249
  }
177
250
 
178
- ui.stopSpinnerSuccess(`${providerInfo.displayName} credentials verified!`);
251
+ // C5: Cloud provider selection step
252
+ let primaryClouds: string[] = [];
253
+ try {
254
+ const cloudInput = await input({
255
+ message: 'Which cloud providers do you primarily use? (comma-separated: aws, gcp, azure, or none)',
256
+ defaultValue: 'none',
257
+ });
258
+ if (cloudInput && cloudInput.trim().toLowerCase() !== 'none') {
259
+ primaryClouds = cloudInput
260
+ .split(',')
261
+ .map((s: string) => s.trim().toLowerCase())
262
+ .filter((s: string) => ['aws', 'gcp', 'azure'].includes(s));
263
+ }
264
+ } catch { /* non-critical — prompt may fail in non-interactive environments */ }
179
265
 
180
266
  // Save credentials
181
267
  const defaultModel = getDefaultModel(provider);
@@ -198,6 +284,25 @@ export async function onboardingCommand(options: OnboardingOptions = {}): Promis
198
284
  /* non-critical */
199
285
  }
200
286
 
287
+ // C5: Save primaryClouds to ~/.nimbus/config.json
288
+ if (primaryClouds.length > 0) {
289
+ try {
290
+ const { existsSync, readFileSync, writeFileSync, mkdirSync } = await import('node:fs');
291
+ const { join } = await import('node:path');
292
+ const { homedir } = await import('node:os');
293
+ const configJsonPath = join(homedir(), '.nimbus', 'config.json');
294
+ mkdirSync(join(homedir(), '.nimbus'), { recursive: true });
295
+ let configData: Record<string, unknown> = {};
296
+ if (existsSync(configJsonPath)) {
297
+ try {
298
+ configData = JSON.parse(readFileSync(configJsonPath, 'utf-8')) as Record<string, unknown>;
299
+ } catch { /* start fresh */ }
300
+ }
301
+ configData.primaryClouds = primaryClouds;
302
+ writeFileSync(configJsonPath, JSON.stringify(configData, null, 2), 'utf-8');
303
+ } catch { /* non-critical */ }
304
+ }
305
+
201
306
  // Success message
202
307
  ui.newLine();
203
308
  ui.box({
@@ -213,6 +318,78 @@ export async function onboardingCommand(options: OnboardingOptions = {}): Promis
213
318
  titleColor: 'brightGreen',
214
319
  padding: 1,
215
320
  });
321
+ // G3: DevOps context wizard — offer to run nimbus init after LLM setup
322
+ try {
323
+ const devopsSetup = await select<string>({
324
+ message: 'Would you like to set up your DevOps context now? (recommended)',
325
+ options: [
326
+ { label: 'Yes, detect my infrastructure', value: 'yes' },
327
+ { label: 'No, I\'ll run nimbus init later', value: 'no' },
328
+ ],
329
+ });
330
+ if (devopsSetup === 'yes') {
331
+ ui.newLine();
332
+ ui.startSpinner({ message: 'Detecting infrastructure context...' });
333
+ try {
334
+ const { runInit } = await import('../cli/init');
335
+ await runInit({ cwd: process.cwd(), quiet: false });
336
+ ui.stopSpinnerSuccess('NIMBUS.md generated — your infra context is ready');
337
+ } catch (initErr) {
338
+ ui.stopSpinnerFail('Could not generate NIMBUS.md (run `nimbus init` manually)');
339
+ }
340
+ }
341
+ } catch { /* non-critical — prompt may fail in non-interactive environments */ }
342
+
343
+ // GAP-4: Offer to install shell completions with clear success/failure feedback
344
+ try {
345
+ ui.newLine();
346
+ const installCompletions = await select<string>({
347
+ message: 'Install shell completions for nimbus? (adds tab completion)',
348
+ options: [
349
+ { label: 'Yes, install completions', value: 'yes' },
350
+ { label: 'No, skip', value: 'no' },
351
+ ],
352
+ });
353
+ if (installCompletions === 'yes') {
354
+ try {
355
+ const { completionsCommand } = await import('./completions');
356
+ await completionsCommand('install');
357
+ ui.success('Shell completions installed successfully!');
358
+ // Write marker file so nimbus can detect first-run tip
359
+ try {
360
+ const { mkdirSync, writeFileSync } = await import('node:fs');
361
+ const { join } = await import('node:path');
362
+ const { homedir } = await import('node:os');
363
+ mkdirSync(join(homedir(), '.nimbus'), { recursive: true });
364
+ writeFileSync(join(homedir(), '.nimbus', 'completions-installed'), '1', 'utf-8');
365
+ } catch { /* non-critical */ }
366
+ } catch (completionErr) {
367
+ const shell = process.env.SHELL ?? '';
368
+ ui.warning(`Could not auto-install completions: ${completionErr instanceof Error ? completionErr.message : String(completionErr)}`);
369
+ if (/zsh/.test(shell)) {
370
+ ui.print(ui.dim(' Manual install: nimbus completions install'));
371
+ ui.print(ui.dim(' Then add to ~/.zshrc: fpath=(~/.zsh/completions $fpath) && autoload -U compinit && compinit'));
372
+ } else if (/bash/.test(shell)) {
373
+ ui.print(ui.dim(' Manual install: nimbus completions install'));
374
+ } else {
375
+ ui.print(ui.dim(' Manual install: nimbus completions install'));
376
+ }
377
+ }
378
+ }
379
+ } catch { /* non-critical — prompt may fail in non-interactive environments */ }
380
+
381
+ // C3/H1: Optional CI/CD and monitoring env vars
382
+ ui.newLine();
383
+ ui.print(ui.dim('Optional: Set these env vars for full DevOps integration:'));
384
+ ui.print(ui.dim(' GITLAB_TOKEN=<token> # GitLab CI pipeline access'));
385
+ ui.print(ui.dim(' CIRCLECI_TOKEN=<token> # CircleCI pipeline access'));
386
+ ui.print(ui.dim(' PROMETHEUS_URL=<url> # Prometheus metrics queries'));
387
+ ui.print(ui.dim(' GRAFANA_URL=<url> # Grafana dashboard access'));
388
+ ui.print(ui.dim(' GRAFANA_TOKEN=<token> # Grafana API token'));
389
+ ui.print(ui.dim(' DD_API_KEY=<key> # Datadog metrics/alerts'));
390
+ ui.print(ui.dim(' PD_API_KEY=<key> # PagerDuty incident management (Gap 5)'));
391
+ ui.print(ui.dim(' OPSGENIE_API_KEY=<key> # Opsgenie alert management (Gap 5)'));
392
+ ui.print(ui.dim(' BRAVE_API_KEY=<key> # Enhanced web search'));
216
393
  ui.newLine();
217
394
  } catch (err: unknown) {
218
395
  // Catch-all for prompt failures (stdin closed, terminal not supported, etc.)
@@ -0,0 +1,186 @@
1
+ /**
2
+ * Pipeline Command — H2
3
+ *
4
+ * View CI/CD pipeline status from GitHub Actions, GitLab CI, or CircleCI.
5
+ *
6
+ * Usage:
7
+ * nimbus pipeline status [run-id]
8
+ * nimbus pipeline status --provider github --limit 10
9
+ */
10
+
11
+ import { execFileSync } from 'node:child_process';
12
+ import * as fs from 'node:fs';
13
+ import * as path from 'node:path';
14
+ import { ui } from '../wizard/ui';
15
+
16
+ export interface PipelineOptions {
17
+ provider?: 'github' | 'gitlab' | 'circleci' | 'auto';
18
+ format?: 'table' | 'json';
19
+ limit?: number;
20
+ }
21
+
22
+ /**
23
+ * Auto-detect the CI/CD provider by scanning the cwd for config files.
24
+ */
25
+ export function detectProvider(cwd: string = process.cwd()): 'github' | 'gitlab' | 'circleci' | null {
26
+ if (fs.existsSync(path.join(cwd, '.github', 'workflows'))) return 'github';
27
+ if (fs.existsSync(path.join(cwd, '.gitlab-ci.yml'))) return 'gitlab';
28
+ if (fs.existsSync(path.join(cwd, '.circleci', 'config.yml'))) return 'circleci';
29
+ return null;
30
+ }
31
+
32
+ interface PipelineRun {
33
+ id: string;
34
+ workflow: string;
35
+ status: string;
36
+ result: string;
37
+ started: string;
38
+ url?: string;
39
+ }
40
+
41
+ function fetchGitHubRuns(limit: number, runId?: string): PipelineRun[] {
42
+ if (runId) {
43
+ const raw = execFileSync(
44
+ 'gh',
45
+ ['run', 'view', runId, '--json', 'status,conclusion,name,createdAt,url,databaseId'],
46
+ { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }
47
+ );
48
+ const r = JSON.parse(raw);
49
+ return [{
50
+ id: String(r.databaseId ?? runId),
51
+ workflow: r.name ?? '',
52
+ status: r.status ?? '',
53
+ result: r.conclusion ?? '-',
54
+ started: r.createdAt ?? '',
55
+ url: r.url,
56
+ }];
57
+ }
58
+ const raw = execFileSync(
59
+ 'gh',
60
+ ['run', 'list', '--limit', String(limit), '--json', 'status,conclusion,name,createdAt,databaseId'],
61
+ { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }
62
+ );
63
+ const runs = JSON.parse(raw) as Array<Record<string, unknown>>;
64
+ return runs.map(r => ({
65
+ id: String(r['databaseId'] ?? ''),
66
+ workflow: String(r['name'] ?? ''),
67
+ status: String(r['status'] ?? ''),
68
+ result: String(r['conclusion'] ?? '-'),
69
+ started: String(r['createdAt'] ?? ''),
70
+ }));
71
+ }
72
+
73
+ function fetchGitLabRuns(limit: number): PipelineRun[] {
74
+ const raw = execFileSync(
75
+ 'glab',
76
+ ['ci', 'list', '--format', 'json'],
77
+ { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }
78
+ );
79
+ const pipelines = JSON.parse(raw) as Array<Record<string, unknown>>;
80
+ return pipelines.slice(0, limit).map(p => ({
81
+ id: String(p['id'] ?? ''),
82
+ workflow: String(p['ref'] ?? ''),
83
+ status: String(p['status'] ?? ''),
84
+ result: String(p['status'] ?? '-'),
85
+ started: String(p['created_at'] ?? ''),
86
+ url: String(p['web_url'] ?? ''),
87
+ }));
88
+ }
89
+
90
+ function fetchCircleCIRuns(): PipelineRun[] {
91
+ const raw = execFileSync(
92
+ 'circleci',
93
+ ['pipeline', 'list'],
94
+ { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }
95
+ );
96
+ // CircleCI CLI output is not always JSON; do best-effort parse
97
+ try {
98
+ const pipelines = JSON.parse(raw) as Array<Record<string, unknown>>;
99
+ return pipelines.map(p => ({
100
+ id: String(p['id'] ?? ''),
101
+ workflow: String(p['vcs'] ?? ''),
102
+ status: String(p['state'] ?? ''),
103
+ result: String(p['state'] ?? '-'),
104
+ started: String(p['created_at'] ?? ''),
105
+ }));
106
+ } catch {
107
+ return [{ id: '-', workflow: '-', status: raw.trim().slice(0, 40), result: '-', started: '-' }];
108
+ }
109
+ }
110
+
111
+ function printTable(runs: PipelineRun[]): void {
112
+ const COL = { id: 12, workflow: 30, status: 12, result: 12, started: 22 };
113
+ const pad = (s: string, n: number) => s.slice(0, n).padEnd(n);
114
+ const divider = `${'-'.repeat(COL.id + 2)}+${'-'.repeat(COL.workflow + 2)}+${'-'.repeat(COL.status + 2)}+${'-'.repeat(COL.result + 2)}+${'-'.repeat(COL.started + 2)}`;
115
+ console.log(divider);
116
+ console.log(`| ${pad('ID', COL.id)} | ${pad('Workflow', COL.workflow)} | ${pad('Status', COL.status)} | ${pad('Result', COL.result)} | ${pad('Started', COL.started)} |`);
117
+ console.log(divider);
118
+ for (const r of runs) {
119
+ console.log(`| ${pad(r.id, COL.id)} | ${pad(r.workflow, COL.workflow)} | ${pad(r.status, COL.status)} | ${pad(r.result, COL.result)} | ${pad(r.started, COL.started)} |`);
120
+ }
121
+ console.log(divider);
122
+ }
123
+
124
+ /**
125
+ * Main pipeline command dispatcher.
126
+ */
127
+ export async function pipelineCommand(subcommand: string, args: string[]): Promise<void> {
128
+ const options: PipelineOptions = { format: 'table', limit: 10 };
129
+ let runId: string | undefined;
130
+
131
+ for (let i = 0; i < args.length; i++) {
132
+ const arg = args[i];
133
+ if (arg === '--provider' && args[i + 1]) {
134
+ options.provider = args[++i] as PipelineOptions['provider'];
135
+ } else if (arg === '--format' && args[i + 1]) {
136
+ options.format = args[++i] as 'table' | 'json';
137
+ } else if (arg === '--limit' && args[i + 1]) {
138
+ options.limit = parseInt(args[++i], 10);
139
+ } else if (arg === '--json') {
140
+ options.format = 'json';
141
+ } else if (!arg.startsWith('-') && !runId && subcommand === 'status') {
142
+ runId = arg;
143
+ }
144
+ }
145
+
146
+ const provider =
147
+ options.provider === 'auto' || !options.provider
148
+ ? detectProvider()
149
+ : options.provider;
150
+
151
+ if (!provider) {
152
+ ui.error('Could not detect CI/CD provider. Use --provider github|gitlab|circleci or run in a repo with CI config.');
153
+ process.exit(1);
154
+ }
155
+
156
+ let runs: PipelineRun[];
157
+ try {
158
+ if (provider === 'github') {
159
+ runs = fetchGitHubRuns(options.limit ?? 10, runId);
160
+ } else if (provider === 'gitlab') {
161
+ runs = fetchGitLabRuns(options.limit ?? 10);
162
+ } else {
163
+ runs = fetchCircleCIRuns();
164
+ }
165
+ } catch (error: any) {
166
+ if (error.code === 'ENOENT') {
167
+ const installHints: Record<string, string> = {
168
+ github: 'Install GitHub CLI: https://cli.github.com',
169
+ gitlab: 'Install GitLab CLI: https://gitlab.com/gitlab-org/cli',
170
+ circleci: 'Install CircleCI CLI: https://circleci.com/docs/local-cli/',
171
+ };
172
+ ui.error(`CLI not found for provider "${provider}". ${installHints[provider] ?? ''}`);
173
+ } else {
174
+ ui.error(`Failed to fetch pipeline status: ${error.message}`);
175
+ }
176
+ process.exit(1);
177
+ }
178
+
179
+ if (options.format === 'json') {
180
+ console.log(JSON.stringify(runs, null, 2));
181
+ return;
182
+ }
183
+
184
+ ui.header(`Pipeline Status (${provider})`);
185
+ printTable(runs);
186
+ }