@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,402 @@
1
+ /**
2
+ * File System Commands
3
+ *
4
+ * CLI commands for file system operations via FS Tools Service
5
+ */
6
+
7
+ import { ToolsClient } from '../../clients';
8
+ import { ui } from '../../wizard/ui';
9
+
10
+ const toolsClient = new ToolsClient();
11
+
12
+ export interface FsCommandOptions {
13
+ path?: string;
14
+ recursive?: boolean;
15
+ pattern?: string;
16
+ maxResults?: number;
17
+ maxDepth?: number;
18
+ createDirs?: boolean;
19
+ }
20
+
21
+ interface FsEntry {
22
+ name: string;
23
+ type: string;
24
+ size?: number;
25
+ path?: string;
26
+ }
27
+
28
+ /**
29
+ * List directory contents
30
+ */
31
+ export async function fsListCommand(
32
+ dirPath: string,
33
+ options: FsCommandOptions = {}
34
+ ): Promise<void> {
35
+ ui.header('Files List');
36
+
37
+ ui.info(`Path: ${dirPath}`);
38
+ if (options.recursive) {
39
+ ui.info('Recursive: yes');
40
+ }
41
+
42
+ ui.startSpinner({ message: `Listing ${dirPath}...` });
43
+
44
+ try {
45
+ const result = await toolsClient.fs.list(dirPath, {
46
+ recursive: options.recursive,
47
+ pattern: options.pattern,
48
+ });
49
+
50
+ if (result.success) {
51
+ const data = result.data as { entries?: FsEntry[] } | undefined;
52
+ const entries = data?.entries || [];
53
+ ui.stopSpinnerSuccess(`Found ${entries.length} entries`);
54
+
55
+ if (entries.length > 0) {
56
+ for (const entry of entries) {
57
+ const icon = entry.type === 'directory' ? '📁' : '📄';
58
+ const size = entry.size ? ` (${formatSize(entry.size)})` : '';
59
+ ui.print(` ${icon} ${entry.name}${size}`);
60
+ }
61
+ } else {
62
+ ui.info('Directory is empty');
63
+ }
64
+ } else {
65
+ ui.stopSpinnerFail('Failed to list directory');
66
+ if (result.error) {
67
+ ui.error(result.error.message);
68
+ }
69
+ }
70
+ } catch (error: any) {
71
+ ui.stopSpinnerFail('Error listing directory');
72
+ ui.error(error.message);
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Search for files
78
+ */
79
+ export async function fsSearchCommand(
80
+ pattern: string,
81
+ searchPath: string = '.',
82
+ options: FsCommandOptions = {}
83
+ ): Promise<void> {
84
+ ui.header('Files Search');
85
+
86
+ ui.info(`Pattern: ${pattern}`);
87
+ ui.info(`Path: ${searchPath}`);
88
+
89
+ ui.startSpinner({ message: `Searching for ${pattern}...` });
90
+
91
+ try {
92
+ const result = await toolsClient.fs.search(pattern, {
93
+ path: searchPath,
94
+ maxResults: options.maxResults,
95
+ });
96
+
97
+ if (result.success) {
98
+ const data = result.data as { matches?: any[]; entries?: any[] } | undefined;
99
+ const matches = data?.matches || data?.entries || [];
100
+ ui.stopSpinnerSuccess(`Found ${matches.length} match(es)`);
101
+
102
+ if (matches.length > 0) {
103
+ for (const match of matches) {
104
+ const name = typeof match === 'string' ? match : match.path || match.name;
105
+ ui.print(` ${ui.color('•', 'green')} ${name}`);
106
+ }
107
+ }
108
+ } else {
109
+ ui.stopSpinnerFail('Search failed');
110
+ if (result.error) {
111
+ ui.error(result.error.message);
112
+ }
113
+ }
114
+ } catch (error: any) {
115
+ ui.stopSpinnerFail('Error searching files');
116
+ ui.error(error.message);
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Read file contents
122
+ */
123
+ export async function fsReadCommand(
124
+ filePath: string,
125
+ _options: FsCommandOptions = {}
126
+ ): Promise<void> {
127
+ ui.header('Files Read');
128
+
129
+ ui.info(`File: ${filePath}`);
130
+
131
+ ui.startSpinner({ message: `Reading ${filePath}...` });
132
+
133
+ try {
134
+ const result = await toolsClient.fs.read(filePath);
135
+
136
+ if (result.success) {
137
+ ui.stopSpinnerSuccess('File read successfully');
138
+ const data = result.data as { content?: string } | undefined;
139
+ if (data?.content !== undefined) {
140
+ console.log(data.content);
141
+ }
142
+ } else {
143
+ ui.stopSpinnerFail('Failed to read file');
144
+ if (result.error) {
145
+ ui.error(result.error.message);
146
+ }
147
+ }
148
+ } catch (error: any) {
149
+ ui.stopSpinnerFail('Error reading file');
150
+ ui.error(error.message);
151
+ }
152
+ }
153
+
154
+ interface TreeNode {
155
+ name: string;
156
+ type: string;
157
+ children?: TreeNode[];
158
+ }
159
+
160
+ /**
161
+ * Display directory tree using the dedicated tree endpoint
162
+ */
163
+ export async function fsTreeCommand(
164
+ dirPath: string,
165
+ options: FsCommandOptions = {}
166
+ ): Promise<void> {
167
+ ui.header('File Tree');
168
+
169
+ ui.info(`Path: ${dirPath}`);
170
+ if (options.maxDepth) {
171
+ ui.info(`Max depth: ${options.maxDepth}`);
172
+ }
173
+
174
+ ui.startSpinner({ message: `Building tree for ${dirPath}...` });
175
+
176
+ try {
177
+ const result = await toolsClient.fs.tree(dirPath, {
178
+ maxDepth: options.maxDepth,
179
+ includeFiles: true,
180
+ });
181
+
182
+ if (result.success) {
183
+ const data = result.data as { tree?: TreeNode[]; entries?: TreeNode[] } | undefined;
184
+ const tree = data?.tree || data?.entries || [];
185
+ ui.stopSpinnerSuccess(`Tree for ${dirPath}`);
186
+
187
+ const printTree = (nodes: TreeNode[], prefix = ''): void => {
188
+ for (let i = 0; i < nodes.length; i++) {
189
+ const node = nodes[i];
190
+ const isLast = i === nodes.length - 1;
191
+ const connector = isLast ? '└── ' : '├── ';
192
+ const icon = node.type === 'directory' ? '📁' : '📄';
193
+ ui.print(`${prefix}${connector}${icon} ${node.name}`);
194
+ if (node.children && node.children.length > 0) {
195
+ const childPrefix = prefix + (isLast ? ' ' : '│ ');
196
+ printTree(node.children, childPrefix);
197
+ }
198
+ }
199
+ };
200
+
201
+ if (tree.length > 0) {
202
+ printTree(tree);
203
+ } else {
204
+ ui.info('Directory is empty');
205
+ }
206
+ } else {
207
+ ui.stopSpinnerFail('Failed to build tree');
208
+ if (result.error) {
209
+ ui.error(result.error.message);
210
+ }
211
+ }
212
+ } catch (error: any) {
213
+ ui.stopSpinnerFail('Error building tree');
214
+ ui.error(error.message);
215
+ }
216
+ }
217
+
218
+ /**
219
+ * Write content to a file
220
+ */
221
+ export async function fsWriteCommand(
222
+ filePath: string,
223
+ content: string,
224
+ options: FsCommandOptions = {}
225
+ ): Promise<void> {
226
+ ui.header('Files Write');
227
+
228
+ ui.info(`File: ${filePath}`);
229
+
230
+ ui.startSpinner({ message: `Writing to ${filePath}...` });
231
+
232
+ try {
233
+ const result = await toolsClient.fs.write(filePath, content, {
234
+ createDirs: options.createDirs,
235
+ });
236
+
237
+ if (result.success) {
238
+ ui.stopSpinnerSuccess(`File written successfully: ${filePath}`);
239
+ } else {
240
+ ui.stopSpinnerFail('Failed to write file');
241
+ if (result.error) {
242
+ ui.error(result.error.message);
243
+ }
244
+ }
245
+ } catch (error: any) {
246
+ ui.stopSpinnerFail('Error writing file');
247
+ ui.error(error.message);
248
+ }
249
+ }
250
+
251
+ /**
252
+ * Show diff between two files
253
+ */
254
+ export async function fsDiffCommand(file1: string, file2: string): Promise<void> {
255
+ ui.header('Files Diff');
256
+
257
+ ui.info(`File 1: ${file1}`);
258
+ ui.info(`File 2: ${file2}`);
259
+
260
+ ui.startSpinner({ message: 'Computing diff...' });
261
+
262
+ try {
263
+ // Read both files and compare via the FS tools service diff endpoint
264
+ const result = await toolsClient.fs.read(file1);
265
+ const result2 = await toolsClient.fs.read(file2);
266
+
267
+ if (!result.success) {
268
+ ui.stopSpinnerFail(`Failed to read file: ${file1}`);
269
+ if (result.error) {
270
+ ui.error(result.error.message);
271
+ }
272
+ return;
273
+ }
274
+
275
+ if (!result2.success) {
276
+ ui.stopSpinnerFail(`Failed to read file: ${file2}`);
277
+ if (result2.error) {
278
+ ui.error(result2.error.message);
279
+ }
280
+ return;
281
+ }
282
+
283
+ const data1 = result.data as { content?: string } | undefined;
284
+ const data2 = result2.data as { content?: string } | undefined;
285
+ const content1 = data1?.content || '';
286
+ const content2 = data2?.content || '';
287
+
288
+ if (content1 === content2) {
289
+ ui.stopSpinnerSuccess('Files are identical');
290
+ } else {
291
+ ui.stopSpinnerSuccess('Diff computed');
292
+
293
+ // Simple line-by-line diff display
294
+ const lines1 = content1.split('\n');
295
+ const lines2 = content2.split('\n');
296
+ const maxLines = Math.max(lines1.length, lines2.length);
297
+
298
+ const diffLines: string[] = [];
299
+ for (let i = 0; i < maxLines; i++) {
300
+ const l1 = lines1[i];
301
+ const l2 = lines2[i];
302
+ if (l1 === undefined) {
303
+ diffLines.push(ui.color(`+ ${l2}`, 'green'));
304
+ } else if (l2 === undefined) {
305
+ diffLines.push(ui.color(`- ${l1}`, 'red'));
306
+ } else if (l1 !== l2) {
307
+ diffLines.push(ui.color(`- ${l1}`, 'red'));
308
+ diffLines.push(ui.color(`+ ${l2}`, 'green'));
309
+ }
310
+ }
311
+
312
+ if (diffLines.length > 0) {
313
+ ui.box({ title: 'Diff', content: diffLines.join('\n') });
314
+ }
315
+ }
316
+ } catch (error: any) {
317
+ ui.stopSpinnerFail('Error computing diff');
318
+ ui.error(error.message);
319
+ }
320
+ }
321
+
322
+ /**
323
+ * Main fs command router
324
+ */
325
+ export async function fsCommand(subcommand: string, args: string[]): Promise<void> {
326
+ const options: FsCommandOptions = {};
327
+ const positionalArgs: string[] = [];
328
+
329
+ for (let i = 0; i < args.length; i++) {
330
+ const arg = args[i];
331
+ if (arg === '-r' || arg === '--recursive') {
332
+ options.recursive = true;
333
+ } else if (arg === '-p' || arg === '--pattern') {
334
+ options.pattern = args[++i];
335
+ } else if (arg === '-n' || arg === '--max-results') {
336
+ options.maxResults = parseInt(args[++i], 10);
337
+ } else if (arg === '--depth') {
338
+ options.maxDepth = parseInt(args[++i], 10);
339
+ } else if (arg === '--create-dirs') {
340
+ options.createDirs = true;
341
+ } else if (!arg.startsWith('-')) {
342
+ positionalArgs.push(arg);
343
+ }
344
+ }
345
+
346
+ switch (subcommand) {
347
+ case 'list':
348
+ case 'ls':
349
+ await fsListCommand(positionalArgs[0] || '.', options);
350
+ break;
351
+ case 'tree':
352
+ await fsTreeCommand(positionalArgs[0] || '.', options);
353
+ break;
354
+ case 'search':
355
+ case 'find':
356
+ if (positionalArgs.length < 1) {
357
+ ui.error('Usage: nimbus fs search <pattern> [path]');
358
+ return;
359
+ }
360
+ await fsSearchCommand(positionalArgs[0], positionalArgs[1] || '.', options);
361
+ break;
362
+ case 'read':
363
+ case 'cat':
364
+ if (positionalArgs.length < 1) {
365
+ ui.error('Usage: nimbus fs read <file>');
366
+ return;
367
+ }
368
+ await fsReadCommand(positionalArgs[0], options);
369
+ break;
370
+ case 'write':
371
+ if (positionalArgs.length < 2) {
372
+ ui.error('Usage: nimbus fs write <path> <content>');
373
+ return;
374
+ }
375
+ await fsWriteCommand(positionalArgs[0], positionalArgs.slice(1).join(' '), options);
376
+ break;
377
+ case 'diff':
378
+ if (positionalArgs.length < 2) {
379
+ ui.error('Usage: nimbus fs diff <file1> <file2>');
380
+ return;
381
+ }
382
+ await fsDiffCommand(positionalArgs[0], positionalArgs[1]);
383
+ break;
384
+ default:
385
+ ui.error(`Unknown fs subcommand: ${subcommand}`);
386
+ ui.info('Available commands: list, tree, search, read, write, diff');
387
+ }
388
+ }
389
+
390
+ /**
391
+ * Format file size in human-readable format
392
+ */
393
+ function formatSize(bytes: number): string {
394
+ const units = ['B', 'KB', 'MB', 'GB'];
395
+ let size = bytes;
396
+ let unitIndex = 0;
397
+ while (size >= 1024 && unitIndex < units.length - 1) {
398
+ size /= 1024;
399
+ unitIndex++;
400
+ }
401
+ return `${size.toFixed(unitIndex > 0 ? 1 : 0)} ${units[unitIndex]}`;
402
+ }