@snapback/cli 1.0.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.
- package/LICENSE +201 -0
- package/README.md +565 -0
- package/dist/SkippedTestDetector-JY4EF5BN.js +4 -0
- package/dist/SkippedTestDetector-JY4EF5BN.js.map +1 -0
- package/dist/analysis-B4NVULM4.js +5 -0
- package/dist/analysis-B4NVULM4.js.map +1 -0
- package/dist/chunk-BCIXMIPW.js +26 -0
- package/dist/chunk-BCIXMIPW.js.map +1 -0
- package/dist/chunk-BJS6XH2V.js +108 -0
- package/dist/chunk-BJS6XH2V.js.map +1 -0
- package/dist/chunk-KSPLKCVF.js +299 -0
- package/dist/chunk-KSPLKCVF.js.map +1 -0
- package/dist/chunk-MTQ6ESQR.js +16222 -0
- package/dist/chunk-MTQ6ESQR.js.map +1 -0
- package/dist/chunk-RU7BOXR3.js +906 -0
- package/dist/chunk-RU7BOXR3.js.map +1 -0
- package/dist/chunk-VSJ33PLA.js +1380 -0
- package/dist/chunk-VSJ33PLA.js.map +1 -0
- package/dist/chunk-WALLF2AH.js +12744 -0
- package/dist/chunk-WALLF2AH.js.map +1 -0
- package/dist/chunk-WCQVDF3K.js +12 -0
- package/dist/chunk-WCQVDF3K.js.map +1 -0
- package/dist/dist-7GPVXUEA.js +4 -0
- package/dist/dist-7GPVXUEA.js.map +1 -0
- package/dist/dist-DVM64QIS.js +7 -0
- package/dist/dist-DVM64QIS.js.map +1 -0
- package/dist/dist-FBRR6YHP.js +4 -0
- package/dist/dist-FBRR6YHP.js.map +1 -0
- package/dist/index.js +30717 -0
- package/dist/index.js.map +1 -0
- package/dist/secure-credentials-YKZHAZNB.js +257 -0
- package/dist/secure-credentials-YKZHAZNB.js.map +1 -0
- package/dist/snapback-dir-4QRR2IPV.js +5 -0
- package/dist/snapback-dir-4QRR2IPV.js.map +1 -0
- package/package.json +97 -0
|
@@ -0,0 +1,906 @@
|
|
|
1
|
+
import { __name } from './chunk-WCQVDF3K.js';
|
|
2
|
+
import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'fs';
|
|
3
|
+
import { homedir, platform } from 'os';
|
|
4
|
+
import { resolve, dirname, join } from 'path';
|
|
5
|
+
|
|
6
|
+
var __defProp = Object.defineProperty;
|
|
7
|
+
var __name2 = /* @__PURE__ */ __name((target, value) => __defProp(target, "name", {
|
|
8
|
+
value,
|
|
9
|
+
configurable: true
|
|
10
|
+
}), "__name");
|
|
11
|
+
var CLIENT_CONFIGS = {
|
|
12
|
+
claude: /* @__PURE__ */ __name2((home) => {
|
|
13
|
+
switch (platform()) {
|
|
14
|
+
case "darwin":
|
|
15
|
+
return [
|
|
16
|
+
join(home, "Library/Application Support/Claude/claude_desktop_config.json")
|
|
17
|
+
];
|
|
18
|
+
case "win32":
|
|
19
|
+
return [
|
|
20
|
+
join(process.env.APPDATA || "", "Claude/claude_desktop_config.json")
|
|
21
|
+
];
|
|
22
|
+
default:
|
|
23
|
+
return [
|
|
24
|
+
join(home, ".config/Claude/claude_desktop_config.json")
|
|
25
|
+
];
|
|
26
|
+
}
|
|
27
|
+
}, "claude"),
|
|
28
|
+
// Project-level first (user preference), then global fallback
|
|
29
|
+
cursor: /* @__PURE__ */ __name2((_home, cwd) => [
|
|
30
|
+
...cwd ? [
|
|
31
|
+
join(cwd, ".cursor/mcp.json")
|
|
32
|
+
] : [],
|
|
33
|
+
join(_home, ".cursor/mcp.json")
|
|
34
|
+
], "cursor"),
|
|
35
|
+
windsurf: /* @__PURE__ */ __name2((home) => [
|
|
36
|
+
join(home, ".codeium/windsurf/mcp_config.json")
|
|
37
|
+
], "windsurf"),
|
|
38
|
+
continue: /* @__PURE__ */ __name2((home) => [
|
|
39
|
+
join(home, ".continue/config.json")
|
|
40
|
+
], "continue"),
|
|
41
|
+
// New clients
|
|
42
|
+
vscode: /* @__PURE__ */ __name2((_home, cwd) => [
|
|
43
|
+
...cwd ? [
|
|
44
|
+
join(cwd, ".vscode/mcp.json")
|
|
45
|
+
] : []
|
|
46
|
+
], "vscode"),
|
|
47
|
+
zed: /* @__PURE__ */ __name2((home) => [
|
|
48
|
+
join(home, ".config/zed/settings.json")
|
|
49
|
+
], "zed"),
|
|
50
|
+
cline: /* @__PURE__ */ __name2((home) => [
|
|
51
|
+
join(home, ".cline/mcp.json")
|
|
52
|
+
], "cline"),
|
|
53
|
+
gemini: /* @__PURE__ */ __name2((home) => [
|
|
54
|
+
join(home, ".gemini/settings.json")
|
|
55
|
+
], "gemini"),
|
|
56
|
+
aider: /* @__PURE__ */ __name2((home) => [
|
|
57
|
+
join(home, ".aider/mcp.yaml")
|
|
58
|
+
], "aider"),
|
|
59
|
+
"roo-code": /* @__PURE__ */ __name2((home) => [
|
|
60
|
+
join(home, ".roo-code/mcp.json")
|
|
61
|
+
], "roo-code"),
|
|
62
|
+
// Qoder (VS Code fork) - supports both project-level and global configs
|
|
63
|
+
qoder: /* @__PURE__ */ __name2((home) => {
|
|
64
|
+
switch (platform()) {
|
|
65
|
+
case "darwin":
|
|
66
|
+
return [
|
|
67
|
+
join(home, "Library/Application Support/Qoder/SharedClientCache/extension/local/mcp.json")
|
|
68
|
+
];
|
|
69
|
+
case "win32":
|
|
70
|
+
return [
|
|
71
|
+
join(process.env.APPDATA || "", "Qoder/mcp.json")
|
|
72
|
+
];
|
|
73
|
+
default:
|
|
74
|
+
return [
|
|
75
|
+
join(home, ".config/Qoder/mcp.json")
|
|
76
|
+
];
|
|
77
|
+
}
|
|
78
|
+
}, "qoder")
|
|
79
|
+
};
|
|
80
|
+
var CLIENT_DISPLAY_NAMES = {
|
|
81
|
+
claude: "Claude Desktop",
|
|
82
|
+
cursor: "Cursor",
|
|
83
|
+
windsurf: "Windsurf",
|
|
84
|
+
continue: "Continue",
|
|
85
|
+
vscode: "VS Code",
|
|
86
|
+
zed: "Zed",
|
|
87
|
+
cline: "Cline",
|
|
88
|
+
gemini: "Gemini/Antigravity",
|
|
89
|
+
aider: "Aider",
|
|
90
|
+
"roo-code": "Roo Code",
|
|
91
|
+
qoder: "Qoder"
|
|
92
|
+
};
|
|
93
|
+
function detectAIClients(options = {}) {
|
|
94
|
+
const home = homedir();
|
|
95
|
+
const cwd = options.cwd || process.cwd();
|
|
96
|
+
const clients = [];
|
|
97
|
+
const seenPaths = /* @__PURE__ */ new Set();
|
|
98
|
+
for (const [name, getPaths] of Object.entries(CLIENT_CONFIGS)) {
|
|
99
|
+
const paths = getPaths(home, cwd);
|
|
100
|
+
for (const configPath of paths) {
|
|
101
|
+
if (seenPaths.has(configPath)) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
seenPaths.add(configPath);
|
|
105
|
+
const exists = existsSync(configPath);
|
|
106
|
+
let hasSnapback = false;
|
|
107
|
+
if (exists) {
|
|
108
|
+
try {
|
|
109
|
+
const content = readFileSync(configPath, "utf-8");
|
|
110
|
+
if (configPath.endsWith(".yaml") || configPath.endsWith(".yml")) {
|
|
111
|
+
hasSnapback = content.includes("snapback");
|
|
112
|
+
} else {
|
|
113
|
+
const parsed = JSON.parse(content);
|
|
114
|
+
hasSnapback = checkForSnapback(parsed, name);
|
|
115
|
+
}
|
|
116
|
+
} catch {
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
clients.push({
|
|
120
|
+
name,
|
|
121
|
+
displayName: CLIENT_DISPLAY_NAMES[name] || name,
|
|
122
|
+
configPath,
|
|
123
|
+
exists,
|
|
124
|
+
hasSnapback,
|
|
125
|
+
format: name
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const detected = clients.filter((c) => c.exists);
|
|
130
|
+
const needsSetup = detected.filter((c) => !c.hasSnapback);
|
|
131
|
+
return {
|
|
132
|
+
clients,
|
|
133
|
+
detected,
|
|
134
|
+
needsSetup
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
__name(detectAIClients, "detectAIClients");
|
|
138
|
+
__name2(detectAIClients, "detectAIClients");
|
|
139
|
+
function getClient(clientName) {
|
|
140
|
+
const result = detectAIClients();
|
|
141
|
+
return result.clients.find((c) => c.name === clientName && c.exists);
|
|
142
|
+
}
|
|
143
|
+
__name(getClient, "getClient");
|
|
144
|
+
__name2(getClient, "getClient");
|
|
145
|
+
function getConfiguredClients() {
|
|
146
|
+
const result = detectAIClients();
|
|
147
|
+
return result.detected.filter((c) => c.hasSnapback);
|
|
148
|
+
}
|
|
149
|
+
__name(getConfiguredClients, "getConfiguredClients");
|
|
150
|
+
__name2(getConfiguredClients, "getConfiguredClients");
|
|
151
|
+
function checkForSnapback(config, format) {
|
|
152
|
+
if (!config || typeof config !== "object") {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
const configObj = config;
|
|
156
|
+
switch (format) {
|
|
157
|
+
case "claude":
|
|
158
|
+
case "cursor":
|
|
159
|
+
case "windsurf":
|
|
160
|
+
case "vscode":
|
|
161
|
+
case "cline":
|
|
162
|
+
case "roo-code":
|
|
163
|
+
case "qoder":
|
|
164
|
+
if ("mcpServers" in configObj && typeof configObj.mcpServers === "object" && configObj.mcpServers !== null) {
|
|
165
|
+
const servers = configObj.mcpServers;
|
|
166
|
+
return "snapback" in servers;
|
|
167
|
+
}
|
|
168
|
+
return false;
|
|
169
|
+
case "gemini":
|
|
170
|
+
case "zed":
|
|
171
|
+
if ("mcpServers" in configObj && typeof configObj.mcpServers === "object" && configObj.mcpServers !== null) {
|
|
172
|
+
const servers = configObj.mcpServers;
|
|
173
|
+
return "snapback" in servers;
|
|
174
|
+
}
|
|
175
|
+
return false;
|
|
176
|
+
case "continue":
|
|
177
|
+
if ("experimental" in configObj && typeof configObj.experimental === "object" && configObj.experimental !== null) {
|
|
178
|
+
const experimental = configObj.experimental;
|
|
179
|
+
if ("modelContextProtocolServers" in experimental && Array.isArray(experimental.modelContextProtocolServers)) {
|
|
180
|
+
return experimental.modelContextProtocolServers.some((server) => typeof server === "object" && server !== null && server.name === "snapback");
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return false;
|
|
184
|
+
case "aider":
|
|
185
|
+
return false;
|
|
186
|
+
default:
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
__name(checkForSnapback, "checkForSnapback");
|
|
191
|
+
__name2(checkForSnapback, "checkForSnapback");
|
|
192
|
+
function getClientConfigPath(clientName) {
|
|
193
|
+
const getPaths = CLIENT_CONFIGS[clientName];
|
|
194
|
+
if (!getPaths) {
|
|
195
|
+
return void 0;
|
|
196
|
+
}
|
|
197
|
+
const paths = getPaths(homedir());
|
|
198
|
+
return paths[0];
|
|
199
|
+
}
|
|
200
|
+
__name(getClientConfigPath, "getClientConfigPath");
|
|
201
|
+
__name2(getClientConfigPath, "getClientConfigPath");
|
|
202
|
+
function readClientConfig(client) {
|
|
203
|
+
try {
|
|
204
|
+
const content = readFileSync(client.configPath, "utf-8");
|
|
205
|
+
return JSON.parse(content);
|
|
206
|
+
} catch {
|
|
207
|
+
return void 0;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
__name(readClientConfig, "readClientConfig");
|
|
211
|
+
__name2(readClientConfig, "readClientConfig");
|
|
212
|
+
function validateClientConfig(client) {
|
|
213
|
+
const issues = [];
|
|
214
|
+
if (!existsSync(client.configPath)) {
|
|
215
|
+
issues.push({
|
|
216
|
+
severity: "error",
|
|
217
|
+
code: "CONFIG_NOT_FOUND",
|
|
218
|
+
message: `Config file not found: ${client.configPath}`,
|
|
219
|
+
fix: `Run: snap tools configure --${client.name}`
|
|
220
|
+
});
|
|
221
|
+
return {
|
|
222
|
+
valid: false,
|
|
223
|
+
issues
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
let configContent;
|
|
227
|
+
let parsedConfig;
|
|
228
|
+
try {
|
|
229
|
+
configContent = readFileSync(client.configPath, "utf-8");
|
|
230
|
+
} catch (error) {
|
|
231
|
+
issues.push({
|
|
232
|
+
severity: "error",
|
|
233
|
+
code: "CONFIG_READ_ERROR",
|
|
234
|
+
message: `Cannot read config file: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
235
|
+
fix: "Check file permissions"
|
|
236
|
+
});
|
|
237
|
+
return {
|
|
238
|
+
valid: false,
|
|
239
|
+
issues
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
try {
|
|
243
|
+
parsedConfig = JSON.parse(configContent);
|
|
244
|
+
} catch (error) {
|
|
245
|
+
issues.push({
|
|
246
|
+
severity: "error",
|
|
247
|
+
code: "CONFIG_PARSE_ERROR",
|
|
248
|
+
message: `Invalid JSON in config file: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
249
|
+
fix: `Edit ${client.configPath} to fix JSON syntax, or run: snap tools configure --${client.name} --force`
|
|
250
|
+
});
|
|
251
|
+
return {
|
|
252
|
+
valid: false,
|
|
253
|
+
issues
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
if (!client.hasSnapback) {
|
|
257
|
+
issues.push({
|
|
258
|
+
severity: "warning",
|
|
259
|
+
code: "SNAPBACK_NOT_CONFIGURED",
|
|
260
|
+
message: "SnapBack MCP server not found in config",
|
|
261
|
+
fix: `Run: snap tools configure --${client.name}`
|
|
262
|
+
});
|
|
263
|
+
return {
|
|
264
|
+
valid: false,
|
|
265
|
+
issues
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
const snapbackConfig = extractSnapbackConfig(parsedConfig, client.format);
|
|
269
|
+
if (!snapbackConfig) {
|
|
270
|
+
issues.push({
|
|
271
|
+
severity: "error",
|
|
272
|
+
code: "SNAPBACK_CONFIG_INVALID",
|
|
273
|
+
message: "SnapBack config found but cannot be parsed"
|
|
274
|
+
});
|
|
275
|
+
return {
|
|
276
|
+
valid: false,
|
|
277
|
+
issues
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
validateSnapbackConfig(snapbackConfig, issues);
|
|
281
|
+
if (snapbackConfig.command && snapbackConfig.args) {
|
|
282
|
+
const workspaceIdx = snapbackConfig.args.indexOf("--workspace");
|
|
283
|
+
if (workspaceIdx !== -1 && workspaceIdx + 1 < snapbackConfig.args.length) {
|
|
284
|
+
const workspacePath = snapbackConfig.args[workspaceIdx + 1];
|
|
285
|
+
const wsValidation = validateWorkspacePath(workspacePath);
|
|
286
|
+
if (!wsValidation.exists) {
|
|
287
|
+
issues.push({
|
|
288
|
+
severity: "error",
|
|
289
|
+
code: "WORKSPACE_NOT_FOUND",
|
|
290
|
+
message: `Workspace path does not exist: ${workspacePath}`,
|
|
291
|
+
fix: "Update workspace path or run: snap tools configure --force"
|
|
292
|
+
});
|
|
293
|
+
} else if (!wsValidation.hasMarkers) {
|
|
294
|
+
issues.push({
|
|
295
|
+
severity: "warning",
|
|
296
|
+
code: "WORKSPACE_NO_MARKERS",
|
|
297
|
+
message: `Workspace path has no markers (.git, package.json, .snapback): ${workspacePath}`,
|
|
298
|
+
fix: "Run: snap init"
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return {
|
|
304
|
+
valid: issues.filter((i) => i.severity === "error").length === 0,
|
|
305
|
+
issues,
|
|
306
|
+
config: snapbackConfig
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
__name(validateClientConfig, "validateClientConfig");
|
|
310
|
+
__name2(validateClientConfig, "validateClientConfig");
|
|
311
|
+
function validateWorkspacePath(workspacePath) {
|
|
312
|
+
const absPath = resolve(workspacePath);
|
|
313
|
+
if (!existsSync(absPath)) {
|
|
314
|
+
return {
|
|
315
|
+
exists: false,
|
|
316
|
+
hasMarkers: false,
|
|
317
|
+
path: absPath
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
const hasGit = existsSync(resolve(absPath, ".git"));
|
|
321
|
+
const hasPackageJson = existsSync(resolve(absPath, "package.json"));
|
|
322
|
+
const hasSnapback = existsSync(resolve(absPath, ".snapback"));
|
|
323
|
+
return {
|
|
324
|
+
exists: true,
|
|
325
|
+
hasMarkers: hasGit || hasPackageJson || hasSnapback,
|
|
326
|
+
path: absPath
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
__name(validateWorkspacePath, "validateWorkspacePath");
|
|
330
|
+
__name2(validateWorkspacePath, "validateWorkspacePath");
|
|
331
|
+
function extractSnapbackConfig(parsed, format) {
|
|
332
|
+
if (!parsed || typeof parsed !== "object") {
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
const config = parsed;
|
|
336
|
+
switch (format) {
|
|
337
|
+
case "claude":
|
|
338
|
+
case "cursor":
|
|
339
|
+
case "windsurf":
|
|
340
|
+
case "vscode":
|
|
341
|
+
case "cline":
|
|
342
|
+
case "roo-code":
|
|
343
|
+
case "qoder":
|
|
344
|
+
case "gemini":
|
|
345
|
+
case "zed":
|
|
346
|
+
if ("mcpServers" in config && typeof config.mcpServers === "object" && config.mcpServers !== null) {
|
|
347
|
+
const servers = config.mcpServers;
|
|
348
|
+
if ("snapback" in servers) {
|
|
349
|
+
return servers.snapback;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return null;
|
|
353
|
+
case "continue":
|
|
354
|
+
if ("experimental" in config && typeof config.experimental === "object" && config.experimental !== null) {
|
|
355
|
+
const experimental = config.experimental;
|
|
356
|
+
if ("modelContextProtocolServers" in experimental && Array.isArray(experimental.modelContextProtocolServers)) {
|
|
357
|
+
const server = experimental.modelContextProtocolServers.find((s) => typeof s === "object" && s !== null && s.name === "snapback");
|
|
358
|
+
return server ? server : null;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return null;
|
|
362
|
+
default:
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
__name(extractSnapbackConfig, "extractSnapbackConfig");
|
|
367
|
+
__name2(extractSnapbackConfig, "extractSnapbackConfig");
|
|
368
|
+
function validateSnapbackConfig(config, issues) {
|
|
369
|
+
if (!config.command && !config.url) {
|
|
370
|
+
issues.push({
|
|
371
|
+
severity: "error",
|
|
372
|
+
code: "MISSING_COMMAND_OR_URL",
|
|
373
|
+
message: "Config must have either 'command' (stdio) or 'url' (HTTP)",
|
|
374
|
+
fix: "Run: snap tools configure --force"
|
|
375
|
+
});
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
if (config.command) {
|
|
379
|
+
if (!config.args || !Array.isArray(config.args)) {
|
|
380
|
+
issues.push({
|
|
381
|
+
severity: "error",
|
|
382
|
+
code: "MISSING_ARGS",
|
|
383
|
+
message: "Command-based config must have 'args' array",
|
|
384
|
+
fix: "Run: snap tools configure --force"
|
|
385
|
+
});
|
|
386
|
+
} else {
|
|
387
|
+
if (!config.args.includes("mcp")) {
|
|
388
|
+
issues.push({
|
|
389
|
+
severity: "error",
|
|
390
|
+
code: "MISSING_MCP_ARG",
|
|
391
|
+
message: "Args must include 'mcp' command",
|
|
392
|
+
fix: "Run: snap tools configure --force"
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
if (!config.args.includes("--stdio")) {
|
|
396
|
+
issues.push({
|
|
397
|
+
severity: "error",
|
|
398
|
+
code: "MISSING_STDIO_ARG",
|
|
399
|
+
message: "Args must include '--stdio' flag",
|
|
400
|
+
fix: "Run: snap tools configure --force"
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
if (!config.args.includes("--workspace")) {
|
|
404
|
+
issues.push({
|
|
405
|
+
severity: "warning",
|
|
406
|
+
code: "MISSING_WORKSPACE_ARG",
|
|
407
|
+
message: "Args should include '--workspace' path for reliability",
|
|
408
|
+
fix: "Run: snap tools configure --force"
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
if (config.url) {
|
|
414
|
+
try {
|
|
415
|
+
new URL(config.url);
|
|
416
|
+
} catch {
|
|
417
|
+
issues.push({
|
|
418
|
+
severity: "error",
|
|
419
|
+
code: "INVALID_URL",
|
|
420
|
+
message: `Invalid server URL: ${config.url}`,
|
|
421
|
+
fix: "Run: snap tools configure --force"
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
if (config.env) {
|
|
426
|
+
if (!config.env.SNAPBACK_API_KEY && !config.env.SNAPBACK_WORKSPACE_ID) {
|
|
427
|
+
issues.push({
|
|
428
|
+
severity: "info",
|
|
429
|
+
code: "NO_AUTH",
|
|
430
|
+
message: "No API key or workspace ID found (free tier will be used)"
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
__name(validateSnapbackConfig, "validateSnapbackConfig");
|
|
436
|
+
__name2(validateSnapbackConfig, "validateSnapbackConfig");
|
|
437
|
+
function getSnapbackMCPConfig(options = {}) {
|
|
438
|
+
const { apiKey, workspaceId, serverUrl, useBinary = false, customCommand, additionalEnv, workspaceRoot, useLocalDev = false, localCliPath } = options;
|
|
439
|
+
const env = {
|
|
440
|
+
...additionalEnv
|
|
441
|
+
};
|
|
442
|
+
if (workspaceId) {
|
|
443
|
+
env.SNAPBACK_WORKSPACE_ID = workspaceId;
|
|
444
|
+
}
|
|
445
|
+
if (apiKey) {
|
|
446
|
+
env.SNAPBACK_API_KEY = apiKey;
|
|
447
|
+
}
|
|
448
|
+
if (customCommand) {
|
|
449
|
+
return {
|
|
450
|
+
command: customCommand,
|
|
451
|
+
args: [],
|
|
452
|
+
...Object.keys(env).length > 0 && {
|
|
453
|
+
env
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
if (serverUrl || !useLocalDev && !useBinary) {
|
|
458
|
+
const url = serverUrl || "https://snapback-mcp.fly.dev";
|
|
459
|
+
return {
|
|
460
|
+
url,
|
|
461
|
+
...Object.keys(env).length > 0 && {
|
|
462
|
+
env
|
|
463
|
+
}
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
if (useLocalDev && localCliPath) {
|
|
467
|
+
const tier = apiKey ? "pro" : "free";
|
|
468
|
+
const args = [
|
|
469
|
+
localCliPath,
|
|
470
|
+
"mcp",
|
|
471
|
+
"--stdio",
|
|
472
|
+
"--tier",
|
|
473
|
+
tier
|
|
474
|
+
];
|
|
475
|
+
if (workspaceRoot) {
|
|
476
|
+
args.push("--workspace", workspaceRoot);
|
|
477
|
+
}
|
|
478
|
+
return {
|
|
479
|
+
command: "node",
|
|
480
|
+
args,
|
|
481
|
+
...Object.keys(env).length > 0 && {
|
|
482
|
+
env
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
if (useBinary) {
|
|
487
|
+
const tier = apiKey ? "pro" : "free";
|
|
488
|
+
const args = [
|
|
489
|
+
"mcp",
|
|
490
|
+
"--stdio",
|
|
491
|
+
"--tier",
|
|
492
|
+
tier
|
|
493
|
+
];
|
|
494
|
+
if (workspaceRoot) {
|
|
495
|
+
args.push("--workspace", workspaceRoot);
|
|
496
|
+
}
|
|
497
|
+
return {
|
|
498
|
+
command: "snapback",
|
|
499
|
+
args,
|
|
500
|
+
...Object.keys(env).length > 0 && {
|
|
501
|
+
env
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
return {
|
|
506
|
+
url: "https://snapback-mcp.fly.dev",
|
|
507
|
+
...Object.keys(env).length > 0 && {
|
|
508
|
+
env
|
|
509
|
+
}
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
__name(getSnapbackMCPConfig, "getSnapbackMCPConfig");
|
|
513
|
+
__name2(getSnapbackMCPConfig, "getSnapbackMCPConfig");
|
|
514
|
+
function writeClientConfig(client, mcpConfig) {
|
|
515
|
+
try {
|
|
516
|
+
const configDir = dirname(client.configPath);
|
|
517
|
+
mkdirSync(configDir, {
|
|
518
|
+
recursive: true
|
|
519
|
+
});
|
|
520
|
+
let existingConfig = {
|
|
521
|
+
mcpServers: {}
|
|
522
|
+
};
|
|
523
|
+
let hasExistingConfig = false;
|
|
524
|
+
if (existsSync(client.configPath)) {
|
|
525
|
+
try {
|
|
526
|
+
const content = readFileSync(client.configPath, "utf-8");
|
|
527
|
+
existingConfig = JSON.parse(content);
|
|
528
|
+
hasExistingConfig = Object.keys(existingConfig).length > 0;
|
|
529
|
+
} catch {
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
let backup;
|
|
533
|
+
if (hasExistingConfig) {
|
|
534
|
+
backup = `${client.configPath}.backup.${Date.now()}`;
|
|
535
|
+
writeFileSync(backup, JSON.stringify(existingConfig, null, 2));
|
|
536
|
+
}
|
|
537
|
+
const newConfig = mergeConfig(existingConfig, mcpConfig, client.format);
|
|
538
|
+
writeFileSync(client.configPath, JSON.stringify(newConfig, null, 2));
|
|
539
|
+
return {
|
|
540
|
+
success: true,
|
|
541
|
+
backup
|
|
542
|
+
};
|
|
543
|
+
} catch (error) {
|
|
544
|
+
return {
|
|
545
|
+
success: false,
|
|
546
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
__name(writeClientConfig, "writeClientConfig");
|
|
551
|
+
__name2(writeClientConfig, "writeClientConfig");
|
|
552
|
+
function removeSnapbackConfig(client) {
|
|
553
|
+
try {
|
|
554
|
+
if (!existsSync(client.configPath)) {
|
|
555
|
+
return {
|
|
556
|
+
success: true
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
const content = readFileSync(client.configPath, "utf-8");
|
|
560
|
+
const config = JSON.parse(content);
|
|
561
|
+
switch (client.format) {
|
|
562
|
+
case "claude":
|
|
563
|
+
case "cursor":
|
|
564
|
+
case "windsurf":
|
|
565
|
+
if (config.mcpServers?.snapback) {
|
|
566
|
+
delete config.mcpServers.snapback;
|
|
567
|
+
}
|
|
568
|
+
break;
|
|
569
|
+
case "continue": {
|
|
570
|
+
const experimental = config.experimental;
|
|
571
|
+
if (experimental?.modelContextProtocolServers) {
|
|
572
|
+
const servers = experimental.modelContextProtocolServers;
|
|
573
|
+
experimental.modelContextProtocolServers = servers.filter((s) => s.name !== "snapback");
|
|
574
|
+
}
|
|
575
|
+
break;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
writeFileSync(client.configPath, JSON.stringify(config, null, 2));
|
|
579
|
+
return {
|
|
580
|
+
success: true
|
|
581
|
+
};
|
|
582
|
+
} catch (error) {
|
|
583
|
+
return {
|
|
584
|
+
success: false,
|
|
585
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
__name(removeSnapbackConfig, "removeSnapbackConfig");
|
|
590
|
+
__name2(removeSnapbackConfig, "removeSnapbackConfig");
|
|
591
|
+
function mergeConfig(existing, snapbackConfig, format) {
|
|
592
|
+
switch (format) {
|
|
593
|
+
case "claude":
|
|
594
|
+
case "cursor":
|
|
595
|
+
case "windsurf":
|
|
596
|
+
case "vscode":
|
|
597
|
+
case "cline":
|
|
598
|
+
case "roo-code":
|
|
599
|
+
case "gemini":
|
|
600
|
+
case "zed":
|
|
601
|
+
case "qoder":
|
|
602
|
+
return {
|
|
603
|
+
...existing,
|
|
604
|
+
mcpServers: {
|
|
605
|
+
...existing.mcpServers || {},
|
|
606
|
+
snapback: snapbackConfig
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
|
+
case "continue": {
|
|
610
|
+
const continueConfig = existing;
|
|
611
|
+
const experimental = continueConfig.experimental || {};
|
|
612
|
+
const servers = experimental.modelContextProtocolServers || [];
|
|
613
|
+
const filteredServers = servers.filter((s) => s.name !== "snapback");
|
|
614
|
+
filteredServers.push({
|
|
615
|
+
name: "snapback",
|
|
616
|
+
...snapbackConfig
|
|
617
|
+
});
|
|
618
|
+
return {
|
|
619
|
+
...continueConfig,
|
|
620
|
+
experimental: {
|
|
621
|
+
...experimental,
|
|
622
|
+
modelContextProtocolServers: filteredServers
|
|
623
|
+
}
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
case "aider":
|
|
627
|
+
return existing;
|
|
628
|
+
default:
|
|
629
|
+
return {
|
|
630
|
+
...existing,
|
|
631
|
+
mcpServers: {
|
|
632
|
+
...existing.mcpServers || {},
|
|
633
|
+
snapback: snapbackConfig
|
|
634
|
+
}
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
__name(mergeConfig, "mergeConfig");
|
|
639
|
+
__name2(mergeConfig, "mergeConfig");
|
|
640
|
+
function validateConfig(client) {
|
|
641
|
+
try {
|
|
642
|
+
const content = readFileSync(client.configPath, "utf-8");
|
|
643
|
+
const config = JSON.parse(content);
|
|
644
|
+
switch (client.format) {
|
|
645
|
+
case "claude":
|
|
646
|
+
case "cursor":
|
|
647
|
+
case "windsurf":
|
|
648
|
+
case "vscode":
|
|
649
|
+
case "cline":
|
|
650
|
+
case "roo-code":
|
|
651
|
+
case "gemini":
|
|
652
|
+
case "zed":
|
|
653
|
+
case "qoder":
|
|
654
|
+
return Boolean(config.mcpServers?.snapback?.command || config.mcpServers?.snapback?.url);
|
|
655
|
+
case "continue": {
|
|
656
|
+
const expCfg = config.experimental;
|
|
657
|
+
const srvs = expCfg?.modelContextProtocolServers;
|
|
658
|
+
return Boolean(srvs?.some((s) => s.name === "snapback" && (s.command || s.url)));
|
|
659
|
+
}
|
|
660
|
+
case "aider":
|
|
661
|
+
return false;
|
|
662
|
+
default:
|
|
663
|
+
return false;
|
|
664
|
+
}
|
|
665
|
+
} catch {
|
|
666
|
+
return false;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
__name(validateConfig, "validateConfig");
|
|
670
|
+
__name2(validateConfig, "validateConfig");
|
|
671
|
+
function repairClientConfig(client, options = {}) {
|
|
672
|
+
const fixed = [];
|
|
673
|
+
const remaining = [];
|
|
674
|
+
const validation = validateClientConfig(client);
|
|
675
|
+
if (options.force) {
|
|
676
|
+
return performFullReconfiguration(client, options);
|
|
677
|
+
}
|
|
678
|
+
if (validation.valid && validation.issues.length === 0) {
|
|
679
|
+
return {
|
|
680
|
+
success: true,
|
|
681
|
+
fixed: [],
|
|
682
|
+
remaining: []
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
for (const issue of validation.issues) {
|
|
686
|
+
const fixResult = attemptFix(client, issue, validation, options);
|
|
687
|
+
if (fixResult.success) {
|
|
688
|
+
fixed.push(issue.message);
|
|
689
|
+
} else {
|
|
690
|
+
remaining.push(issue.message);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
const hasCriticalErrors = remaining.some((msg) => validation.issues.find((i) => i.message === msg && i.severity === "error"));
|
|
694
|
+
if (hasCriticalErrors) {
|
|
695
|
+
return performFullReconfiguration(client, options);
|
|
696
|
+
}
|
|
697
|
+
return {
|
|
698
|
+
success: remaining.length === 0,
|
|
699
|
+
fixed,
|
|
700
|
+
remaining
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
__name(repairClientConfig, "repairClientConfig");
|
|
704
|
+
__name2(repairClientConfig, "repairClientConfig");
|
|
705
|
+
function injectWorkspacePath(client, workspaceRoot) {
|
|
706
|
+
const fixed = [];
|
|
707
|
+
const remaining = [];
|
|
708
|
+
const detectedWorkspace = workspaceRoot || detectWorkspaceRoot(process.cwd());
|
|
709
|
+
if (!detectedWorkspace) {
|
|
710
|
+
return {
|
|
711
|
+
success: false,
|
|
712
|
+
fixed,
|
|
713
|
+
remaining: [
|
|
714
|
+
"Could not auto-detect workspace root"
|
|
715
|
+
],
|
|
716
|
+
error: "No workspace markers found (.git, package.json, .snapback)"
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
if (!existsSync(detectedWorkspace)) {
|
|
720
|
+
return {
|
|
721
|
+
success: false,
|
|
722
|
+
fixed,
|
|
723
|
+
remaining: [
|
|
724
|
+
`Workspace path does not exist: ${detectedWorkspace}`
|
|
725
|
+
],
|
|
726
|
+
error: "Invalid workspace path"
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
const validation = validateClientConfig(client);
|
|
730
|
+
if (!validation.config) {
|
|
731
|
+
return {
|
|
732
|
+
success: false,
|
|
733
|
+
fixed,
|
|
734
|
+
remaining: [
|
|
735
|
+
"No existing SnapBack config found"
|
|
736
|
+
],
|
|
737
|
+
error: "Must run initial configuration first"
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
if (!validation.config.command) {
|
|
741
|
+
return {
|
|
742
|
+
success: true,
|
|
743
|
+
fixed: [
|
|
744
|
+
"Config uses HTTP transport - no workspace path needed"
|
|
745
|
+
],
|
|
746
|
+
remaining: []
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
const hasWorkspace = validation.config.args?.includes("--workspace");
|
|
750
|
+
if (hasWorkspace) {
|
|
751
|
+
fixed.push("Workspace path already configured");
|
|
752
|
+
return {
|
|
753
|
+
success: true,
|
|
754
|
+
fixed,
|
|
755
|
+
remaining
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
const result = performFullReconfiguration(client, {
|
|
759
|
+
workspaceRoot: detectedWorkspace
|
|
760
|
+
});
|
|
761
|
+
if (result.success) {
|
|
762
|
+
fixed.push(`Injected workspace path: ${detectedWorkspace}`);
|
|
763
|
+
}
|
|
764
|
+
return {
|
|
765
|
+
success: result.success,
|
|
766
|
+
fixed,
|
|
767
|
+
remaining
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
__name(injectWorkspacePath, "injectWorkspacePath");
|
|
771
|
+
__name2(injectWorkspacePath, "injectWorkspacePath");
|
|
772
|
+
function attemptFix(client, issue, _validation, options) {
|
|
773
|
+
switch (issue.code) {
|
|
774
|
+
case "CONFIG_NOT_FOUND":
|
|
775
|
+
case "CONFIG_PARSE_ERROR":
|
|
776
|
+
case "SNAPBACK_NOT_CONFIGURED":
|
|
777
|
+
case "MISSING_COMMAND_OR_URL":
|
|
778
|
+
case "MISSING_ARGS":
|
|
779
|
+
case "MISSING_MCP_ARG":
|
|
780
|
+
case "MISSING_STDIO_ARG":
|
|
781
|
+
case "INVALID_URL":
|
|
782
|
+
return performFullReconfiguration(client, options);
|
|
783
|
+
case "MISSING_WORKSPACE_ARG": {
|
|
784
|
+
const workspace = options.workspaceRoot || detectWorkspaceRoot(process.cwd());
|
|
785
|
+
if (workspace) {
|
|
786
|
+
return performFullReconfiguration(client, {
|
|
787
|
+
...options,
|
|
788
|
+
workspaceRoot: workspace
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
return {
|
|
792
|
+
success: false
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
case "WORKSPACE_NOT_FOUND": {
|
|
796
|
+
const detected = detectWorkspaceRoot(process.cwd());
|
|
797
|
+
if (detected) {
|
|
798
|
+
return performFullReconfiguration(client, {
|
|
799
|
+
...options,
|
|
800
|
+
workspaceRoot: detected
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
return {
|
|
804
|
+
success: false
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
case "WORKSPACE_NO_MARKERS":
|
|
808
|
+
return {
|
|
809
|
+
success: true
|
|
810
|
+
};
|
|
811
|
+
case "NO_AUTH":
|
|
812
|
+
return {
|
|
813
|
+
success: true
|
|
814
|
+
};
|
|
815
|
+
default:
|
|
816
|
+
return {
|
|
817
|
+
success: false
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
__name(attemptFix, "attemptFix");
|
|
822
|
+
__name2(attemptFix, "attemptFix");
|
|
823
|
+
function performFullReconfiguration(client, options) {
|
|
824
|
+
try {
|
|
825
|
+
const workspaceRoot = options.workspaceRoot || detectWorkspaceRoot(process.cwd());
|
|
826
|
+
const mcpConfig = getSnapbackMCPConfig({
|
|
827
|
+
apiKey: options.apiKey,
|
|
828
|
+
workspaceId: options.workspaceId,
|
|
829
|
+
workspaceRoot: workspaceRoot || void 0,
|
|
830
|
+
useLocalDev: true,
|
|
831
|
+
localCliPath: findCliPath()
|
|
832
|
+
});
|
|
833
|
+
const writeResult = writeClientConfig(client, mcpConfig);
|
|
834
|
+
if (writeResult.success) {
|
|
835
|
+
return {
|
|
836
|
+
success: true,
|
|
837
|
+
fixed: [
|
|
838
|
+
"Full reconfiguration completed"
|
|
839
|
+
],
|
|
840
|
+
remaining: []
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
return {
|
|
844
|
+
success: false,
|
|
845
|
+
fixed: [],
|
|
846
|
+
remaining: [
|
|
847
|
+
"Write failed"
|
|
848
|
+
],
|
|
849
|
+
error: writeResult.error
|
|
850
|
+
};
|
|
851
|
+
} catch (error) {
|
|
852
|
+
return {
|
|
853
|
+
success: false,
|
|
854
|
+
fixed: [],
|
|
855
|
+
remaining: [
|
|
856
|
+
"Reconfiguration failed"
|
|
857
|
+
],
|
|
858
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
__name(performFullReconfiguration, "performFullReconfiguration");
|
|
863
|
+
__name2(performFullReconfiguration, "performFullReconfiguration");
|
|
864
|
+
function detectWorkspaceRoot(startPath) {
|
|
865
|
+
let currentPath = resolve(startPath);
|
|
866
|
+
const maxIterations = 50;
|
|
867
|
+
let iterations = 0;
|
|
868
|
+
while (iterations < maxIterations) {
|
|
869
|
+
iterations++;
|
|
870
|
+
const hasGit = existsSync(resolve(currentPath, ".git"));
|
|
871
|
+
const hasPackageJson = existsSync(resolve(currentPath, "package.json"));
|
|
872
|
+
const hasSnapback = existsSync(resolve(currentPath, ".snapback"));
|
|
873
|
+
if (hasGit || hasPackageJson || hasSnapback) {
|
|
874
|
+
return currentPath;
|
|
875
|
+
}
|
|
876
|
+
const parent = resolve(currentPath, "..");
|
|
877
|
+
if (parent === currentPath) {
|
|
878
|
+
break;
|
|
879
|
+
}
|
|
880
|
+
currentPath = parent;
|
|
881
|
+
}
|
|
882
|
+
return null;
|
|
883
|
+
}
|
|
884
|
+
__name(detectWorkspaceRoot, "detectWorkspaceRoot");
|
|
885
|
+
__name2(detectWorkspaceRoot, "detectWorkspaceRoot");
|
|
886
|
+
function findCliPath() {
|
|
887
|
+
const cwd = process.cwd();
|
|
888
|
+
const candidates = [
|
|
889
|
+
resolve(cwd, "apps/cli/dist/index.js"),
|
|
890
|
+
resolve(cwd, "dist/index.js"),
|
|
891
|
+
resolve(cwd, "../cli/dist/index.js"),
|
|
892
|
+
resolve(cwd, "../../apps/cli/dist/index.js")
|
|
893
|
+
];
|
|
894
|
+
for (const path of candidates) {
|
|
895
|
+
if (existsSync(path)) {
|
|
896
|
+
return path;
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
return void 0;
|
|
900
|
+
}
|
|
901
|
+
__name(findCliPath, "findCliPath");
|
|
902
|
+
__name2(findCliPath, "findCliPath");
|
|
903
|
+
|
|
904
|
+
export { detectAIClients, getClient, getClientConfigPath, getConfiguredClients, getSnapbackMCPConfig, injectWorkspacePath, readClientConfig, removeSnapbackConfig, repairClientConfig, validateClientConfig, validateConfig, validateWorkspacePath, writeClientConfig };
|
|
905
|
+
//# sourceMappingURL=chunk-RU7BOXR3.js.map
|
|
906
|
+
//# sourceMappingURL=chunk-RU7BOXR3.js.map
|