@build-astron-co/nimbus 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (313) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +628 -0
  3. package/bin/nimbus +38 -0
  4. package/package.json +80 -0
  5. package/src/__tests__/app.test.ts +76 -0
  6. package/src/__tests__/audit.test.ts +877 -0
  7. package/src/__tests__/circuit-breaker.test.ts +116 -0
  8. package/src/__tests__/cli-run.test.ts +115 -0
  9. package/src/__tests__/context-manager.test.ts +502 -0
  10. package/src/__tests__/context.test.ts +242 -0
  11. package/src/__tests__/enterprise.test.ts +401 -0
  12. package/src/__tests__/generator.test.ts +433 -0
  13. package/src/__tests__/hooks.test.ts +582 -0
  14. package/src/__tests__/init.test.ts +436 -0
  15. package/src/__tests__/intent-parser.test.ts +229 -0
  16. package/src/__tests__/llm-router.test.ts +209 -0
  17. package/src/__tests__/lsp.test.ts +293 -0
  18. package/src/__tests__/modes.test.ts +336 -0
  19. package/src/__tests__/permissions.test.ts +338 -0
  20. package/src/__tests__/serve.test.ts +275 -0
  21. package/src/__tests__/sessions.test.ts +227 -0
  22. package/src/__tests__/sharing.test.ts +288 -0
  23. package/src/__tests__/snapshots.test.ts +581 -0
  24. package/src/__tests__/state-db.test.ts +334 -0
  25. package/src/__tests__/stream-with-tools.test.ts +732 -0
  26. package/src/__tests__/subagents.test.ts +176 -0
  27. package/src/__tests__/system-prompt.test.ts +169 -0
  28. package/src/__tests__/tool-converter.test.ts +256 -0
  29. package/src/__tests__/tool-schemas.test.ts +397 -0
  30. package/src/__tests__/tools.test.ts +143 -0
  31. package/src/__tests__/version.test.ts +49 -0
  32. package/src/agent/compaction-agent.ts +227 -0
  33. package/src/agent/context-manager.ts +435 -0
  34. package/src/agent/context.ts +427 -0
  35. package/src/agent/deploy-preview.ts +426 -0
  36. package/src/agent/index.ts +68 -0
  37. package/src/agent/loop.ts +717 -0
  38. package/src/agent/modes.ts +429 -0
  39. package/src/agent/permissions.ts +466 -0
  40. package/src/agent/subagents/base.ts +116 -0
  41. package/src/agent/subagents/cost.ts +51 -0
  42. package/src/agent/subagents/explore.ts +42 -0
  43. package/src/agent/subagents/general.ts +54 -0
  44. package/src/agent/subagents/index.ts +102 -0
  45. package/src/agent/subagents/infra.ts +59 -0
  46. package/src/agent/subagents/security.ts +69 -0
  47. package/src/agent/system-prompt.ts +436 -0
  48. package/src/app.ts +122 -0
  49. package/src/audit/activity-log.ts +290 -0
  50. package/src/audit/compliance-checker.ts +540 -0
  51. package/src/audit/cost-tracker.ts +318 -0
  52. package/src/audit/index.ts +23 -0
  53. package/src/audit/security-scanner.ts +596 -0
  54. package/src/auth/guard.ts +75 -0
  55. package/src/auth/index.ts +56 -0
  56. package/src/auth/oauth.ts +455 -0
  57. package/src/auth/providers.ts +470 -0
  58. package/src/auth/sso.ts +113 -0
  59. package/src/auth/store.ts +505 -0
  60. package/src/auth/types.ts +187 -0
  61. package/src/build.ts +141 -0
  62. package/src/cli/index.ts +16 -0
  63. package/src/cli/init.ts +854 -0
  64. package/src/cli/openapi-spec.ts +356 -0
  65. package/src/cli/run.ts +237 -0
  66. package/src/cli/serve-auth.ts +80 -0
  67. package/src/cli/serve.ts +462 -0
  68. package/src/cli/web.ts +67 -0
  69. package/src/cli.ts +1417 -0
  70. package/src/clients/core-engine-client.ts +227 -0
  71. package/src/clients/enterprise-client.ts +334 -0
  72. package/src/clients/generator-client.ts +351 -0
  73. package/src/clients/git-client.ts +627 -0
  74. package/src/clients/github-client.ts +410 -0
  75. package/src/clients/helm-client.ts +504 -0
  76. package/src/clients/index.ts +80 -0
  77. package/src/clients/k8s-client.ts +497 -0
  78. package/src/clients/llm-client.ts +161 -0
  79. package/src/clients/rest-client.ts +130 -0
  80. package/src/clients/service-discovery.ts +33 -0
  81. package/src/clients/terraform-client.ts +482 -0
  82. package/src/clients/tools-client.ts +1843 -0
  83. package/src/clients/ws-client.ts +115 -0
  84. package/src/commands/analyze/index.ts +352 -0
  85. package/src/commands/apply/helm.ts +473 -0
  86. package/src/commands/apply/index.ts +213 -0
  87. package/src/commands/apply/k8s.ts +454 -0
  88. package/src/commands/apply/terraform.ts +582 -0
  89. package/src/commands/ask.ts +167 -0
  90. package/src/commands/audit/index.ts +238 -0
  91. package/src/commands/auth-cloud.ts +294 -0
  92. package/src/commands/auth-list.ts +134 -0
  93. package/src/commands/auth-profile.ts +121 -0
  94. package/src/commands/auth-status.ts +141 -0
  95. package/src/commands/aws/ec2.ts +501 -0
  96. package/src/commands/aws/iam.ts +397 -0
  97. package/src/commands/aws/index.ts +133 -0
  98. package/src/commands/aws/lambda.ts +396 -0
  99. package/src/commands/aws/rds.ts +439 -0
  100. package/src/commands/aws/s3.ts +439 -0
  101. package/src/commands/aws/vpc.ts +393 -0
  102. package/src/commands/aws-discover.ts +649 -0
  103. package/src/commands/aws-terraform.ts +805 -0
  104. package/src/commands/azure/aks.ts +376 -0
  105. package/src/commands/azure/functions.ts +253 -0
  106. package/src/commands/azure/index.ts +116 -0
  107. package/src/commands/azure/storage.ts +478 -0
  108. package/src/commands/azure/vm.ts +355 -0
  109. package/src/commands/billing/index.ts +256 -0
  110. package/src/commands/chat.ts +314 -0
  111. package/src/commands/config.ts +346 -0
  112. package/src/commands/cost/cloud-cost-estimator.ts +266 -0
  113. package/src/commands/cost/estimator.ts +79 -0
  114. package/src/commands/cost/index.ts +594 -0
  115. package/src/commands/cost/parsers/terraform.ts +273 -0
  116. package/src/commands/cost/parsers/types.ts +25 -0
  117. package/src/commands/cost/pricing/aws.ts +544 -0
  118. package/src/commands/cost/pricing/azure.ts +499 -0
  119. package/src/commands/cost/pricing/gcp.ts +396 -0
  120. package/src/commands/cost/pricing/index.ts +40 -0
  121. package/src/commands/demo.ts +250 -0
  122. package/src/commands/doctor.ts +794 -0
  123. package/src/commands/drift/index.ts +439 -0
  124. package/src/commands/explain.ts +277 -0
  125. package/src/commands/feedback.ts +389 -0
  126. package/src/commands/fix.ts +324 -0
  127. package/src/commands/fs/index.ts +402 -0
  128. package/src/commands/gcp/compute.ts +325 -0
  129. package/src/commands/gcp/functions.ts +271 -0
  130. package/src/commands/gcp/gke.ts +438 -0
  131. package/src/commands/gcp/iam.ts +344 -0
  132. package/src/commands/gcp/index.ts +129 -0
  133. package/src/commands/gcp/storage.ts +284 -0
  134. package/src/commands/generate-helm.ts +1249 -0
  135. package/src/commands/generate-k8s.ts +1560 -0
  136. package/src/commands/generate-terraform.ts +1460 -0
  137. package/src/commands/gh/index.ts +863 -0
  138. package/src/commands/git/index.ts +1343 -0
  139. package/src/commands/helm/index.ts +1126 -0
  140. package/src/commands/help.ts +539 -0
  141. package/src/commands/history.ts +142 -0
  142. package/src/commands/import.ts +868 -0
  143. package/src/commands/index.ts +367 -0
  144. package/src/commands/init.ts +1046 -0
  145. package/src/commands/k8s/index.ts +1137 -0
  146. package/src/commands/login.ts +631 -0
  147. package/src/commands/logout.ts +83 -0
  148. package/src/commands/onboarding.ts +228 -0
  149. package/src/commands/plan/display.ts +279 -0
  150. package/src/commands/plan/index.ts +599 -0
  151. package/src/commands/preview.ts +452 -0
  152. package/src/commands/questionnaire.ts +1270 -0
  153. package/src/commands/resume.ts +55 -0
  154. package/src/commands/team/index.ts +346 -0
  155. package/src/commands/template.ts +232 -0
  156. package/src/commands/tf/index.ts +1034 -0
  157. package/src/commands/upgrade.ts +550 -0
  158. package/src/commands/usage/index.ts +134 -0
  159. package/src/commands/version.ts +170 -0
  160. package/src/compat/index.ts +2 -0
  161. package/src/compat/runtime.ts +12 -0
  162. package/src/compat/sqlite.ts +107 -0
  163. package/src/config/index.ts +17 -0
  164. package/src/config/manager.ts +530 -0
  165. package/src/config/safety-policy.ts +358 -0
  166. package/src/config/schema.ts +125 -0
  167. package/src/config/types.ts +527 -0
  168. package/src/context/context-db.ts +199 -0
  169. package/src/demo/index.ts +349 -0
  170. package/src/demo/scenarios/full-journey.ts +229 -0
  171. package/src/demo/scenarios/getting-started.ts +127 -0
  172. package/src/demo/scenarios/helm-release.ts +341 -0
  173. package/src/demo/scenarios/k8s-deployment.ts +194 -0
  174. package/src/demo/scenarios/terraform-vpc.ts +170 -0
  175. package/src/demo/types.ts +92 -0
  176. package/src/engine/cost-estimator.ts +438 -0
  177. package/src/engine/diagram-generator.ts +256 -0
  178. package/src/engine/drift-detector.ts +902 -0
  179. package/src/engine/executor.ts +1035 -0
  180. package/src/engine/index.ts +76 -0
  181. package/src/engine/orchestrator.ts +636 -0
  182. package/src/engine/planner.ts +720 -0
  183. package/src/engine/safety.ts +743 -0
  184. package/src/engine/verifier.ts +770 -0
  185. package/src/enterprise/audit.ts +348 -0
  186. package/src/enterprise/auth.ts +270 -0
  187. package/src/enterprise/billing.ts +822 -0
  188. package/src/enterprise/index.ts +17 -0
  189. package/src/enterprise/teams.ts +443 -0
  190. package/src/generator/best-practices.ts +1608 -0
  191. package/src/generator/helm.ts +630 -0
  192. package/src/generator/index.ts +37 -0
  193. package/src/generator/intent-parser.ts +514 -0
  194. package/src/generator/kubernetes.ts +976 -0
  195. package/src/generator/terraform.ts +1867 -0
  196. package/src/history/index.ts +8 -0
  197. package/src/history/manager.ts +322 -0
  198. package/src/history/types.ts +34 -0
  199. package/src/hooks/config.ts +432 -0
  200. package/src/hooks/engine.ts +391 -0
  201. package/src/hooks/index.ts +4 -0
  202. package/src/llm/auth-bridge.ts +198 -0
  203. package/src/llm/circuit-breaker.ts +140 -0
  204. package/src/llm/config-loader.ts +201 -0
  205. package/src/llm/cost-calculator.ts +171 -0
  206. package/src/llm/index.ts +8 -0
  207. package/src/llm/model-aliases.ts +115 -0
  208. package/src/llm/provider-registry.ts +63 -0
  209. package/src/llm/providers/anthropic.ts +433 -0
  210. package/src/llm/providers/bedrock.ts +477 -0
  211. package/src/llm/providers/google.ts +405 -0
  212. package/src/llm/providers/ollama.ts +767 -0
  213. package/src/llm/providers/openai-compatible.ts +340 -0
  214. package/src/llm/providers/openai.ts +328 -0
  215. package/src/llm/providers/openrouter.ts +338 -0
  216. package/src/llm/router.ts +1035 -0
  217. package/src/llm/types.ts +232 -0
  218. package/src/lsp/client.ts +298 -0
  219. package/src/lsp/languages.ts +116 -0
  220. package/src/lsp/manager.ts +278 -0
  221. package/src/mcp/client.ts +402 -0
  222. package/src/mcp/index.ts +5 -0
  223. package/src/mcp/manager.ts +133 -0
  224. package/src/nimbus.ts +214 -0
  225. package/src/plugins/index.ts +27 -0
  226. package/src/plugins/loader.ts +334 -0
  227. package/src/plugins/manager.ts +376 -0
  228. package/src/plugins/types.ts +284 -0
  229. package/src/scanners/cicd-scanner.ts +258 -0
  230. package/src/scanners/cloud-scanner.ts +466 -0
  231. package/src/scanners/framework-scanner.ts +469 -0
  232. package/src/scanners/iac-scanner.ts +388 -0
  233. package/src/scanners/index.ts +539 -0
  234. package/src/scanners/language-scanner.ts +276 -0
  235. package/src/scanners/package-manager-scanner.ts +277 -0
  236. package/src/scanners/types.ts +172 -0
  237. package/src/sessions/manager.ts +365 -0
  238. package/src/sessions/types.ts +44 -0
  239. package/src/sharing/sync.ts +296 -0
  240. package/src/sharing/viewer.ts +97 -0
  241. package/src/snapshots/index.ts +2 -0
  242. package/src/snapshots/manager.ts +530 -0
  243. package/src/state/artifacts.ts +147 -0
  244. package/src/state/audit.ts +137 -0
  245. package/src/state/billing.ts +240 -0
  246. package/src/state/checkpoints.ts +117 -0
  247. package/src/state/config.ts +67 -0
  248. package/src/state/conversations.ts +14 -0
  249. package/src/state/credentials.ts +154 -0
  250. package/src/state/db.ts +58 -0
  251. package/src/state/index.ts +26 -0
  252. package/src/state/messages.ts +115 -0
  253. package/src/state/projects.ts +123 -0
  254. package/src/state/schema.ts +236 -0
  255. package/src/state/sessions.ts +147 -0
  256. package/src/state/teams.ts +200 -0
  257. package/src/telemetry.ts +108 -0
  258. package/src/tools/aws-ops.ts +952 -0
  259. package/src/tools/azure-ops.ts +579 -0
  260. package/src/tools/file-ops.ts +593 -0
  261. package/src/tools/gcp-ops.ts +625 -0
  262. package/src/tools/git-ops.ts +773 -0
  263. package/src/tools/github-ops.ts +799 -0
  264. package/src/tools/helm-ops.ts +943 -0
  265. package/src/tools/index.ts +17 -0
  266. package/src/tools/k8s-ops.ts +819 -0
  267. package/src/tools/schemas/converter.ts +184 -0
  268. package/src/tools/schemas/devops.ts +612 -0
  269. package/src/tools/schemas/index.ts +73 -0
  270. package/src/tools/schemas/standard.ts +1144 -0
  271. package/src/tools/schemas/types.ts +705 -0
  272. package/src/tools/terraform-ops.ts +862 -0
  273. package/src/types/ambient.d.ts +193 -0
  274. package/src/types/config.ts +83 -0
  275. package/src/types/drift.ts +116 -0
  276. package/src/types/enterprise.ts +335 -0
  277. package/src/types/index.ts +20 -0
  278. package/src/types/plan.ts +44 -0
  279. package/src/types/request.ts +65 -0
  280. package/src/types/response.ts +54 -0
  281. package/src/types/service.ts +51 -0
  282. package/src/ui/App.tsx +997 -0
  283. package/src/ui/DeployPreview.tsx +169 -0
  284. package/src/ui/Header.tsx +68 -0
  285. package/src/ui/InputBox.tsx +350 -0
  286. package/src/ui/MessageList.tsx +585 -0
  287. package/src/ui/PermissionPrompt.tsx +151 -0
  288. package/src/ui/StatusBar.tsx +158 -0
  289. package/src/ui/ToolCallDisplay.tsx +409 -0
  290. package/src/ui/chat-ui.ts +853 -0
  291. package/src/ui/index.ts +33 -0
  292. package/src/ui/ink/index.ts +711 -0
  293. package/src/ui/streaming.ts +176 -0
  294. package/src/ui/types.ts +57 -0
  295. package/src/utils/analytics.ts +72 -0
  296. package/src/utils/cost-warning.ts +27 -0
  297. package/src/utils/env.ts +46 -0
  298. package/src/utils/errors.ts +69 -0
  299. package/src/utils/event-bus.ts +38 -0
  300. package/src/utils/index.ts +24 -0
  301. package/src/utils/logger.ts +171 -0
  302. package/src/utils/rate-limiter.ts +121 -0
  303. package/src/utils/service-auth.ts +49 -0
  304. package/src/utils/validation.ts +53 -0
  305. package/src/version.ts +4 -0
  306. package/src/watcher/index.ts +163 -0
  307. package/src/wizard/approval.ts +383 -0
  308. package/src/wizard/index.ts +25 -0
  309. package/src/wizard/prompts.ts +338 -0
  310. package/src/wizard/types.ts +171 -0
  311. package/src/wizard/ui.ts +556 -0
  312. package/src/wizard/wizard.ts +304 -0
  313. package/tsconfig.json +24 -0
