@abacus-ai/cli 2.0.0-canary.1 → 2.0.0-canary.3

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 (197) hide show
  1. package/dist/index.mjs +450 -422
  2. package/package.json +4 -1
  3. package/.oxlintrc.json +0 -8
  4. package/resources/abacus.ico +0 -0
  5. package/resources/entitlements.plist +0 -9
  6. package/src/__e2e__/README.md +0 -196
  7. package/src/__e2e__/agent-interactions.e2e.test.tsx +0 -61
  8. package/src/__e2e__/cli-commands.e2e.test.tsx +0 -77
  9. package/src/__e2e__/conversation-throttle.e2e.test.ts +0 -453
  10. package/src/__e2e__/conversation.e2e.test.tsx +0 -56
  11. package/src/__e2e__/diff-preview.e2e.test.tsx +0 -3399
  12. package/src/__e2e__/file-creation.e2e.test.tsx +0 -149
  13. package/src/__e2e__/helpers/test-helpers.ts +0 -449
  14. package/src/__e2e__/keyboard-navigation.e2e.test.tsx +0 -34
  15. package/src/__e2e__/llm-models.e2e.test.ts +0 -402
  16. package/src/__e2e__/mcp/mcp-callback-flow.e2e.test.tsx +0 -71
  17. package/src/__e2e__/mcp/mcp-full-app-ui.e2e.test.tsx +0 -167
  18. package/src/__e2e__/mcp/mcp-ui-rendering.e2e.test.tsx +0 -185
  19. package/src/__e2e__/repl.e2e.test.tsx +0 -78
  20. package/src/__e2e__/shell-compatibility.e2e.test.tsx +0 -76
  21. package/src/__e2e__/theme-mcp.e2e.test.tsx +0 -98
  22. package/src/__e2e__/tool-permissions.e2e.test.tsx +0 -66
  23. package/src/args.ts +0 -22
  24. package/src/components/__tests__/react-compiler.test.tsx +0 -78
  25. package/src/components/__tests__/status-indicator.test.tsx +0 -403
  26. package/src/components/composer/__tests__/bash-runner.test.tsx +0 -263
  27. package/src/components/composer/agent-mode-indicator.tsx +0 -63
  28. package/src/components/composer/bash-runner.tsx +0 -54
  29. package/src/components/composer/commands/default-commands.tsx +0 -615
  30. package/src/components/composer/commands/handler.tsx +0 -59
  31. package/src/components/composer/commands/picker.tsx +0 -273
  32. package/src/components/composer/commands/registry.ts +0 -233
  33. package/src/components/composer/commands/types.ts +0 -33
  34. package/src/components/composer/context.tsx +0 -88
  35. package/src/components/composer/file-mention-picker.tsx +0 -83
  36. package/src/components/composer/help.tsx +0 -44
  37. package/src/components/composer/index.tsx +0 -1007
  38. package/src/components/composer/mentions.ts +0 -57
  39. package/src/components/composer/message-queue.tsx +0 -70
  40. package/src/components/composer/mode-panel.tsx +0 -35
  41. package/src/components/composer/modes/__tests__/bash-handler.test.tsx +0 -755
  42. package/src/components/composer/modes/__tests__/bash-renderer.test.tsx +0 -1108
  43. package/src/components/composer/modes/bash-handler.tsx +0 -132
  44. package/src/components/composer/modes/bash-renderer.tsx +0 -175
  45. package/src/components/composer/modes/default-handlers.tsx +0 -33
  46. package/src/components/composer/modes/index.ts +0 -41
  47. package/src/components/composer/modes/types.ts +0 -21
  48. package/src/components/composer/persistent-shell.ts +0 -283
  49. package/src/components/composer/process.ts +0 -65
  50. package/src/components/composer/types.ts +0 -9
  51. package/src/components/composer/use-mention-search.ts +0 -68
  52. package/src/components/error-boundry.tsx +0 -60
  53. package/src/components/exit-message.tsx +0 -29
  54. package/src/components/expanded-view.tsx +0 -74
  55. package/src/components/file-completion.tsx +0 -127
  56. package/src/components/header.tsx +0 -47
  57. package/src/components/logo.tsx +0 -37
  58. package/src/components/segments.tsx +0 -356
  59. package/src/components/status-indicator.tsx +0 -306
  60. package/src/components/tool-group-summary.tsx +0 -263
  61. package/src/components/tool-permissions/ask-user-question-permission-ui.tsx +0 -319
  62. package/src/components/tool-permissions/diff-preview.tsx +0 -359
  63. package/src/components/tool-permissions/index.ts +0 -5
  64. package/src/components/tool-permissions/permission-options.tsx +0 -401
  65. package/src/components/tool-permissions/permission-preview-header.tsx +0 -57
  66. package/src/components/tool-permissions/tool-permission-ui.tsx +0 -420
  67. package/src/components/tools/agent/ask-user-question.tsx +0 -107
  68. package/src/components/tools/agent/enter-plan-mode.tsx +0 -55
  69. package/src/components/tools/agent/exit-plan-mode.tsx +0 -83
  70. package/src/components/tools/agent/handoff-to-main.tsx +0 -27
  71. package/src/components/tools/agent/subagent.tsx +0 -37
  72. package/src/components/tools/agent/todo-write.tsx +0 -104
  73. package/src/components/tools/browser/close-tab.tsx +0 -58
  74. package/src/components/tools/browser/computer.tsx +0 -70
  75. package/src/components/tools/browser/get-interactive-elements.tsx +0 -54
  76. package/src/components/tools/browser/get-tab-content.tsx +0 -51
  77. package/src/components/tools/browser/navigate-to.tsx +0 -59
  78. package/src/components/tools/browser/new-tab.tsx +0 -60
  79. package/src/components/tools/browser/perform-action.tsx +0 -63
  80. package/src/components/tools/browser/refresh-tab.tsx +0 -43
  81. package/src/components/tools/browser/switch-tab.tsx +0 -58
  82. package/src/components/tools/filesystem/delete-file.tsx +0 -104
  83. package/src/components/tools/filesystem/edit.tsx +0 -220
  84. package/src/components/tools/filesystem/list-dir.tsx +0 -78
  85. package/src/components/tools/filesystem/read-file.tsx +0 -180
  86. package/src/components/tools/filesystem/upload-image.tsx +0 -76
  87. package/src/components/tools/ide/ide-diagnostics.tsx +0 -62
  88. package/src/components/tools/index.ts +0 -91
  89. package/src/components/tools/mcp/mcp-tool.tsx +0 -158
  90. package/src/components/tools/search/fetch-url.tsx +0 -73
  91. package/src/components/tools/search/file-search.tsx +0 -78
  92. package/src/components/tools/search/grep.tsx +0 -90
  93. package/src/components/tools/search/semantic-search.tsx +0 -66
  94. package/src/components/tools/search/web-search.tsx +0 -71
  95. package/src/components/tools/shared/index.tsx +0 -48
  96. package/src/components/tools/shared/zod-coercion.ts +0 -35
  97. package/src/components/tools/terminal/bash-tool-output.tsx +0 -188
  98. package/src/components/tools/terminal/get-terminal-output.tsx +0 -91
  99. package/src/components/tools/terminal/run-in-terminal.tsx +0 -131
  100. package/src/components/tools/types.ts +0 -16
  101. package/src/components/tools.tsx +0 -68
  102. package/src/components/ui/__tests__/divider.test.tsx +0 -61
  103. package/src/components/ui/__tests__/gradient.test.tsx +0 -125
  104. package/src/components/ui/__tests__/input.test.tsx +0 -166
  105. package/src/components/ui/__tests__/select.test.tsx +0 -273
  106. package/src/components/ui/__tests__/shimmer.test.tsx +0 -99
  107. package/src/components/ui/blinking-indicator.tsx +0 -27
  108. package/src/components/ui/divider.tsx +0 -162
  109. package/src/components/ui/gradient.tsx +0 -56
  110. package/src/components/ui/input.tsx +0 -228
  111. package/src/components/ui/select.tsx +0 -151
  112. package/src/components/ui/shimmer.tsx +0 -76
  113. package/src/context/agent-mode.tsx +0 -95
  114. package/src/context/extension-file.tsx +0 -136
  115. package/src/context/network-activity.tsx +0 -45
  116. package/src/context/notification.tsx +0 -62
  117. package/src/context/shell-size.tsx +0 -49
  118. package/src/context/shell-title.tsx +0 -38
  119. package/src/entrypoints/print-mode.ts +0 -312
  120. package/src/entrypoints/repl.tsx +0 -389
  121. package/src/hooks/use-agent.ts +0 -15
  122. package/src/hooks/use-api-client.ts +0 -1
  123. package/src/hooks/use-available-height.ts +0 -8
  124. package/src/hooks/use-cleanup.ts +0 -29
  125. package/src/hooks/use-interrupt-manager.ts +0 -242
  126. package/src/hooks/use-models.ts +0 -22
  127. package/src/index.ts +0 -217
  128. package/src/lib/__tests__/ansi.test.ts +0 -255
  129. package/src/lib/__tests__/cli.test.ts +0 -122
  130. package/src/lib/__tests__/commands.test.ts +0 -325
  131. package/src/lib/__tests__/constants.test.ts +0 -15
  132. package/src/lib/__tests__/focusables.test.ts +0 -25
  133. package/src/lib/__tests__/fs.test.ts +0 -231
  134. package/src/lib/__tests__/markdown.test.tsx +0 -348
  135. package/src/lib/__tests__/mcpCommandHandler.test.ts +0 -173
  136. package/src/lib/__tests__/mcpManagement.test.ts +0 -38
  137. package/src/lib/__tests__/path-paste.test.ts +0 -144
  138. package/src/lib/__tests__/path.test.ts +0 -300
  139. package/src/lib/__tests__/queries.test.ts +0 -39
  140. package/src/lib/__tests__/standaloneMcpService.test.ts +0 -71
  141. package/src/lib/__tests__/text-buffer.test.ts +0 -328
  142. package/src/lib/__tests__/text-utils.test.ts +0 -32
  143. package/src/lib/__tests__/timing.test.ts +0 -78
  144. package/src/lib/__tests__/utils.test.ts +0 -238
  145. package/src/lib/__tests__/vim-buffer-actions.test.ts +0 -154
  146. package/src/lib/ansi.ts +0 -150
  147. package/src/lib/cli-push-server.ts +0 -112
  148. package/src/lib/cli.ts +0 -44
  149. package/src/lib/clipboard.ts +0 -226
  150. package/src/lib/command-utils.ts +0 -93
  151. package/src/lib/commands.ts +0 -270
  152. package/src/lib/constants.ts +0 -3
  153. package/src/lib/extension-connection.ts +0 -181
  154. package/src/lib/focusables.ts +0 -7
  155. package/src/lib/fs.ts +0 -533
  156. package/src/lib/markdown/code-block.tsx +0 -63
  157. package/src/lib/markdown/index.ts +0 -4
  158. package/src/lib/markdown/link.tsx +0 -19
  159. package/src/lib/markdown/markdown.tsx +0 -372
  160. package/src/lib/markdown/types.ts +0 -15
  161. package/src/lib/mcpCommandHandler.ts +0 -121
  162. package/src/lib/mcpManagement.ts +0 -44
  163. package/src/lib/path-paste.ts +0 -185
  164. package/src/lib/path.ts +0 -179
  165. package/src/lib/queries.ts +0 -15
  166. package/src/lib/standaloneMcpService.ts +0 -688
  167. package/src/lib/status-utils.ts +0 -237
  168. package/src/lib/test-utils.tsx +0 -72
  169. package/src/lib/text-buffer.ts +0 -2415
  170. package/src/lib/text-utils.ts +0 -272
  171. package/src/lib/timing.ts +0 -63
  172. package/src/lib/types.ts +0 -295
  173. package/src/lib/utils.ts +0 -182
  174. package/src/lib/vim-buffer-actions.ts +0 -732
  175. package/src/providers/agent.tsx +0 -1063
  176. package/src/providers/api-client.tsx +0 -43
  177. package/src/services/logger.ts +0 -85
  178. package/src/terminal/detection.ts +0 -187
  179. package/src/terminal/exit.ts +0 -279
  180. package/src/terminal/notification.ts +0 -83
  181. package/src/terminal/progress.ts +0 -201
  182. package/src/terminal/setup.ts +0 -797
  183. package/src/terminal/types.ts +0 -51
  184. package/src/theme/context.tsx +0 -57
  185. package/src/theme/index.ts +0 -4
  186. package/src/theme/themed.tsx +0 -35
  187. package/src/theme/themes.json +0 -546
  188. package/src/theme/types.ts +0 -110
  189. package/src/tools/types.ts +0 -59
  190. package/src/tools/utils/__tests__/zod-coercion.test.ts +0 -33
  191. package/src/tools/utils/tool-ui-components.tsx +0 -649
  192. package/src/tools/utils/zod-coercion.ts +0 -35
  193. package/tsconfig.json +0 -16
  194. package/tsconfig.node.json +0 -29
  195. package/tsconfig.test.json +0 -27
  196. package/tsdown.config.ts +0 -17
  197. package/vitest.config.ts +0 -76
