@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
@@ -41,6 +41,14 @@ const RISK_COLORS: Record<RiskLevel, string> = {
41
41
  critical: 'magenta',
42
42
  };
43
43
 
44
+ /** H4: Human-readable descriptions of each risk tier. */
45
+ const RISK_DESCRIPTIONS: Record<RiskLevel, string> = {
46
+ low: 'Read-only — safe to auto-approve',
47
+ medium: 'Modifies local files — review before approving',
48
+ high: 'System or cloud operation — approve with caution',
49
+ critical: 'Destructive or irreversible — explicit confirmation required',
50
+ };
51
+
44
52
  /**
45
53
  * Format tool input into a list of truncated key=value lines.
46
54
  */
@@ -112,12 +120,16 @@ export function PermissionPrompt({
112
120
  </Box>
113
121
 
114
122
  {/* Risk level */}
115
- <Box marginBottom={1}>
123
+ <Box>
116
124
  <Text dimColor>Risk: </Text>
117
125
  <Text bold color={riskColor}>
118
126
  {riskLevel.toUpperCase()}
119
127
  </Text>
120
128
  </Box>
129
+ {/* H4: Risk description */}
130
+ <Box marginBottom={1}>
131
+ <Text dimColor>{RISK_DESCRIPTIONS[riskLevel]}</Text>
132
+ </Box>
121
133
 
122
134
  {/* Parameters */}
123
135
  <Box flexDirection="column" marginBottom={1}>
@@ -132,19 +144,19 @@ export function PermissionPrompt({
132
144
  <Text color="green" bold inverse={pressed === 'a'}>
133
145
  [a]
134
146
  </Text>
135
- <Text> Approve </Text>
147
+ <Text>=approve </Text>
136
148
  <Text color="red" bold inverse={pressed === 'r'}>
137
149
  [r]
138
150
  </Text>
139
- <Text> Reject </Text>
151
+ <Text>=reject </Text>
140
152
  <Text color="cyan" bold inverse={pressed === 'A'}>
141
153
  [A]
142
154
  </Text>
143
- <Text> Approve all </Text>
155
+ <Text>=approve-all-session </Text>
144
156
  <Text color="blue" bold inverse={pressed === 's'}>
145
157
  [s]
146
158
  </Text>
147
- <Text> Session</Text>
159
+ <Text>=approve-this-tool-session</Text>
148
160
  </Box>
149
161
  </Box>
150
162
  );
@@ -5,7 +5,7 @@
5
5
  * percentage bar, estimated cost, snapshot count, and elapsed processing time.
6
6
  *
7
7
  * Layout:
8
- * [Plan] [Build] [Deploy] | Tokens: 45.2k/200k (22%) | Cost: $0.03 | Snapshots: 3 | 12s
8
+ * [Plan] [Build] [Deploy] | Tokens: 45.2k/200k (22%) | Cost: $0.03 | Snapshots: 3 (undo) | tf:default | k8s:prod-cluster | delta:+$1.20 | 12s
9
9
  *
10
10
  * Token percentage colour:
11
11
  * green < 50%
@@ -24,6 +24,20 @@ export interface StatusBarProps {
24
24
  isProcessing?: boolean;
25
25
  /** Timestamp (Date.now()) when processing started. Null when idle. */
26
26
  processingStartTime?: number | null;
27
+ /** Number of lines in the current input (Gap 9 — multi-line indicator). */
28
+ inputLineCount?: number;
29
+ /** C1: Show scroll hint when the user has scrolled away from the bottom. */
30
+ showScrollHint?: boolean;
31
+ /** H1: Toast message shown after copying a code block to clipboard. */
32
+ copyToast?: string;
33
+ /** Show "Esc to stop" hint when a log stream is active. */
34
+ showStreamingHint?: boolean;
35
+ /** H5: Mode change toast — shown for 2 seconds after Tab cycle. */
36
+ modeToast?: string;
37
+ /** M1: Active search query (shows "Search: N results" when set). */
38
+ searchQuery?: string;
39
+ /** M1: Number of messages matching the current search. */
40
+ searchResultCount?: number;
27
41
  }
28
42
 
29
43
  /** All modes in display order. */
@@ -55,6 +69,13 @@ function tokenColor(pct: number): string {
55
69
  return 'green';
56
70
  }
57
71
 
72
+ /**
73
+ * Determine if an environment name is production-like (G1).
74
+ */
75
+ function isProdEnvironment(name: string): boolean {
76
+ return /prod|production|live/i.test(name);
77
+ }
78
+
58
79
  /**
59
80
  * A single mode badge. The active mode is rendered with inverse styling.
60
81
  */
@@ -79,6 +100,13 @@ export function StatusBar({
79
100
  session,
80
101
  isProcessing = false,
81
102
  processingStartTime = null,
103
+ inputLineCount = 1,
104
+ showScrollHint = false,
105
+ copyToast = '',
106
+ showStreamingHint = false,
107
+ modeToast,
108
+ searchQuery,
109
+ searchResultCount,
82
110
  }: StatusBarProps) {
83
111
  const pct =
84
112
  session.maxTokens > 0 ? Math.round((session.tokenCount / session.maxTokens) * 100) : 0;
@@ -115,6 +143,24 @@ export function StatusBar({
115
143
  }
116
144
  }, [isProcessing, processingStartTime]);
117
145
 
146
+ // Snapshot display: show undo hint when snapshots exist (G19)
147
+ const snapshotDisplay = session.snapshotCount > 0
148
+ ? `${session.snapshotCount} (↶ undo)`
149
+ : String(session.snapshotCount);
150
+
151
+ // Infra context colors (G1)
152
+ const tfColor = session.terraformWorkspace && isProdEnvironment(session.terraformWorkspace)
153
+ ? 'yellow'
154
+ : 'green';
155
+ const k8sColor = session.kubectlContext && isProdEnvironment(session.kubectlContext)
156
+ ? 'yellow'
157
+ : 'green';
158
+
159
+ // L5: Build a compact visual progress bar for context budget
160
+ const BAR_WIDTH = 8;
161
+ const filledBars = Math.round((pct / 100) * BAR_WIDTH);
162
+ const progressBar = '█'.repeat(filledBars) + '░'.repeat(BAR_WIDTH - filledBars);
163
+
118
164
  return (
119
165
  <Box
120
166
  borderStyle="single"
@@ -132,26 +178,99 @@ export function StatusBar({
132
178
 
133
179
  {/* Metrics + keyboard hints */}
134
180
  <Box>
135
- <Text dimColor>Tokens: </Text>
181
+ <Text dimColor>Ctx: </Text>
182
+ <Text color={pctColor}>{progressBar}</Text>
183
+ <Text dimColor> </Text>
136
184
  <Text color={pctColor}>
137
185
  {formatTokens(session.tokenCount)}/{formatTokens(session.maxTokens)} ({pct}%)
138
186
  </Text>
139
187
  <Text dimColor> | Cost: </Text>
140
188
  <Text>{costStr}</Text>
141
189
  <Text dimColor> | Snapshots: </Text>
142
- <Text>{String(session.snapshotCount)}</Text>
190
+ <Text>{snapshotDisplay}</Text>
191
+ {/* G1: Terraform workspace display */}
192
+ {session.terraformWorkspace && (
193
+ <>
194
+ <Text dimColor> | </Text>
195
+ <Text color={tfColor}>tf:{session.terraformWorkspace}</Text>
196
+ </>
197
+ )}
198
+ {/* G1: kubectl context display */}
199
+ {session.kubectlContext && (
200
+ <>
201
+ <Text dimColor> | </Text>
202
+ <Text color={k8sColor}>k8s:{session.kubectlContext}</Text>
203
+ </>
204
+ )}
205
+ {/* G15: Infra cost delta display */}
206
+ {session.infraCostDelta && (
207
+ <>
208
+ <Text dimColor> | </Text>
209
+ <Text color="green">delta:{session.infraCostDelta}</Text>
210
+ </>
211
+ )}
143
212
  {isProcessing && elapsedSeconds > 0 && (
144
213
  <>
145
214
  <Text dimColor> | </Text>
146
215
  <Text color="cyan">{elapsedSeconds}s</Text>
147
216
  </>
148
217
  )}
218
+ {inputLineCount > 1 && !isProcessing && (
219
+ <>
220
+ <Text dimColor> | </Text>
221
+ <Text color="cyan">{inputLineCount} lines</Text>
222
+ </>
223
+ )}
149
224
  <Text dimColor> | Tab</Text>
150
225
  <Text dimColor>:mode </Text>
151
226
  <Text dimColor>Esc</Text>
152
227
  <Text dimColor>:cancel </Text>
153
228
  <Text dimColor>^C</Text>
154
229
  <Text dimColor>:exit</Text>
230
+ {/* C3: Discoverability hints when idle */}
231
+ {!isProcessing && inputLineCount <= 1 && (
232
+ <>
233
+ <Text dimColor> | </Text>
234
+ <Text dimColor>F1:help /tree /terminal</Text>
235
+ <Text dimColor> </Text>
236
+ <Text dimColor>? help</Text>
237
+ </>
238
+ )}
239
+ {/* C1: Scroll hint when user has scrolled away from bottom */}
240
+ {showScrollHint && (
241
+ <>
242
+ <Text dimColor> | </Text>
243
+ <Text color="cyan">↑↓ scroll | G bottom</Text>
244
+ </>
245
+ )}
246
+ {/* H1: Streaming tool indicator — show "Esc to stop" when a log stream is active */}
247
+ {showStreamingHint && (
248
+ <>
249
+ <Text dimColor> | </Text>
250
+ <Text color="cyan">Esc:stop stream</Text>
251
+ </>
252
+ )}
253
+ {/* H1: Copy toast message after copying a code block */}
254
+ {copyToast && (
255
+ <>
256
+ <Text dimColor> | </Text>
257
+ <Text color="green">{copyToast}</Text>
258
+ </>
259
+ )}
260
+ {/* H5: Mode change toast */}
261
+ {modeToast && (
262
+ <>
263
+ <Text dimColor> | </Text>
264
+ <Text color="cyan" italic>{modeToast}</Text>
265
+ </>
266
+ )}
267
+ {/* M1: Search result count */}
268
+ {searchQuery && searchResultCount !== undefined && (
269
+ <>
270
+ <Text dimColor> | </Text>
271
+ <Text color="yellow">Search: "{searchQuery}" — {searchResultCount} results</Text>
272
+ </>
273
+ )}
155
274
  </Box>
156
275
  </Box>
157
276
  );
@@ -0,0 +1,84 @@
1
+ /**
2
+ * TerminalPane Component (M1)
3
+ *
4
+ * Read-only tool output observation pane showing the last N lines from
5
+ * completed tool calls. Rendered alongside MessageList in a side-by-side
6
+ * layout when active. Toggle via /terminal slash command.
7
+ */
8
+
9
+ import React from 'react';
10
+ import { Box, Text } from 'ink';
11
+ import type { UIToolCall } from './types';
12
+
13
+ export interface TerminalPaneProps {
14
+ /** Tool calls to display output from. */
15
+ toolCalls: UIToolCall[];
16
+ /** Maximum number of output lines to show (default: 20). */
17
+ maxLines?: number;
18
+ /** Width percentage hint for layout (unused — parent controls width). */
19
+ width?: number;
20
+ }
21
+
22
+ /**
23
+ * A scrollable pane showing the last tool outputs as a rolling buffer.
24
+ */
25
+ export function TerminalPane({ toolCalls, maxLines = 20 }: TerminalPaneProps) {
26
+ // Collect lines from all tool calls (completed + running with streaming output)
27
+ const outputLines: Array<{ text: string; isError: boolean; toolName: string; live?: boolean }> = [];
28
+
29
+ for (const tc of toolCalls) {
30
+ if (tc.status === 'running') {
31
+ // Show live streaming output for in-progress tool calls (Gap 1)
32
+ const liveOutput = tc.streamingOutput ?? '(waiting for output...)';
33
+ const lines = liveOutput.split('\n').filter(l => l.length > 0);
34
+ for (const line of lines) {
35
+ outputLines.push({ text: line, isError: false, toolName: tc.name, live: true });
36
+ }
37
+ continue;
38
+ }
39
+ if (tc.status !== 'completed' && tc.status !== 'failed') continue;
40
+ const output = tc.result?.output ?? '';
41
+ const isError = tc.result?.isError ?? false;
42
+ const lines = output.split('\n').filter(l => l.length > 0);
43
+ for (const line of lines) {
44
+ outputLines.push({ text: line, isError, toolName: tc.name });
45
+ }
46
+ }
47
+
48
+ // Show only the last maxLines lines
49
+ const visible = outputLines.slice(-maxLines);
50
+
51
+ return (
52
+ <Box
53
+ flexDirection="column"
54
+ borderStyle="single"
55
+ borderColor="gray"
56
+ paddingX={1}
57
+ flexGrow={1}
58
+ overflow="hidden"
59
+ >
60
+ {/* Header */}
61
+ <Box marginBottom={1}>
62
+ <Text bold color="cyan">
63
+ Terminal Output
64
+ </Text>
65
+ <Text dimColor> (read-only · /terminal to toggle)</Text>
66
+ </Box>
67
+
68
+ {visible.length === 0 ? (
69
+ <Text dimColor italic>No tool output yet.</Text>
70
+ ) : (
71
+ visible.map((line, i) => (
72
+ <Text
73
+ key={i}
74
+ color={line.isError ? 'red' : line.live ? 'cyan' : undefined}
75
+ dimColor={!line.isError && !line.live}
76
+ wrap="truncate"
77
+ >
78
+ {line.live ? '[>] ' : ''}{line.text}
79
+ </Text>
80
+ ))
81
+ )}
82
+ </Box>
83
+ );
84
+ }
@@ -15,9 +15,10 @@
15
15
  * All other tools fall through to a generic key/value display.
16
16
  */
17
17
 
18
- import React, { useState } from 'react';
18
+ import React, { useState, useEffect } from 'react';
19
19
  import { Box, Text, useInput } from 'ink';
20
20
  import Spinner from 'ink-spinner';
21
+ import { parseTerraformPlanOutput } from '../agent/deploy-preview';
21
22
  import type { UIToolCall } from './types';
22
23
 
23
24
  /** Props accepted by the ToolCallDisplay component. */
@@ -36,7 +37,25 @@ const COLLAPSED_LINES = 20;
36
37
  * Status badge
37
38
  * -------------------------------------------------------------------------*/
38
39
 
39
- function StatusBadge({ status }: { status: UIToolCall['status'] }) {
40
+ /** Long-running tools that get a context hint about expected duration. */
41
+ const LONG_RUNNING_TOOLS = new Set([
42
+ 'terraform', 'terraform_plan_analyze', 'deploy_preview', 'drift_detect',
43
+ 'helm', 'k8s_rbac', 'cfn', 'gitops',
44
+ ]);
45
+
46
+ function StatusBadge({ status, startTime }: { status: UIToolCall['status']; startTime?: number }) {
47
+ const [elapsed, setElapsed] = useState(0);
48
+
49
+ useEffect(() => {
50
+ if (status !== 'running' || !startTime) return;
51
+ const initial = Math.floor((Date.now() - startTime) / 1000);
52
+ setElapsed(initial);
53
+ const id = setInterval(() => {
54
+ setElapsed(Math.floor((Date.now() - startTime) / 1000));
55
+ }, 1000);
56
+ return () => clearInterval(id);
57
+ }, [status, startTime]);
58
+
40
59
  switch (status) {
41
60
  case 'pending':
42
61
  return <Text dimColor>[pending]</Text>;
@@ -44,6 +63,7 @@ function StatusBadge({ status }: { status: UIToolCall['status'] }) {
44
63
  return (
45
64
  <Text color="cyan">
46
65
  <Spinner type="dots" />
66
+ {startTime && elapsed > 0 ? ` ${elapsed}s` : ''}
47
67
  </Text>
48
68
  );
49
69
  case 'completed':
@@ -258,31 +278,129 @@ function TerraformBody({
258
278
  input: Record<string, unknown>;
259
279
  result?: UIToolCall['result'];
260
280
  }) {
261
- const subcommand = String(input.command ?? input.subcommand ?? 'plan');
281
+ const action = String(input.action ?? input.command ?? input.subcommand ?? 'plan');
282
+
283
+ // Gap 8: Show structured plan summary when output contains plan data
284
+ const isPlan = action === 'plan' || (result?.output ?? '').includes('Plan:');
285
+ const changes = isPlan && result && !result.isError
286
+ ? parseTerraformPlanOutput(result.output)
287
+ : [];
288
+
289
+ const creates = changes.filter(c => c.action === 'create').length;
290
+ const updates = changes.filter(c => c.action === 'update').length;
291
+ const destroys = changes.filter(c => c.action === 'destroy').length;
292
+ const replaces = changes.filter(c => c.action === 'replace').length;
262
293
 
263
294
  return (
264
295
  <Box flexDirection="column">
265
296
  <Text>
266
297
  <Text dimColor>terraform </Text>
267
- <Text bold>{subcommand}</Text>
298
+ <Text bold>{action}</Text>
268
299
  </Text>
269
- {result && !result.isError && (
270
- <Box flexDirection="column" marginTop={1}>
271
- {result.output.split('\n').map((line, i) => {
272
- let color: string | undefined;
273
- if (line.startsWith('+') || line.includes('will be created')) {
274
- color = 'green';
275
- } else if (line.startsWith('-') || line.includes('will be destroyed')) {
276
- color = 'red';
277
- } else if (line.startsWith('~') || line.includes('will be updated')) {
278
- color = 'yellow';
279
- }
300
+
301
+ {/* Gap 8: Structured plan summary panel */}
302
+ {isPlan && changes.length > 0 && result && !result.isError && (
303
+ <Box flexDirection="column" marginTop={1} borderStyle="single" borderColor="gray" paddingX={1}>
304
+ <Text bold>Plan Summary</Text>
305
+ <Box>
306
+ {creates > 0 && <Text color="green">+{creates} create </Text>}
307
+ {updates > 0 && <Text color="yellow">~{updates} change </Text>}
308
+ {destroys > 0 && <Text color="red">-{destroys} destroy </Text>}
309
+ {replaces > 0 && <Text color="magenta">±{replaces} replace </Text>}
310
+ {creates === 0 && updates === 0 && destroys === 0 && replaces === 0 && (
311
+ <Text dimColor>No changes</Text>
312
+ )}
313
+ </Box>
314
+ {changes.slice(0, 10).map((c, i) => {
315
+ const icon = c.action === 'create' ? '+' : c.action === 'destroy' ? '-' : c.action === 'replace' ? '±' : '~';
316
+ const color = c.action === 'create' ? 'green' : c.action === 'destroy' ? 'red' : c.action === 'replace' ? 'magenta' : 'yellow';
280
317
  return (
281
- <Text key={i} color={color} dimColor={!color}>
282
- {line}
318
+ <Text key={i} color={color}>
319
+ {icon} {c.resource}
283
320
  </Text>
284
321
  );
285
322
  })}
323
+ {changes.length > 10 && <Text dimColor>... and {changes.length - 10} more</Text>}
324
+ </Box>
325
+ )}
326
+
327
+ {/* H1: Fallback raw line coloring — show up to 200 lines with indicator for truncation */}
328
+ {!(isPlan && changes.length > 0) && result && !result.isError && (
329
+ <Box flexDirection="column" marginTop={1}>
330
+ {(() => {
331
+ const lines = result.output.split('\n');
332
+ const MAX_LINES = 200;
333
+ const displayLines = lines.slice(0, MAX_LINES);
334
+ return (
335
+ <>
336
+ {displayLines.map((line, i) => {
337
+ let color: string | undefined;
338
+ if (line.startsWith('+') || line.includes('will be created')) {
339
+ color = 'green';
340
+ } else if (line.startsWith('-') || line.includes('will be destroyed')) {
341
+ color = 'red';
342
+ } else if (line.startsWith('~') || line.includes('will be updated')) {
343
+ color = 'yellow';
344
+ }
345
+ return (
346
+ <Text key={i} color={color} dimColor={!color}>
347
+ {line}
348
+ </Text>
349
+ );
350
+ })}
351
+ {lines.length > MAX_LINES && (
352
+ <Text dimColor>... {lines.length - MAX_LINES} more lines (full output saved to tool history)</Text>
353
+ )}
354
+ </>
355
+ );
356
+ })()}
357
+ </Box>
358
+ )}
359
+ {result && result.isError && <Text color="red">{result.output}</Text>}
360
+ </Box>
361
+ );
362
+ }
363
+
364
+ /** G12: Kubectl output renderer with status colorization */
365
+ function KubectlBody({
366
+ input,
367
+ result,
368
+ }: {
369
+ input: Record<string, unknown>;
370
+ result?: UIToolCall['result'];
371
+ }) {
372
+ const action = String(input.action ?? input.command ?? 'get');
373
+
374
+ const colorizeKubectlLine = (line: string, i: number): React.ReactNode => {
375
+ // Status coloring for kubectl get output
376
+ if (/\bRunning\b/.test(line)) {
377
+ return <Text key={i} color="green">{line}</Text>;
378
+ }
379
+ if (/\b(Pending|ContainerCreating|Init:|PodInitializing)\b/.test(line)) {
380
+ return <Text key={i} color="yellow">{line}</Text>;
381
+ }
382
+ if (/\b(CrashLoopBackOff|Error|Failed|OOMKilled|ImagePullBackOff|ErrImagePull)\b/.test(line)) {
383
+ return <Text key={i} color="red">{line}</Text>;
384
+ }
385
+ if (/\bCompleted\b/.test(line)) {
386
+ return <Text key={i} color="green" dimColor>{line}</Text>;
387
+ }
388
+ if (/\bTerminating\b/.test(line)) {
389
+ return <Text key={i} color="yellow" dimColor>{line}</Text>;
390
+ }
391
+ return <Text key={i} dimColor>{line}</Text>;
392
+ };
393
+
394
+ return (
395
+ <Box flexDirection="column">
396
+ <Text>
397
+ <Text dimColor>kubectl </Text>
398
+ <Text bold>{action}</Text>
399
+ </Text>
400
+ {result && !result.isError && (
401
+ <Box flexDirection="column" marginTop={1}>
402
+ {/* M1: Increased from 40 to 80 lines for better kubectl output visibility */}
403
+ {result.output.split('\n').slice(0, 80).map((line, i) => colorizeKubectlLine(line, i))}
286
404
  </Box>
287
405
  )}
288
406
  {result && result.isError && <Text color="red">{result.output}</Text>}
@@ -290,6 +408,66 @@ function TerraformBody({
290
408
  );
291
409
  }
292
410
 
411
+ /** M2: Docker build progress renderer */
412
+ function DockerBuildBody({
413
+ input,
414
+ result,
415
+ streamingOutput,
416
+ }: {
417
+ input: Record<string, unknown>;
418
+ result?: UIToolCall['result'];
419
+ streamingOutput?: string;
420
+ }) {
421
+ const action = String(input.action ?? '');
422
+ if (action !== 'build') {
423
+ // Non-build actions: show raw output
424
+ if (result && !result.isError) {
425
+ return (
426
+ <Box flexDirection="column" marginTop={1}>
427
+ {result.output.split('\n').slice(0, 20).map((line, i) => (
428
+ <Text key={i} dimColor>{line}</Text>
429
+ ))}
430
+ </Box>
431
+ );
432
+ }
433
+ if (result?.isError) return <Text color="red">{result.output}</Text>;
434
+ return null;
435
+ }
436
+
437
+ // Parse step progress from streaming output or result
438
+ const outputText = streamingOutput || result?.output || '';
439
+ const stepMatch = outputText.match(/Step\s+(\d+)\/(\d+)/gi);
440
+ const lastStep = stepMatch ? stepMatch[stepMatch.length - 1] : null;
441
+ const totalSteps = lastStep ? parseInt(lastStep.match(/\/(\d+)/)![1]) : 0;
442
+ const currentStep = lastStep ? parseInt(lastStep.match(/Step\s+(\d+)/i)![1]) : 0;
443
+ const succeeded = outputText.includes('Successfully built') || outputText.includes('Successfully tagged');
444
+ const failed = result?.isError || false;
445
+
446
+ return (
447
+ <Box flexDirection="column" marginTop={1}>
448
+ {totalSteps > 0 && (
449
+ <Box>
450
+ <Text color={succeeded ? 'green' : failed ? 'red' : 'cyan'}>
451
+ {succeeded ? '[ok] ' : failed ? '[xx] ' : '[..] '}
452
+ [{currentStep}/{totalSteps} steps]
453
+ </Text>
454
+ </Box>
455
+ )}
456
+ {outputText.split('\n').filter(l => l.trim()).slice(-5).map((line, i) => {
457
+ const isStep = /^Step\s+\d+\/\d+/i.test(line.trim());
458
+ const isSuccess = /Successfully/i.test(line);
459
+ const isError = /error/i.test(line);
460
+ return (
461
+ <Text key={i} color={isSuccess ? 'green' : isError ? 'red' : isStep ? 'cyan' : undefined} dimColor={!isStep && !isSuccess && !isError}>
462
+ {line}
463
+ </Text>
464
+ );
465
+ })}
466
+ {result?.isError && <Text color="red">{result.output}</Text>}
467
+ </Box>
468
+ );
469
+ }
470
+
293
471
  function GenericBody({
294
472
  input,
295
473
  result,
@@ -332,6 +510,7 @@ function GenericBody({
332
510
 
333
511
  function ToolCallBox({ toolCall, expanded }: { toolCall: UIToolCall; expanded: boolean }) {
334
512
  const durationLabel = toolCall.duration != null ? ` (${toolCall.duration}ms)` : '';
513
+ const isLongRunning = LONG_RUNNING_TOOLS.has(toolCall.name.toLowerCase());
335
514
 
336
515
  // Choose specialised body renderer based on tool name
337
516
  const renderBody = () => {
@@ -362,6 +541,12 @@ function ToolCallBox({ toolCall, expanded }: { toolCall: UIToolCall; expanded: b
362
541
  if (name.startsWith('terraform') || name === 'tf_plan' || name === 'tf_apply') {
363
542
  return <TerraformBody {...props} />;
364
543
  }
544
+ if (name === 'kubectl' || name === 'k8s') {
545
+ return <KubectlBody {...props} />;
546
+ }
547
+ if (name === 'docker') {
548
+ return <DockerBuildBody input={toolCall.input} result={toolCall.result} streamingOutput={toolCall.streamingOutput} />;
549
+ }
365
550
  return <GenericBody {...props} />;
366
551
  };
367
552
 
@@ -375,11 +560,60 @@ function ToolCallBox({ toolCall, expanded }: { toolCall: UIToolCall; expanded: b
375
560
  >
376
561
  {/* Header */}
377
562
  <Box>
378
- <StatusBadge status={toolCall.status} />
563
+ <StatusBadge status={toolCall.status} startTime={toolCall.startTime} />
379
564
  <Text bold> {toolCall.name}</Text>
380
565
  <Text dimColor>{durationLabel}</Text>
566
+ {/* M4: Highlight duration prominently when operation took > 5 seconds */}
567
+ {toolCall.status === 'completed' && toolCall.duration != null && toolCall.duration > 5000 && (
568
+ <Text dimColor> [{(toolCall.duration / 1000).toFixed(1)}s]</Text>
569
+ )}
570
+ {toolCall.status === 'running' && toolCall.name === 'logs' && (
571
+ <Text color="cyan"> ● LIVE</Text>
572
+ )}
381
573
  </Box>
382
574
 
575
+ {/* Long-running hint */}
576
+ {toolCall.status === 'running' && isLongRunning && (
577
+ <Text dimColor italic>
578
+ This may take several minutes for large infrastructure changes.
579
+ </Text>
580
+ )}
581
+
582
+ {/* Streaming output — shown while tool is running */}
583
+ {toolCall.status === 'running' && toolCall.streamingOutput && toolCall.streamingOutput.trim() && (
584
+ <Box flexDirection="column" marginTop={1} marginLeft={2}>
585
+ <Box>
586
+ <Text color="green" bold>[LIVE] </Text>
587
+ <Text>{'─'.repeat(34)}</Text>
588
+ </Box>
589
+ {(() => {
590
+ const allLines = toolCall.streamingOutput!.split('\n');
591
+ const isTerraformOrKubectl = toolCall.name === 'terraform' || toolCall.name === 'kubectl' || toolCall.name === 'logs';
592
+ // M1: Increased streaming window — 60 lines for terraform/kubectl/logs, 40 for others
593
+ const windowSize = isTerraformOrKubectl ? 60 : 40;
594
+ const visibleLines = allLines.slice(-windowSize);
595
+ // Pad to minimum 4 lines so the live area is always visible
596
+ while (visibleLines.length < 4) visibleLines.push('');
597
+ const hiddenCount = Math.max(0, allLines.length - windowSize);
598
+ return (
599
+ <>
600
+ {hiddenCount > 0 && (
601
+ <Text dimColor>... {hiddenCount} earlier lines</Text>
602
+ )}
603
+ {visibleLines.map((line, i) => {
604
+ // M2: Color terraform/kubectl streaming output lines
605
+ let lineColor: string | undefined;
606
+ if (line.match(/^\s*\+/) || line.includes('will be created') || line.includes(' created')) lineColor = 'green';
607
+ else if (line.match(/^\s*-/) || line.includes('will be destroyed') || line.includes(' destroyed')) lineColor = 'red';
608
+ else if (line.match(/^\s*~/) || line.includes('will be updated') || line.includes(' modified')) lineColor = 'yellow';
609
+ return <Text key={i} color={lineColor ?? 'gray'} dimColor={!lineColor}>{line}</Text>;
610
+ })}
611
+ </>
612
+ );
613
+ })()}
614
+ </Box>
615
+ )}
616
+
383
617
  {/* Body */}
384
618
  <Box marginTop={1}>{renderBody()}</Box>
385
619
  </Box>