@upturtle/wizard 0.1.1 → 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.
- package/README.md +18 -8
- package/package.json +2 -2
- package/src/agents.mjs +9 -0
- package/src/index.mjs +176 -23
- package/src/writers.mjs +50 -13
package/README.md
CHANGED
|
@@ -8,23 +8,33 @@ npx @upturtle/wizard
|
|
|
8
8
|
|
|
9
9
|
The installer:
|
|
10
10
|
|
|
11
|
-
1.
|
|
12
|
-
2.
|
|
13
|
-
3.
|
|
14
|
-
4.
|
|
11
|
+
1. Detects which coding agent(s) you have installed (Claude Code, Cursor, Antigravity, OpenCode, Zed, Claude Desktop).
|
|
12
|
+
2. Shows a checkbox list — `↑`/`↓` to move, space to toggle, `a` to toggle all, enter to confirm.
|
|
13
|
+
3. Opens UpTurtle in your browser to authenticate and mint a scoped personal access token.
|
|
14
|
+
4. Writes the MCP server entry into each selected agent's config.
|
|
15
15
|
|
|
16
16
|
Once it's done, ask your AI: _"deploy this project to UpTurtle."_
|
|
17
17
|
|
|
18
|
+
## Scope
|
|
19
|
+
|
|
20
|
+
By default the wizard writes to your **user-wide** config — the MCP server is available in every project that agent opens. To install into the **current project** instead (so it's checked into git and shared with the team), pass `--project`:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npx @upturtle/wizard --project
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Project scope is supported for `claude-code`, `cursor`, `opencode`, and `zed`. Antigravity and Claude Desktop don't have a project-level config format.
|
|
27
|
+
|
|
18
28
|
## Flags
|
|
19
29
|
|
|
20
30
|
| Flag | Description |
|
|
21
31
|
|---|---|
|
|
22
|
-
| `--host=<url>` | Override the UpTurtle host. Defaults to `https://upturtle.com`. |
|
|
23
|
-
| `--
|
|
32
|
+
| `--host=<url>` | Override the UpTurtle host. Defaults to `https://what.upturtle.com`. Also `UPTURTLE_HOST=<url>`. |
|
|
33
|
+
| `--scope=<scope>` | `user` (default) or `project`. Shorthands: `--user`, `--project`. |
|
|
34
|
+
| `--agent=<id,...>` | Skip auto-detection and the prompt; install into specific agent(s). Comma-separated. |
|
|
35
|
+
| `--yes`, `-y` | Skip the prompt; install into every detected agent. Implied when stdin is not a TTY (e.g. CI). |
|
|
24
36
|
| `--help`, `-h` | Show usage. |
|
|
25
37
|
|
|
26
|
-
The host can also be supplied via the `UPTURTLE_HOST` environment variable.
|
|
27
|
-
|
|
28
38
|
## License
|
|
29
39
|
|
|
30
40
|
Proprietary.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@upturtle/wizard",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Connects your coding agent to UpTurtle's MCP server. Detects the agent, mints a scoped PAT via browser handshake, and writes the right MCP config.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -22,5 +22,5 @@
|
|
|
22
22
|
"publishConfig": {
|
|
23
23
|
"access": "public"
|
|
24
24
|
},
|
|
25
|
-
"homepage": "https://upturtle.com/dashboard/connect"
|
|
25
|
+
"homepage": "https://what.upturtle.com/dashboard/connect"
|
|
26
26
|
}
|
package/src/agents.mjs
CHANGED
|
@@ -3,42 +3,51 @@ import { homedir, platform } from "node:os";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { spawnSync } from "node:child_process";
|
|
5
5
|
|
|
6
|
+
// `scopes` lists the install scopes the agent's writer supports. Agents whose
|
|
7
|
+
// native config formats have no project-level alternative (Antigravity, Claude
|
|
8
|
+
// Desktop) are user-only.
|
|
6
9
|
export const AGENTS = [
|
|
7
10
|
{
|
|
8
11
|
id: "claude-code",
|
|
9
12
|
label: "Claude Code",
|
|
10
13
|
detect: () => isOnPath("claude"),
|
|
11
14
|
writer: "claude-code",
|
|
15
|
+
scopes: ["user", "project"],
|
|
12
16
|
},
|
|
13
17
|
{
|
|
14
18
|
id: "cursor",
|
|
15
19
|
label: "Cursor",
|
|
16
20
|
detect: () => existsSync(cursorAppDataDir()),
|
|
17
21
|
writer: "cursor",
|
|
22
|
+
scopes: ["user", "project"],
|
|
18
23
|
},
|
|
19
24
|
{
|
|
20
25
|
id: "antigravity",
|
|
21
26
|
label: "Google Antigravity",
|
|
22
27
|
detect: () => existsSync(antigravityConfigPath()),
|
|
23
28
|
writer: "antigravity",
|
|
29
|
+
scopes: ["user"],
|
|
24
30
|
},
|
|
25
31
|
{
|
|
26
32
|
id: "opencode",
|
|
27
33
|
label: "OpenCode",
|
|
28
34
|
detect: () => isOnPath("opencode") || existsSync(opencodeConfigPath()),
|
|
29
35
|
writer: "opencode",
|
|
36
|
+
scopes: ["user", "project"],
|
|
30
37
|
},
|
|
31
38
|
{
|
|
32
39
|
id: "zed",
|
|
33
40
|
label: "Zed",
|
|
34
41
|
detect: () => existsSync(zedConfigPath()) || existsSync("/Applications/Zed.app"),
|
|
35
42
|
writer: "zed",
|
|
43
|
+
scopes: ["user", "project"],
|
|
36
44
|
},
|
|
37
45
|
{
|
|
38
46
|
id: "claude-desktop",
|
|
39
47
|
label: "Claude Desktop",
|
|
40
48
|
detect: () => existsSync(claudeDesktopConfigPath()),
|
|
41
49
|
writer: "claude-desktop",
|
|
50
|
+
scopes: ["user"],
|
|
42
51
|
},
|
|
43
52
|
];
|
|
44
53
|
|
package/src/index.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { runDeviceFlow } from "./auth.mjs";
|
|
|
2
2
|
import { AGENTS, detectInstalledAgents } from "./agents.mjs";
|
|
3
3
|
import { WRITERS } from "./writers.mjs";
|
|
4
4
|
|
|
5
|
-
const DEFAULT_HOST = "https://upturtle.com";
|
|
5
|
+
const DEFAULT_HOST = "https://what.upturtle.com";
|
|
6
6
|
|
|
7
7
|
export async function run(argv) {
|
|
8
8
|
const flags = parseFlags(argv);
|
|
@@ -15,25 +15,43 @@ export async function run(argv) {
|
|
|
15
15
|
const log = console.log;
|
|
16
16
|
|
|
17
17
|
log("UpTurtle install");
|
|
18
|
-
log(" host:
|
|
18
|
+
log(" host: " + host);
|
|
19
|
+
log(" scope: " + flags.scope + (flags.scope === "project" ? ` (cwd: ${process.cwd()})` : ""));
|
|
19
20
|
|
|
20
21
|
const detected = detectInstalledAgents();
|
|
21
|
-
if (detected.length === 0) {
|
|
22
|
+
if (detected.length === 0 && !flags.agent) {
|
|
22
23
|
log("");
|
|
23
24
|
log("Couldn't detect any supported coding agents on this machine.");
|
|
24
25
|
log("Supported: " + AGENTS.map((a) => a.label).join(", "));
|
|
25
26
|
log("");
|
|
26
27
|
log("If your agent is installed but not detected, re-run with --agent=<id>:");
|
|
27
28
|
log(" " + AGENTS.map((a) => a.id).join(", "));
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
process.exitCode = 1;
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let targets;
|
|
34
|
+
try {
|
|
35
|
+
targets = await pickTargets(detected, flags);
|
|
36
|
+
} catch (err) {
|
|
37
|
+
log(err.message);
|
|
38
|
+
process.exitCode = 1;
|
|
39
|
+
return;
|
|
32
40
|
}
|
|
33
41
|
|
|
34
|
-
const targets = resolveTargets(detected, flags);
|
|
35
42
|
if (targets.length === 0) {
|
|
36
|
-
log("No
|
|
43
|
+
log("No agents selected. Exiting.");
|
|
44
|
+
process.exitCode = 1;
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Validate scope compatibility BEFORE minting a token — no point burning a
|
|
49
|
+
// PAT just to discover the chosen scope is unsupported.
|
|
50
|
+
const incompatible = targets.filter((a) => !a.scopes.includes(flags.scope));
|
|
51
|
+
if (incompatible.length > 0) {
|
|
52
|
+
log("");
|
|
53
|
+
log(`Scope "${flags.scope}" isn't supported by: ${incompatible.map((a) => a.label).join(", ")}`);
|
|
54
|
+
log("Re-run with --scope=user, or pick different agent(s) via --agent= or the prompt.");
|
|
37
55
|
process.exitCode = 1;
|
|
38
56
|
return;
|
|
39
57
|
}
|
|
@@ -55,7 +73,7 @@ export async function run(argv) {
|
|
|
55
73
|
continue;
|
|
56
74
|
}
|
|
57
75
|
try {
|
|
58
|
-
const out = writer({ host, secret });
|
|
76
|
+
const out = writer({ host, secret, scope: flags.scope });
|
|
59
77
|
log(` ✓ ${agent.label} → ${out.configPath}`);
|
|
60
78
|
results.push({ agent, ok: true });
|
|
61
79
|
} catch (err) {
|
|
@@ -79,12 +97,21 @@ function parseFlags(argv) {
|
|
|
79
97
|
const flags = {
|
|
80
98
|
host: process.env.UPTURTLE_HOST ?? DEFAULT_HOST,
|
|
81
99
|
agent: null,
|
|
100
|
+
scope: "user",
|
|
101
|
+
yes: false,
|
|
82
102
|
help: false,
|
|
83
103
|
};
|
|
84
104
|
for (const a of argv) {
|
|
85
105
|
if (a === "--help" || a === "-h") flags.help = true;
|
|
106
|
+
else if (a === "--yes" || a === "-y") flags.yes = true;
|
|
86
107
|
else if (a.startsWith("--host=")) flags.host = a.slice("--host=".length);
|
|
87
108
|
else if (a.startsWith("--agent=")) flags.agent = a.slice("--agent=".length);
|
|
109
|
+
else if (a.startsWith("--scope=")) flags.scope = a.slice("--scope=".length);
|
|
110
|
+
else if (a === "--project") flags.scope = "project";
|
|
111
|
+
else if (a === "--user") flags.scope = "user";
|
|
112
|
+
}
|
|
113
|
+
if (!["user", "project"].includes(flags.scope)) {
|
|
114
|
+
throw new Error(`--scope must be "user" or "project" (got "${flags.scope}")`);
|
|
88
115
|
}
|
|
89
116
|
flags.host = stripTrailingSlash(flags.host);
|
|
90
117
|
return flags;
|
|
@@ -94,19 +121,134 @@ function stripTrailingSlash(s) {
|
|
|
94
121
|
return s.endsWith("/") ? s.slice(0, -1) : s;
|
|
95
122
|
}
|
|
96
123
|
|
|
97
|
-
|
|
124
|
+
// Decides which agents to install for. Order of precedence:
|
|
125
|
+
// 1. Explicit --agent=id,id (skips prompt; honored even if not in detected)
|
|
126
|
+
// 2. Non-interactive (--yes or non-TTY): every detected agent
|
|
127
|
+
// 3. Interactive: prompt the user
|
|
128
|
+
async function pickTargets(detected, flags) {
|
|
98
129
|
if (flags.agent) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const known = AGENTS.map((a) => a.id).join(", ");
|
|
105
|
-
throw new Error(`Unknown --agent value. Known ids: ${known}`);
|
|
106
|
-
}
|
|
107
|
-
return matched;
|
|
130
|
+
return resolveExplicitAgents(flags.agent);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (flags.yes || !process.stdin.isTTY) {
|
|
134
|
+
return detected;
|
|
108
135
|
}
|
|
109
|
-
|
|
136
|
+
|
|
137
|
+
return await promptForAgents(detected);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function resolveExplicitAgents(value) {
|
|
141
|
+
const ids = value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
142
|
+
const matched = ids
|
|
143
|
+
.map((id) => AGENTS.find((a) => a.id === id))
|
|
144
|
+
.filter(Boolean);
|
|
145
|
+
if (matched.length !== ids.length) {
|
|
146
|
+
const known = AGENTS.map((a) => a.id).join(", ");
|
|
147
|
+
throw new Error(`Unknown --agent value. Known ids: ${known}`);
|
|
148
|
+
}
|
|
149
|
+
return matched;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Inquirer-style checkbox prompt. ↑/↓ moves the cursor, space toggles the
|
|
153
|
+
// current row, `a` toggles all, enter confirms, q or Ctrl-C aborts. Falls back
|
|
154
|
+
// to "install all detected" when the terminal doesn't support raw-mode input
|
|
155
|
+
// (rare; only relevant if some host fakes a TTY without keystroke capture).
|
|
156
|
+
async function promptForAgents(detected) {
|
|
157
|
+
const stdin = process.stdin;
|
|
158
|
+
const stdout = process.stdout;
|
|
159
|
+
|
|
160
|
+
if (typeof stdin.setRawMode !== "function") {
|
|
161
|
+
return detected;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return await new Promise((resolve, reject) => {
|
|
165
|
+
let cursor = 0;
|
|
166
|
+
const selected = new Set();
|
|
167
|
+
let linesWritten = 0;
|
|
168
|
+
|
|
169
|
+
const render = () => {
|
|
170
|
+
if (linesWritten > 0) {
|
|
171
|
+
// Move up to start of last render and clear from there to end of screen.
|
|
172
|
+
stdout.write(`\x1b[${linesWritten}A\x1b[0J`);
|
|
173
|
+
}
|
|
174
|
+
let buf = "";
|
|
175
|
+
buf += "Set up MCP for which agents?\n";
|
|
176
|
+
buf += " ↑/↓ move · space toggle · a toggle-all · enter confirm · q quit\n";
|
|
177
|
+
detected.forEach((agent, i) => {
|
|
178
|
+
const cursorMark = i === cursor ? "\x1b[36m❯\x1b[0m" : " ";
|
|
179
|
+
const checkbox = selected.has(i) ? "\x1b[32m[x]\x1b[0m" : "[ ]";
|
|
180
|
+
buf += ` ${cursorMark} ${checkbox} ${agent.label}\n`;
|
|
181
|
+
});
|
|
182
|
+
stdout.write(buf);
|
|
183
|
+
linesWritten = (buf.match(/\n/g) || []).length;
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const cleanup = () => {
|
|
187
|
+
try { stdin.setRawMode(false); } catch { /* ignore */ }
|
|
188
|
+
stdin.pause();
|
|
189
|
+
stdout.write("\x1b[?25h"); // restore cursor
|
|
190
|
+
stdin.removeListener("data", onData);
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const finalize = () => {
|
|
194
|
+
if (linesWritten > 0) {
|
|
195
|
+
stdout.write(`\x1b[${linesWritten}A\x1b[0J`);
|
|
196
|
+
}
|
|
197
|
+
const result = [...selected].sort((a, b) => a - b).map((i) => detected[i]);
|
|
198
|
+
stdout.write(
|
|
199
|
+
`Selected: ${result.length > 0 ? result.map((a) => a.label).join(", ") : "(none)"}\n`,
|
|
200
|
+
);
|
|
201
|
+
cleanup();
|
|
202
|
+
if (result.length === 0) {
|
|
203
|
+
reject(new Error("No agents selected. Aborted."));
|
|
204
|
+
} else {
|
|
205
|
+
resolve(result);
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const abort = (exitCode) => {
|
|
210
|
+
cleanup();
|
|
211
|
+
stdout.write("\n");
|
|
212
|
+
if (exitCode !== undefined) {
|
|
213
|
+
process.exit(exitCode);
|
|
214
|
+
}
|
|
215
|
+
reject(new Error("Aborted."));
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const onData = (key) => {
|
|
219
|
+
if (key === "\x03") return abort(130); // Ctrl-C
|
|
220
|
+
if (key === "q" || key === "Q") return abort();
|
|
221
|
+
if (key === "\x1b[A" || key === "k") { // ↑
|
|
222
|
+
cursor = (cursor - 1 + detected.length) % detected.length;
|
|
223
|
+
return render();
|
|
224
|
+
}
|
|
225
|
+
if (key === "\x1b[B" || key === "j") { // ↓
|
|
226
|
+
cursor = (cursor + 1) % detected.length;
|
|
227
|
+
return render();
|
|
228
|
+
}
|
|
229
|
+
if (key === " ") {
|
|
230
|
+
if (selected.has(cursor)) selected.delete(cursor);
|
|
231
|
+
else selected.add(cursor);
|
|
232
|
+
return render();
|
|
233
|
+
}
|
|
234
|
+
if (key === "a" || key === "A") {
|
|
235
|
+
if (selected.size === detected.length) {
|
|
236
|
+
selected.clear();
|
|
237
|
+
} else {
|
|
238
|
+
for (let i = 0; i < detected.length; i++) selected.add(i);
|
|
239
|
+
}
|
|
240
|
+
return render();
|
|
241
|
+
}
|
|
242
|
+
if (key === "\r" || key === "\n") return finalize();
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
stdout.write("\x1b[?25l"); // hide cursor
|
|
246
|
+
stdin.setRawMode(true);
|
|
247
|
+
stdin.resume();
|
|
248
|
+
stdin.setEncoding("utf8");
|
|
249
|
+
stdin.on("data", onData);
|
|
250
|
+
render();
|
|
251
|
+
});
|
|
110
252
|
}
|
|
111
253
|
|
|
112
254
|
function printHelp() {
|
|
@@ -115,10 +257,21 @@ function printHelp() {
|
|
|
115
257
|
npx @upturtle/wizard [options]
|
|
116
258
|
|
|
117
259
|
Options:
|
|
118
|
-
--host=<url> Override the UpTurtle host. Defaults to
|
|
260
|
+
--host=<url> Override the UpTurtle host. Defaults to ${DEFAULT_HOST}.
|
|
119
261
|
Also: UPTURTLE_HOST=<url>.
|
|
120
|
-
--
|
|
262
|
+
--scope=<scope> Where to write the MCP config:
|
|
263
|
+
user — your home dir; available in every project (default).
|
|
264
|
+
project — the current working directory; checked into git,
|
|
265
|
+
shareable across the team.
|
|
266
|
+
Shorthand flags: --user, --project.
|
|
267
|
+
--agent=<id,...> Skip auto-detection and the prompt; install into specific agent(s).
|
|
121
268
|
Known ids: ${AGENTS.map((a) => a.id).join(", ")}
|
|
269
|
+
--yes, -y Skip the agent prompt and install into every detected agent.
|
|
270
|
+
Implied when stdin is not a TTY (e.g. CI).
|
|
122
271
|
--help, -h Show this message.
|
|
272
|
+
|
|
273
|
+
Project scope is supported for: claude-code, cursor, opencode, zed.
|
|
274
|
+
Antigravity and Claude Desktop only support user scope (their config formats
|
|
275
|
+
don't have a project-level alternative).
|
|
123
276
|
`);
|
|
124
277
|
}
|
package/src/writers.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { mkdirSync, readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
2
|
-
import { dirname } from "node:path";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
3
|
import { spawnSync } from "node:child_process";
|
|
4
4
|
|
|
5
5
|
import {
|
|
@@ -20,26 +20,49 @@ export const WRITERS = {
|
|
|
20
20
|
"claude-desktop": writeClaudeDesktop,
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
-
function writeClaudeCode({ host, secret }) {
|
|
24
|
-
removeClaudeCodeServer();
|
|
23
|
+
function writeClaudeCode({ host, secret, scope }) {
|
|
24
|
+
removeClaudeCodeServer(scope);
|
|
25
25
|
const args = [
|
|
26
26
|
"mcp", "add", SERVER_KEY, host + "/mcp",
|
|
27
27
|
"--transport", "http",
|
|
28
28
|
"--header", `Authorization: Bearer ${secret}`,
|
|
29
29
|
];
|
|
30
|
+
if (scope === "project") args.push("--scope", "project");
|
|
30
31
|
const result = spawnSync("claude", args, { stdio: "pipe", encoding: "utf8" });
|
|
31
32
|
if (result.status !== 0) {
|
|
32
33
|
const detail = (result.stderr || result.stdout || "").trim();
|
|
33
34
|
throw new Error(`claude mcp add failed: ${detail || `exit ${result.status}`}`);
|
|
34
35
|
}
|
|
35
|
-
return {
|
|
36
|
+
return {
|
|
37
|
+
configPath: scope === "project"
|
|
38
|
+
? "./.mcp.json (managed by `claude mcp`)"
|
|
39
|
+
: "(managed by `claude mcp`)",
|
|
40
|
+
};
|
|
36
41
|
}
|
|
37
42
|
|
|
38
|
-
function removeClaudeCodeServer() {
|
|
39
|
-
|
|
43
|
+
function removeClaudeCodeServer(scope) {
|
|
44
|
+
const args = ["mcp", "remove", SERVER_KEY];
|
|
45
|
+
if (scope === "project") args.push("--scope", "project");
|
|
46
|
+
spawnSync("claude", args, { stdio: "ignore" });
|
|
40
47
|
}
|
|
41
48
|
|
|
42
|
-
function writeCursor({ host, secret }) {
|
|
49
|
+
function writeCursor({ host, secret, scope }) {
|
|
50
|
+
if (scope === "project") {
|
|
51
|
+
// Project: write .cursor/mcp.json directly. The deep link only handles
|
|
52
|
+
// user-scope; project config lives in the repo and is meant to be checked
|
|
53
|
+
// in.
|
|
54
|
+
const path = join(process.cwd(), ".cursor", "mcp.json");
|
|
55
|
+
return mergeJson(path, (cfg) => {
|
|
56
|
+
cfg.mcpServers ??= {};
|
|
57
|
+
cfg.mcpServers[SERVER_KEY] = {
|
|
58
|
+
url: host + "/mcp",
|
|
59
|
+
headers: { Authorization: `Bearer ${secret}` },
|
|
60
|
+
};
|
|
61
|
+
return cfg;
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// User scope: open the Cursor deep link.
|
|
43
66
|
const config = {
|
|
44
67
|
url: host + "/mcp",
|
|
45
68
|
headers: { Authorization: `Bearer ${secret}` },
|
|
@@ -59,7 +82,12 @@ function writeCursor({ host, secret }) {
|
|
|
59
82
|
return { configPath: "(installed via Cursor deep link)" };
|
|
60
83
|
}
|
|
61
84
|
|
|
62
|
-
function writeAntigravity({ host, secret }) {
|
|
85
|
+
function writeAntigravity({ host, secret, scope }) {
|
|
86
|
+
if (scope === "project") {
|
|
87
|
+
throw new Error(
|
|
88
|
+
"Antigravity doesn't have a project-scoped MCP config (only user-wide). " +
|
|
89
|
+
"Re-run without --scope=project for this agent.");
|
|
90
|
+
}
|
|
63
91
|
const path = antigravityConfigPath();
|
|
64
92
|
return mergeJson(path, (cfg) => {
|
|
65
93
|
cfg.mcpServers ??= {};
|
|
@@ -71,8 +99,10 @@ function writeAntigravity({ host, secret }) {
|
|
|
71
99
|
});
|
|
72
100
|
}
|
|
73
101
|
|
|
74
|
-
function writeOpenCode({ host, secret }) {
|
|
75
|
-
const path =
|
|
102
|
+
function writeOpenCode({ host, secret, scope }) {
|
|
103
|
+
const path = scope === "project"
|
|
104
|
+
? join(process.cwd(), "opencode.json")
|
|
105
|
+
: opencodeConfigPath();
|
|
76
106
|
return mergeJson(path, (cfg) => {
|
|
77
107
|
cfg.mcp ??= {};
|
|
78
108
|
cfg.mcp[SERVER_KEY] = {
|
|
@@ -84,8 +114,10 @@ function writeOpenCode({ host, secret }) {
|
|
|
84
114
|
});
|
|
85
115
|
}
|
|
86
116
|
|
|
87
|
-
function writeZed({ host, secret }) {
|
|
88
|
-
const path =
|
|
117
|
+
function writeZed({ host, secret, scope }) {
|
|
118
|
+
const path = scope === "project"
|
|
119
|
+
? join(process.cwd(), ".zed", "settings.json")
|
|
120
|
+
: zedConfigPath();
|
|
89
121
|
return mergeJson(path, (cfg) => {
|
|
90
122
|
cfg.context_servers ??= {};
|
|
91
123
|
cfg.context_servers[SERVER_KEY] = {
|
|
@@ -103,7 +135,12 @@ function writeZed({ host, secret }) {
|
|
|
103
135
|
});
|
|
104
136
|
}
|
|
105
137
|
|
|
106
|
-
function writeClaudeDesktop({ host, secret }) {
|
|
138
|
+
function writeClaudeDesktop({ host, secret, scope }) {
|
|
139
|
+
if (scope === "project") {
|
|
140
|
+
throw new Error(
|
|
141
|
+
"Claude Desktop has only one global config file — there's no project " +
|
|
142
|
+
"scope. Re-run without --scope=project for this agent.");
|
|
143
|
+
}
|
|
107
144
|
const path = claudeDesktopConfigPath();
|
|
108
145
|
return mergeJson(path, (cfg) => {
|
|
109
146
|
cfg.mcpServers ??= {};
|