@@ -1,688 +0,0 @@
1
- import { product } from "@codellm/product";
2
- import fs, { promises as fsPromises } from "node:fs";
3
- import os from "node:os";
4
- import path from "node:path";
5
-
6
- import type { IMcpServerInfo, IMcpManagementService, IMcpServerConfig } from "./types.js";
7
-
8
- import { helpText } from "./mcpCommandHandler.js";
9
-
10
- interface McpConfigFile {
11
- inputs?: unknown[];
12
- servers?: Record<string, IMcpServerConfig>;
13
- }
14
-
15
- interface CursorGlobalConfig {
16
- mcpServers?: Record<
17
- string,
18
- {
19
- command?: string;
20
- args?: string[];
21
- env?: Record<string, string>;
22
- url?: string;
23
- }
24
- >;
25
- }
26
-
27
- interface VSCodeSettings {
28
- mcp?: McpConfigFile;
29
- [key: string]: any;
30
- }
31
-
32
- interface ConfigSource {
33
- path: string;
34
- type:
35
- | "cursor-global"
36
- | "cursor-workspace"
37
- | "vscode-user"
38
- | "vscode-workspace"
39
- | "claude-desktop"
40
- | "windsurf"
41
- | "abacusai-user";
42
- label: string;
43
- exists: boolean;
44
- }
45
-
46
- /**
47
- * Standalone MCP service that reads configurations directly from files
48
- * without requiring any workbench dependencies.
49
- */
50
- export class StandaloneMcpService implements IMcpManagementService {
51
- constructor() {}
52
-
53
- async listServers(): Promise<IMcpServerInfo[]> {
54
- const configSources = await this.findConfigSources();
55
- const servers: IMcpServerInfo[] = [];
56
-
57
- for (const source of configSources) {
58
- if (!source.exists) {
59
- continue;
60
- }
61
-
62
- try {
63
- const sourceServers = await this.loadServersFromSource(source);
64
- servers.push(...sourceServers);
65
- } catch {
66
- // Failed to load from this source, skip
67
- }
68
- }
69
-
70
- return servers;
71
- }
72
-
73
- async addServer(name: string, config: IMcpServerConfig): Promise<void> {
74
- // Validate the server configuration before saving
75
- this.validateServerConfig(config);
76
- // Prefer product-specific User settings over other formats
77
- const configSources = await this.findConfigSources();
78
-
79
- // Priority order: Product User > VSCode User > existing files > Cursor global
80
- const abacusaiUser = configSources.find((s) => s.type === "abacusai-user" && s.exists);
81
- const vscodeUser = configSources.find((s) => s.type === "vscode-user" && s.exists);
82
- const existingFiles = configSources.filter((s) => s.exists);
83
- const cursorGlobal = configSources.find((s) => s.type === "cursor-global");
84
- const vscodeUserNonExisting = configSources.find((s) => s.type === "vscode-user");
85
-
86
- // Try existing files first, then fall back to creating new ones
87
- const targetSource =
88
- abacusaiUser || vscodeUser || existingFiles[0] || vscodeUserNonExisting || cursorGlobal;
89
- if (!targetSource) {
90
- throw new Error("No suitable configuration location found.");
91
- }
92
-
93
- await this.addServerToSource(targetSource, name, config);
94
- }
95
-
96
- async removeServer(name: string): Promise<void> {
97
- const configSources = await this.findConfigSources();
98
- let removed = false;
99
-
100
- for (const source of configSources) {
101
- if (!source.exists) {
102
- continue;
103
- }
104
-
105
- try {
106
- const wasRemoved = await this.removeServerFromSource(source.path, name);
107
- if (wasRemoved) {
108
- removed = true;
109
- }
110
- } catch {
111
- // Failed to remove from this source, continue
112
- }
113
- }
114
-
115
- if (!removed) {
116
- throw new Error(`Server '${name}' not found in any configuration file`);
117
- }
118
- }
119
-
120
- async getServer(name: string): Promise<IMcpServerInfo | undefined> {
121
- const servers = await this.listServers();
122
- return servers.find((server) => server.name === name || server.id === name);
123
- }
124
-
125
- async addServerFromJson(name: string, jsonConfig: string): Promise<void> {
126
- let config: IMcpServerConfig;
127
-
128
- try {
129
- config = this.parseJsonWithFallback(jsonConfig) as IMcpServerConfig;
130
- } catch (error) {
131
- throw new Error(
132
- `Invalid JSON syntax: ${error instanceof Error ? error.message : String(error)}`,
133
- );
134
- }
135
-
136
- try {
137
- this.validateServerConfig(config);
138
- } catch (error) {
139
- throw new Error(
140
- `Invalid server configuration: ${error instanceof Error ? error.message : String(error)}`,
141
- );
142
- }
143
-
144
- await this.addServer(name, config);
145
- }
146
-
147
- async addServerFromUrl(name: string, url: string, args?: string[]): Promise<void> {
148
- let config: IMcpServerConfig;
149
-
150
- // Check if it's an HTTP URL
151
- if (url.startsWith("http://") || url.startsWith("https://")) {
152
- config = {
153
- url,
154
- };
155
- } else {
156
- // Treat as command
157
- config = {
158
- command: url,
159
- args: args || [],
160
- };
161
- }
162
-
163
- await this.addServer(name, config);
164
- }
165
-
166
- formatServerInfo(server: IMcpServerInfo): string {
167
- const lines = [
168
- `Name: ${server.name}`,
169
- `ID: ${server.id}`,
170
- `Source: ${server.description || "Unknown"}`,
171
- `Status: ${server.status || "unknown"}`,
172
- ];
173
-
174
- if ("url" in server.config) {
175
- lines.push(`Type: HTTP`);
176
- lines.push(`URL: ${server.config.url}`);
177
- if (server.config.headers && Object.keys(server.config.headers).length > 0) {
178
- lines.push(`Headers: ${JSON.stringify(server.config.headers, null, 2)}`);
179
- }
180
- } else if ("command" in server.config) {
181
- lines.push(`Type: Command`);
182
- lines.push(`Command: ${server.config.command}`);
183
- if (server.config.args && server.config.args.length > 0) {
184
- lines.push(`Arguments: ${server.config.args.join(" ")}`);
185
- }
186
- if (server.config.env && Object.keys(server.config.env).length > 0) {
187
- lines.push(`Environment: ${JSON.stringify(server.config.env, null, 2)}`);
188
- }
189
- if (server.config.cwd) {
190
- lines.push(`Working Directory: ${server.config.cwd}`);
191
- }
192
- }
193
-
194
- return lines.join("\n");
195
- }
196
-
197
- formatServerList(servers: IMcpServerInfo[]): string {
198
- const lines: string[] = [];
199
-
200
- if (servers.length === 0) {
201
- lines.push("No MCP servers found.");
202
- lines.push("");
203
- lines.push("Use the following commands to manage MCP servers:");
204
- lines.push("");
205
- // Add the MCP help content
206
- lines.push(helpText);
207
- } else {
208
- lines.push("📡 MCP Servers:");
209
- lines.push("");
210
-
211
- // Group servers by source
212
- const serversBySource: Record<string, IMcpServerInfo[]> = {};
213
- for (const server of servers) {
214
- const source = this.getServerSourceLabel(server);
215
- if (!serversBySource[source]) {
216
- serversBySource[source] = [];
217
- }
218
- serversBySource[source].push(server);
219
- }
220
-
221
- for (const [source, sourceServers] of Object.entries(serversBySource)) {
222
- lines.push(` ${source}:`);
223
- for (const server of sourceServers) {
224
- const typeLabel = "url" in server.config ? "🌐" : "💻";
225
- lines.push(` ${typeLabel} ${server.name}`);
226
- }
227
- lines.push("");
228
- }
229
-
230
- lines.push("📝 Example - Add GitHub MCP Server:");
231
- lines.push(
232
- ' /mcp add-json github \'{"command":"npx","args":["-y","@modelcontextprotocol/server-github"],"env":{"GITHUB_PERSONAL_ACCESS_TOKEN":"your_token"}}\'',
233
- );
234
- lines.push("");
235
- }
236
-
237
- return lines.join("\n");
238
- }
239
-
240
- async findConfigSources(): Promise<ConfigSource[]> {
241
- const sources: ConfigSource[] = [];
242
-
243
- // Claude Desktop
244
- const claudePath =
245
- process.platform === "win32"
246
- ? path.join(os.homedir(), "AppData", "Roaming", "Claude", "claude_desktop_config.json")
247
- : process.platform === "darwin"
248
- ? path.join(
249
- os.homedir(),
250
- "Library",
251
- "Application Support",
252
- "Claude",
253
- "claude_desktop_config.json",
254
- )
255
- : path.join(os.homedir(), ".config", "Claude", "claude_desktop_config.json");
256
- sources.push({
257
- path: claudePath,
258
- type: "claude-desktop",
259
- label: "Claude Desktop",
260
- exists: fs.existsSync(claudePath),
261
- });
262
-
263
- // Windsurf
264
- const windsurfPath = path.join(os.homedir(), ".codeium", "windsurf", "mcp_config.json");
265
- sources.push({
266
- path: windsurfPath,
267
- type: "windsurf",
268
- label: "Windsurf",
269
- exists: fs.existsSync(windsurfPath),
270
- });
271
-
272
- // Cursor Global: ~/.cursor/mcp.json
273
- const cursorGlobal = path.join(os.homedir(), ".cursor", "mcp.json");
274
- sources.push({
275
- path: cursorGlobal,
276
- type: "cursor-global",
277
- label: "Cursor (Global)",
278
- exists: fs.existsSync(cursorGlobal),
279
- });
280
-
281
- // Cursor Workspace: .cursor/mcp.json
282
- const cursorWorkspace = path.join(process.cwd(), ".cursor", "mcp.json");
283
- sources.push({
284
- path: cursorWorkspace,
285
- type: "cursor-workspace",
286
- label: "Cursor (Workspace)",
287
- exists: fs.existsSync(cursorWorkspace),
288
- });
289
-
290
- const possibleProductNames = [product.fullName, "AbacusAI", "CodeLLM"];
291
- for (const productName of possibleProductNames) {
292
- const userDataDir = this.getUserDataDir(productName);
293
- if (userDataDir) {
294
- const mcpJsonPath = path.join(userDataDir, "User", "mcp.json");
295
- if (fs.existsSync(mcpJsonPath)) {
296
- sources.push({
297
- path: mcpJsonPath,
298
- type: "abacusai-user",
299
- label: `${productName} (User)`,
300
- exists: true,
301
- });
302
- }
303
- }
304
- }
305
-
306
- // Standard VSCode User Settings (fallback)
307
- const vscodeUserDir =
308
- process.platform === "win32"
309
- ? path.join(os.homedir(), "AppData", "Roaming", "Code", "User")
310
- : process.platform === "darwin"
311
- ? path.join(os.homedir(), "Library", "Application Support", "Code", "User")
312
- : path.join(os.homedir(), ".config", "Code", "User");
313
- const vscodeMcpJson = path.join(vscodeUserDir, "mcp.json");
314
- sources.push({
315
- path: vscodeMcpJson,
316
- type: "vscode-user",
317
- label: "VSCode (User)",
318
- exists: fs.existsSync(vscodeMcpJson),
319
- });
320
-
321
- return sources;
322
- }
323
-
324
- private getUserDataDir(productName: string): string | null {
325
- try {
326
- switch (process.platform) {
327
- case "win32": {
328
- const appData = process.env["APPDATA"];
329
- if (!appData) {
330
- return null;
331
- }
332
- return path.join(appData, productName);
333
- }
334
-
335
- case "darwin": {
336
- return path.join(os.homedir(), "Library", "Application Support", productName);
337
- }
338
-
339
- case "linux": {
340
- const configHome = process.env["XDG_CONFIG_HOME"] || path.join(os.homedir(), ".config");
341
- return path.join(configHome, productName);
342
- }
343
-
344
- default:
345
- return null;
346
- }
347
- } catch {
348
- return null;
349
- }
350
- }
351
-
352
- private async loadServersFromSource(source: ConfigSource): Promise<IMcpServerInfo[]> {
353
- const content = await fsPromises.readFile(source.path, "utf8");
354
- const servers: IMcpServerInfo[] = [];
355
-
356
- // Parse JSON with better error handling and trailing comma support
357
- let parsedContent: any;
358
- try {
359
- parsedContent = this.parseJsonWithFallback(content);
360
- } catch (error) {
361
- throw new Error(
362
- `JSON parsing failed: ${error instanceof Error ? error.message : String(error)}`,
363
- );
364
- }
365
-
366
- if (source.type.startsWith("vscode") || source.type === "abacusai-user") {
367
- let serversConfig: Record<string, IMcpServerConfig> | undefined;
368
-
369
- if (parsedContent.servers && !parsedContent.mcp) {
370
- serversConfig = parsedContent.servers;
371
- } else if (parsedContent.mcp?.servers) {
372
- serversConfig = parsedContent.mcp.servers;
373
- }
374
-
375
- if (serversConfig) {
376
- for (const [name, serverConfig] of Object.entries(serversConfig)) {
377
- servers.push({
378
- id: `${source.type}.${name}`,
379
- name,
380
- config: serverConfig,
381
- status: "stopped",
382
- description: `${source.label} - ${this.getServerTypeDescription(serverConfig)}`,
383
- });
384
- }
385
- }
386
- } else if (
387
- ["cursor-global", "cursor-workspace", "claude-desktop", "windsurf"].includes(source.type)
388
- ) {
389
- const cursorConfig: CursorGlobalConfig = parsedContent;
390
-
391
- if (cursorConfig.mcpServers) {
392
- for (const [name, serverConfig] of Object.entries(cursorConfig.mcpServers)) {
393
- // Convert format to our internal format
394
- const config: IMcpServerConfig = serverConfig.url
395
- ? {
396
- type: "http",
397
- url: serverConfig.url,
398
- headers: {},
399
- }
400
- : {
401
- type: "stdio",
402
- command: serverConfig.command || "",
403
- args: serverConfig.args || [],
404
- env: serverConfig.env || {},
405
- };
406
-
407
- servers.push({
408
- id: `${source.type}.${name}`,
409
- name,
410
- config,
411
- status: "stopped",
412
- description: `${source.label} - ${this.getServerTypeDescription(config)}`,
413
- });
414
- }
415
- }
416
- }
417
-
418
- return servers;
419
- }
420
-
421
- private parseJsonWithFallback(content: string): any {
422
- // First try standard JSON parsing
423
- try {
424
- return JSON.parse(content);
425
- } catch (error) {
426
- // If that fails, try to fix common JSON issues like trailing commas
427
- try {
428
- // Remove trailing commas before closing braces/brackets
429
- const fixedContent = content
430
- .replace(/,(\s*[}\]])/g, "$1") // Remove trailing commas
431
- .replace(/([}\]]),(\s*[}\]])/g, "$1$2"); // Remove commas before closing braces
432
-
433
- return JSON.parse(fixedContent);
434
- } catch (fallbackError) {
435
- // If still failing, provide more detailed error info
436
- const originalError = error instanceof Error ? error.message : String(error);
437
- const fallbackErrorMsg =
438
- fallbackError instanceof Error ? fallbackError.message : String(fallbackError);
439
- throw new Error(
440
- `JSON parsing failed. Original error: ${originalError}. After fixing trailing commas: ${fallbackErrorMsg}`,
441
- );
442
- }
443
- }
444
- }
445
-
446
- private async addServerToSource(
447
- source: ConfigSource,
448
- name: string,
449
- config: IMcpServerConfig,
450
- ): Promise<void> {
451
- const configPath = source.path;
452
- // Ensure directory exists
453
- const dir = path.dirname(configPath);
454
- await fsPromises.mkdir(dir, { recursive: true });
455
-
456
- // Determine config type based on source type
457
- const isVSCodeLikeSettings = ["vscode-user", "vscode-workspace", "abacusai-user"].includes(
458
- source.type,
459
- );
460
-
461
- if (isVSCodeLikeSettings) {
462
- if (configPath.endsWith("mcp.json")) {
463
- let mcpConfig: McpConfigFile = { servers: {} };
464
- if (fs.existsSync(configPath)) {
465
- try {
466
- const content = await fsPromises.readFile(configPath, "utf8");
467
- mcpConfig = this.parseJsonWithFallback(content);
468
- } catch {}
469
- }
470
-
471
- mcpConfig.servers = mcpConfig.servers || {};
472
- mcpConfig.servers[name] = config;
473
-
474
- const newContent = JSON.stringify(mcpConfig, null, 2);
475
- await fsPromises.writeFile(configPath, newContent, "utf8");
476
- } else {
477
- let vscodeSettings: VSCodeSettings = {};
478
- if (fs.existsSync(configPath)) {
479
- try {
480
- const content = await fsPromises.readFile(configPath, "utf8");
481
- vscodeSettings = this.parseJsonWithFallback(content);
482
- } catch {}
483
- }
484
-
485
- vscodeSettings.mcp = vscodeSettings.mcp || { servers: {} };
486
- vscodeSettings.mcp.servers = vscodeSettings.mcp.servers || {};
487
- vscodeSettings.mcp.servers[name] = config;
488
-
489
- const newContent = JSON.stringify(vscodeSettings, null, 2);
490
- await fsPromises.writeFile(configPath, newContent, "utf8");
491
- }
492
- } else if (source.type === "cursor-global") {
493
- // Cursor Global format: { "mcpServers": { ... } }
494
- let cursorConfig: CursorGlobalConfig = { mcpServers: {} };
495
- if (fs.existsSync(configPath)) {
496
- try {
497
- const content = await fsPromises.readFile(configPath, "utf8");
498
- cursorConfig = this.parseJsonWithFallback(content);
499
- } catch {
500
- // Invalid JSON, start fresh
501
- }
502
- }
503
-
504
- cursorConfig.mcpServers = cursorConfig.mcpServers || {};
505
-
506
- // Convert our internal format to Cursor format
507
- const cursorServerConfig =
508
- "url" in config
509
- ? {
510
- url: config.url,
511
- }
512
- : {
513
- command: config.command,
514
- args: config.args ? [...config.args] : undefined,
515
- env: config.env
516
- ? Object.fromEntries(Object.entries(config.env).map(([k, v]) => [k, String(v)]))
517
- : undefined,
518
- };
519
-
520
- cursorConfig.mcpServers[name] = cursorServerConfig;
521
-
522
- const newContent = JSON.stringify(cursorConfig, null, 2);
523
- await fsPromises.writeFile(configPath, newContent, "utf8");
524
- } else if (source.type === "cursor-workspace") {
525
- let cursorWorkspaceConfig: CursorGlobalConfig = { mcpServers: {} };
526
- if (fs.existsSync(configPath)) {
527
- try {
528
- const content = await fsPromises.readFile(configPath, "utf8");
529
- cursorWorkspaceConfig = this.parseJsonWithFallback(content);
530
- } catch {
531
- // Invalid JSON, start fresh
532
- }
533
- }
534
-
535
- cursorWorkspaceConfig.mcpServers = cursorWorkspaceConfig.mcpServers || {};
536
-
537
- const cursorServerConfig =
538
- "url" in config
539
- ? {
540
- url: config.url,
541
- }
542
- : {
543
- command: config.command,
544
- args: config.args ? [...config.args] : undefined,
545
- env: config.env
546
- ? Object.fromEntries(Object.entries(config.env).map(([k, v]) => [k, String(v)]))
547
- : undefined,
548
- };
549
-
550
- cursorWorkspaceConfig.mcpServers[name] = cursorServerConfig;
551
- const newContent = JSON.stringify(cursorWorkspaceConfig, null, 2);
552
- await fsPromises.writeFile(configPath, newContent, "utf8");
553
- } else {
554
- throw new Error(`Unsupported config source type: ${source.type}`);
555
- }
556
- }
557
-
558
- private async removeServerFromSource(configPath: string, name: string): Promise<boolean> {
559
- if (!fs.existsSync(configPath)) {
560
- return false;
561
- }
562
-
563
- const content = await fsPromises.readFile(configPath, "utf8");
564
- const isCursorOrClaudeFormat =
565
- configPath.includes(".cursor") ||
566
- configPath.includes("claude_desktop_config.json") ||
567
- configPath.includes("mcp_config.json"); // Windsurf
568
-
569
- if (configPath.endsWith("mcp.json")) {
570
- const mcpConfig: McpConfigFile = this.parseJsonWithFallback(content);
571
-
572
- if (!mcpConfig.servers || !mcpConfig.servers[name]) {
573
- return false;
574
- }
575
-
576
- delete mcpConfig.servers[name];
577
-
578
- const newContent = JSON.stringify(mcpConfig, null, 2);
579
- await fsPromises.writeFile(configPath, newContent, "utf8");
580
- return true;
581
- } else if (configPath.endsWith("settings.json")) {
582
- const vscodeSettings: VSCodeSettings = this.parseJsonWithFallback(content);
583
- const config = vscodeSettings?.mcp || { servers: {} };
584
-
585
- if (!config.servers || !config.servers[name]) {
586
- return false;
587
- }
588
-
589
- delete config.servers[name];
590
- vscodeSettings.mcp = config;
591
-
592
- const newContent = JSON.stringify(vscodeSettings, null, 2);
593
- await fsPromises.writeFile(configPath, newContent, "utf8");
594
- return true;
595
- } else if (isCursorOrClaudeFormat) {
596
- // Claude/Cursor/Windsurf format - matches GUI's claudeConfigToServerDefinition
597
- const cursorConfig: CursorGlobalConfig = this.parseJsonWithFallback(content);
598
-
599
- if (!cursorConfig.mcpServers || !cursorConfig.mcpServers[name]) {
600
- return false;
601
- }
602
-
603
- delete cursorConfig.mcpServers[name];
604
-
605
- const newContent = JSON.stringify(cursorConfig, null, 2);
606
- await fsPromises.writeFile(configPath, newContent, "utf8");
607
- return true;
608
- } else {
609
- throw new Error(`Unsupported config file format: ${configPath}`);
610
- }
611
- }
612
-
613
- private validateServerConfig(config: IMcpServerConfig): void {
614
- // Check if config is null, undefined, or not an object
615
- if (!config || typeof config !== "object") {
616
- throw new Error("Server configuration must be a valid object");
617
- }
618
-
619
- // Check for invalid top-level properties that suggest wrong format
620
- if ("mcpServers" in config || "servers" in config) {
621
- throw new Error(
622
- 'Invalid configuration format. Server configuration should not contain "mcpServers" or "servers" properties. Each server should be configured individually.',
623
- );
624
- }
625
-
626
- if ("url" in config) {
627
- // HTTP server validation
628
- if (!config.url) {
629
- throw new Error("URL is required for HTTP servers");
630
- }
631
- if (typeof config.url !== "string") {
632
- throw new Error("URL must be a string");
633
- }
634
- if (!config.url.startsWith("http://") && !config.url.startsWith("https://")) {
635
- throw new Error("URL must start with http:// or https://");
636
- }
637
- } else if ("command" in config) {
638
- // Stdio server validation
639
- if (!config.command) {
640
- throw new Error("Command is required for stdio servers");
641
- }
642
- if (typeof config.command !== "string") {
643
- throw new Error("Command must be a string");
644
- }
645
- // Validate args if present
646
- if (config.args && !Array.isArray(config.args)) {
647
- throw new Error("Arguments must be an array");
648
- }
649
- if (config.args && config.args.some((arg) => typeof arg !== "string")) {
650
- throw new Error("All arguments must be strings");
651
- }
652
- } else {
653
- throw new Error('Server configuration must have either "url" or "command" property');
654
- }
655
- }
656
-
657
- private getServerTypeDescription(config: IMcpServerConfig): string {
658
- if ("url" in config) {
659
- return `HTTP server at ${config.url}`;
660
- } else if ("command" in config) {
661
- const argsStr = config.args && config.args.length > 0 ? ` ${config.args.join(" ")}` : "";
662
- return `Command: ${config.command}${argsStr}`;
663
- }
664
- return "Unknown server type";
665
- }
666
-
667
- private getServerSourceLabel(server: IMcpServerInfo): string {
668
- if (server.id.startsWith("claude-desktop")) {
669
- return "Claude Desktop";
670
- } else if (server.id.startsWith("windsurf")) {
671
- return "Windsurf";
672
- } else if (server.id.startsWith("cursor-global")) {
673
- return "Cursor (Global)";
674
- } else if (server.id.startsWith("cursor-workspace")) {
675
- return "Cursor (Workspace)";
676
- } else if (server.id.startsWith("abacusai-user")) {
677
- const isDev =
678
- server.description?.includes(`${product.fullName}-dev`) ||
679
- server.description?.includes("CodeLLM-dev");
680
- return isDev ? `${product.fullName}-dev (User)` : `${product.fullName} (User)`;
681
- } else if (server.id.startsWith("vscode-user")) {
682
- return "VSCode (User)";
683
- } else if (server.id.startsWith("vscode-workspace")) {
684
- return "VSCode (Workspace)";
685
- }
686
- return "Other";
687
- }
688
- }