@@ -0,0 +1,409 @@
1
+ /**
2
+ * ToolCallDisplay Component
3
+ *
4
+ * Renders one or more tool invocations inline with the conversation. Each tool
5
+ * call is shown in a bordered box with a header containing the tool name and
6
+ * status indicator. While a tool is running the box shows a Spinner; on
7
+ * completion or failure it shows a condensed result summary.
8
+ *
9
+ * Specialised renderers exist for common tools:
10
+ * - read_file: filename + optional line range
11
+ * - edit_file: unified diff with context lines, red/green colouring
12
+ * - bash: command + expandable/collapsible output
13
+ * - terraform: resource table
14
+ *
15
+ * All other tools fall through to a generic key/value display.
16
+ */
17
+
18
+ import React, { useState } from 'react';
19
+ import { Box, Text, useInput } from 'ink';
20
+ import Spinner from 'ink-spinner';
21
+ import type { UIToolCall } from './types';
22
+
23
+ /** Props accepted by the ToolCallDisplay component. */
24
+ export interface ToolCallDisplayProps {
25
+ toolCalls: UIToolCall[];
26
+ /** Whether tool call detail is expanded. Defaults to true. */
27
+ expanded?: boolean;
28
+ }
29
+
30
+ /** Maximum number of output lines shown for bash tool results. */
31
+ const MAX_BASH_OUTPUT_LINES = 50;
32
+ /** Lines shown in collapsed view. */
33
+ const COLLAPSED_LINES = 20;
34
+
35
+ /* ---------------------------------------------------------------------------
36
+ * Status badge
37
+ * -------------------------------------------------------------------------*/
38
+
39
+ function StatusBadge({ status }: { status: UIToolCall['status'] }) {
40
+ switch (status) {
41
+ case 'pending':
42
+ return <Text dimColor>[pending]</Text>;
43
+ case 'running':
44
+ return (
45
+ <Text color="cyan">
46
+ <Spinner type="dots" />
47
+ </Text>
48
+ );
49
+ case 'completed':
50
+ return <Text color="green">[done]</Text>;
51
+ case 'failed':
52
+ return <Text color="red">[failed]</Text>;
53
+ }
54
+ }
55
+
56
+ /* ---------------------------------------------------------------------------
57
+ * Per-tool body renderers
58
+ * -------------------------------------------------------------------------*/
59
+
60
+ function ReadFileBody({
61
+ input,
62
+ result,
63
+ }: {
64
+ input: Record<string, unknown>;
65
+ result?: UIToolCall['result'];
66
+ }) {
67
+ const filePath = String(input.file_path ?? input.path ?? '');
68
+ const startLine = input.start_line as number | undefined;
69
+ const endLine = input.end_line as number | undefined;
70
+ const rangeLabel =
71
+ startLine != null
72
+ ? endLine != null
73
+ ? ` (lines ${startLine}-${endLine})`
74
+ : ` (from line ${startLine})`
75
+ : '';
76
+
77
+ return (
78
+ <Box flexDirection="column">
79
+ <Text>
80
+ <Text dimColor>file: </Text>
81
+ <Text color="cyan">{filePath}</Text>
82
+ <Text dimColor>{rangeLabel}</Text>
83
+ </Text>
84
+ {result && !result.isError && (
85
+ <Text dimColor>{result.output.split('\n').length} lines read</Text>
86
+ )}
87
+ {result && result.isError && <Text color="red">{result.output}</Text>}
88
+ </Box>
89
+ );
90
+ }
91
+
92
+ /**
93
+ * Compute a minimal unified diff between old and new text with context lines.
94
+ */
95
+ function computeDiff(oldStr: string, newStr: string): React.ReactNode[] {
96
+ const oldLines = oldStr.split('\n');
97
+ const newLines = newStr.split('\n');
98
+ const elements: React.ReactNode[] = [];
99
+
100
+ // Find common prefix
101
+ let prefixLen = 0;
102
+ while (
103
+ prefixLen < oldLines.length &&
104
+ prefixLen < newLines.length &&
105
+ oldLines[prefixLen] === newLines[prefixLen]
106
+ ) {
107
+ prefixLen++;
108
+ }
109
+
110
+ // Find common suffix (from the end, but not overlapping with prefix)
111
+ let suffixLen = 0;
112
+ while (
113
+ suffixLen < oldLines.length - prefixLen &&
114
+ suffixLen < newLines.length - prefixLen &&
115
+ oldLines[oldLines.length - 1 - suffixLen] === newLines[newLines.length - 1 - suffixLen]
116
+ ) {
117
+ suffixLen++;
118
+ }
119
+
120
+ // Context: show up to 2 lines before the diff
121
+ const contextStart = Math.max(0, prefixLen - 2);
122
+ for (let i = contextStart; i < prefixLen; i++) {
123
+ elements.push(
124
+ <Text key={`ctx-pre-${i}`} dimColor>
125
+ {' '}
126
+ {oldLines[i]}
127
+ </Text>
128
+ );
129
+ }
130
+
131
+ // Removed lines (from old)
132
+ const oldDiffEnd = oldLines.length - suffixLen;
133
+ for (let i = prefixLen; i < oldDiffEnd; i++) {
134
+ elements.push(
135
+ <Text key={`rm-${i}`} color="red">
136
+ - {oldLines[i]}
137
+ </Text>
138
+ );
139
+ }
140
+
141
+ // Added lines (from new)
142
+ const newDiffEnd = newLines.length - suffixLen;
143
+ for (let i = prefixLen; i < newDiffEnd; i++) {
144
+ elements.push(
145
+ <Text key={`add-${i}`} color="green">
146
+ + {newLines[i]}
147
+ </Text>
148
+ );
149
+ }
150
+
151
+ // Context: show up to 2 lines after the diff
152
+ const contextEnd = Math.min(oldLines.length, oldDiffEnd + 2);
153
+ for (let i = oldDiffEnd; i < contextEnd; i++) {
154
+ elements.push(
155
+ <Text key={`ctx-post-${i}`} dimColor>
156
+ {' '}
157
+ {oldLines[i]}
158
+ </Text>
159
+ );
160
+ }
161
+
162
+ return elements;
163
+ }
164
+
165
+ function EditFileBody({
166
+ input,
167
+ result,
168
+ }: {
169
+ input: Record<string, unknown>;
170
+ result?: UIToolCall['result'];
171
+ }) {
172
+ const filePath = String(input.file_path ?? input.path ?? '');
173
+ const oldStr = String(input.old_string ?? '');
174
+ const newStr = String(input.new_string ?? '');
175
+ const replaceAll = input.replace_all === true;
176
+
177
+ return (
178
+ <Box flexDirection="column">
179
+ <Text>
180
+ <Text dimColor>file: </Text>
181
+ <Text color="cyan">{filePath}</Text>
182
+ {replaceAll && <Text dimColor> (replace all)</Text>}
183
+ </Text>
184
+ {oldStr && (
185
+ <Box flexDirection="column" marginTop={1}>
186
+ {computeDiff(oldStr, newStr)}
187
+ </Box>
188
+ )}
189
+ {result && result.isError && <Text color="red">{result.output}</Text>}
190
+ </Box>
191
+ );
192
+ }
193
+
194
+ function BashBody({
195
+ input,
196
+ result,
197
+ }: {
198
+ input: Record<string, unknown>;
199
+ result?: UIToolCall['result'];
200
+ }) {
201
+ const command = String(input.command ?? '');
202
+ const [expanded, setExpanded] = useState(false);
203
+
204
+ useInput((_input, key) => {
205
+ if (_input === 'e' && !key.ctrl && !key.meta) {
206
+ setExpanded(prev => !prev);
207
+ }
208
+ });
209
+
210
+ return (
211
+ <Box flexDirection="column">
212
+ <Text>
213
+ <Text dimColor>$ </Text>
214
+ <Text bold>{command}</Text>
215
+ </Text>
216
+ {result && (
217
+ <Box flexDirection="column" marginTop={1}>
218
+ {(() => {
219
+ const lines = result.output.split('\n');
220
+ const showLimit = expanded ? MAX_BASH_OUTPUT_LINES : COLLAPSED_LINES;
221
+ const truncated = lines.length > showLimit;
222
+ const visible = truncated ? lines.slice(0, showLimit) : lines;
223
+ return (
224
+ <>
225
+ {visible.map((line, i) => (
226
+ <Text
227
+ key={i}
228
+ color={result.isError ? 'red' : undefined}
229
+ dimColor={!result.isError}
230
+ >
231
+ {line}
232
+ </Text>
233
+ ))}
234
+ {truncated && (
235
+ <Text dimColor italic>
236
+ ... {lines.length - showLimit} more lines{' '}
237
+ {expanded ? "(press 'e' to collapse)" : "(press 'e' to expand)"}
238
+ </Text>
239
+ )}
240
+ {!truncated && lines.length > COLLAPSED_LINES && expanded && (
241
+ <Text dimColor italic>
242
+ (press 'e' to collapse)
243
+ </Text>
244
+ )}
245
+ </>
246
+ );
247
+ })()}
248
+ </Box>
249
+ )}
250
+ </Box>
251
+ );
252
+ }
253
+
254
+ function TerraformBody({
255
+ input,
256
+ result,
257
+ }: {
258
+ input: Record<string, unknown>;
259
+ result?: UIToolCall['result'];
260
+ }) {
261
+ const subcommand = String(input.command ?? input.subcommand ?? 'plan');
262
+
263
+ return (
264
+ <Box flexDirection="column">
265
+ <Text>
266
+ <Text dimColor>terraform </Text>
267
+ <Text bold>{subcommand}</Text>
268
+ </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
+ }
280
+ return (
281
+ <Text key={i} color={color} dimColor={!color}>
282
+ {line}
283
+ </Text>
284
+ );
285
+ })}
286
+ </Box>
287
+ )}
288
+ {result && result.isError && <Text color="red">{result.output}</Text>}
289
+ </Box>
290
+ );
291
+ }
292
+
293
+ function GenericBody({
294
+ input,
295
+ result,
296
+ }: {
297
+ input: Record<string, unknown>;
298
+ result?: UIToolCall['result'];
299
+ }) {
300
+ const entries = Object.entries(input).slice(0, 6);
301
+ const omitted = Object.keys(input).length - entries.length;
302
+ return (
303
+ <Box flexDirection="column">
304
+ {entries.map(([key, value]) => {
305
+ const str = String(value);
306
+ const truncated = str.length > 120;
307
+ return (
308
+ <Text key={key}>
309
+ <Text dimColor>{key}: </Text>
310
+ <Text>{truncated ? `${str.slice(0, 120)}...` : str}</Text>
311
+ </Text>
312
+ );
313
+ })}
314
+ {omitted > 0 && (
315
+ <Text dimColor italic>
316
+ ... {omitted} more fields
317
+ </Text>
318
+ )}
319
+ {result && result.isError && <Text color="red">{result.output}</Text>}
320
+ {result && !result.isError && (
321
+ <Text dimColor>
322
+ {result.output.length > 120 ? `${result.output.slice(0, 120)}...` : result.output}
323
+ </Text>
324
+ )}
325
+ </Box>
326
+ );
327
+ }
328
+
329
+ /* ---------------------------------------------------------------------------
330
+ * Single tool call box
331
+ * -------------------------------------------------------------------------*/
332
+
333
+ function ToolCallBox({ toolCall, expanded }: { toolCall: UIToolCall; expanded: boolean }) {
334
+ const durationLabel = toolCall.duration != null ? ` (${toolCall.duration}ms)` : '';
335
+
336
+ // Choose specialised body renderer based on tool name
337
+ const renderBody = () => {
338
+ if (!expanded && toolCall.status === 'completed') {
339
+ return (
340
+ <Text dimColor>
341
+ {toolCall.result
342
+ ? toolCall.result.isError
343
+ ? toolCall.result.output.slice(0, 80)
344
+ : 'completed'
345
+ : 'completed'}
346
+ </Text>
347
+ );
348
+ }
349
+
350
+ const name = toolCall.name.toLowerCase();
351
+ const props = { input: toolCall.input, result: toolCall.result };
352
+
353
+ if (name === 'read_file' || name === 'read') {
354
+ return <ReadFileBody {...props} />;
355
+ }
356
+ if (name === 'edit_file' || name === 'edit') {
357
+ return <EditFileBody {...props} />;
358
+ }
359
+ if (name === 'bash' || name === 'execute' || name === 'run_command') {
360
+ return <BashBody {...props} />;
361
+ }
362
+ if (name.startsWith('terraform') || name === 'tf_plan' || name === 'tf_apply') {
363
+ return <TerraformBody {...props} />;
364
+ }
365
+ return <GenericBody {...props} />;
366
+ };
367
+
368
+ return (
369
+ <Box
370
+ flexDirection="column"
371
+ borderStyle="single"
372
+ borderColor={toolCall.status === 'failed' ? 'red' : 'gray'}
373
+ paddingX={1}
374
+ marginBottom={1}
375
+ >
376
+ {/* Header */}
377
+ <Box>
378
+ <StatusBadge status={toolCall.status} />
379
+ <Text bold> {toolCall.name}</Text>
380
+ <Text dimColor>{durationLabel}</Text>
381
+ </Box>
382
+
383
+ {/* Body */}
384
+ <Box marginTop={1}>{renderBody()}</Box>
385
+ </Box>
386
+ );
387
+ }
388
+
389
+ /* ---------------------------------------------------------------------------
390
+ * Public component
391
+ * -------------------------------------------------------------------------*/
392
+
393
+ /**
394
+ * ToolCallDisplay renders a list of tool invocations. When `expanded` is
395
+ * false, completed calls are collapsed to a single summary line.
396
+ */
397
+ export function ToolCallDisplay({ toolCalls, expanded = true }: ToolCallDisplayProps) {
398
+ if (toolCalls.length === 0) {
399
+ return null;
400
+ }
401
+
402
+ return (
403
+ <Box flexDirection="column" paddingX={1}>
404
+ {toolCalls.map(tc => (
405
+ <ToolCallBox key={tc.id} toolCall={tc} expanded={expanded} />
406
+ ))}
407
+ </Box>
408
+ );
409
+ }