sentinel-ir-mcp 0.2.2 → 0.2.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.
- package/README.md +21 -6
- package/package.json +1 -1
- package/src/clients.js +60 -8
- package/src/doctor.js +18 -6
- package/src/setup.js +34 -9
package/README.md
CHANGED
|
@@ -90,10 +90,16 @@ npx -y sentinel-ir-mcp@0.2 guide # optional: text for AGENTS
|
|
|
90
90
|
| `--client` | file written by `setup` |
|
|
91
91
|
|---|---|
|
|
92
92
|
| `cursor` | `<project>/.cursor/mcp.json` with `${workspaceFolder}`; `--global`: `~/.cursor/mcp.json` with the absolute path |
|
|
93
|
-
| `windsurf` | `~/.codeium/windsurf/mcp_config.json` |
|
|
93
|
+
| `windsurf` | `~/.config/devin/mcp_config.json` (or `$XDG_CONFIG_HOME/devin/`), Windows `%APPDATA%\devin\mcp_config.json`. Servers from an older `~/.codeium/windsurf/mcp_config.json` are copied over; that file stays unchanged |
|
|
94
94
|
| `claude-desktop` | macOS `~/Library/Application Support/Claude/claude_desktop_config.json`, Windows `%APPDATA%\Claude\claude_desktop_config.json`; elsewhere pass `--config <file>` |
|
|
95
95
|
| `vscode`, `claude-code` | nothing; prints the command to run |
|
|
96
96
|
|
|
97
|
+
On Windows the entry is `"command": "cmd", "args": ["/c", "npx", "-y",
|
|
98
|
+
"sentinel-ir-mcp@0.2", "<folder>"]`: there `npx` is `npx.cmd`, and starting it
|
|
99
|
+
without a shell failed with `ENOENT` on a real Windows runner. For VS Code and
|
|
100
|
+
Claude Code, `setup` prints a PowerShell and a Command Prompt version of the
|
|
101
|
+
command. What was measured on which system: [COMPATIBILITY.md](COMPATIBILITY.md).
|
|
102
|
+
|
|
97
103
|
`doctor` checks Node.js, the package version, the client config (valid JSON, a
|
|
98
104
|
`"sentinel-ir"` entry that starts `sentinel-ir-mcp`, existing folders), then
|
|
99
105
|
starts **its own** server with the same folders, performs a real MCP
|
|
@@ -161,13 +167,16 @@ Project `.cursor/mcp.json` (or `~/.cursor/mcp.json` for all projects).
|
|
|
161
167
|
|
|
162
168
|
#### Windsurf
|
|
163
169
|
|
|
164
|
-
|
|
165
|
-
|
|
170
|
+
Windsurf 3 (Devin Desktop) reads `~/.config/devin/mcp_config.json`
|
|
171
|
+
(Windows: `%APPDATA%\devin\mcp_config.json`); older versions read
|
|
172
|
+
`~/.codeium/windsurf/mcp_config.json`. Add the same entry as for Claude Desktop
|
|
173
|
+
with your project's absolute path.
|
|
166
174
|
|
|
167
175
|
#### Other clients
|
|
168
176
|
|
|
169
177
|
Any MCP client that starts stdio servers works with
|
|
170
|
-
`command: "npx"`, `args: ["-y", "sentinel-ir-mcp@0.2", "<folder>"]
|
|
178
|
+
`command: "npx"`, `args: ["-y", "sentinel-ir-mcp@0.2", "<folder>"]`
|
|
179
|
+
(on Windows: `command: "cmd"`, `args: ["/c", "npx", "-y", "sentinel-ir-mcp@0.2", "<folder>"]`).
|
|
171
180
|
Without folder arguments the server uses the client's
|
|
172
181
|
[MCP roots](https://modelcontextprotocol.io/specification/latest/client/roots)
|
|
173
182
|
if the client offers them; if it offers none, only `analyze_code` works.
|
|
@@ -244,8 +253,14 @@ size limits, MCP roots from the client, and the command-line options.
|
|
|
244
253
|
`setup`/`doctor` tests run against temporary `HOME`/`APPDATA` folders: new
|
|
245
254
|
file, keeping other servers and fields, backup, idempotent second run, invalid
|
|
246
255
|
JSON left untouched, `--dry-run`, refused home/root folders, symlinked config,
|
|
247
|
-
Claude Desktop macOS/Windows paths (simulated,
|
|
248
|
-
|
|
256
|
+
Claude Desktop/Windsurf macOS/Windows paths (simulated), a real MCP
|
|
257
|
+
handshake, failing configs and old Node.js.
|
|
258
|
+
|
|
259
|
+
A second CI job runs on a real Windows runner (Node 20/22/24): the full test
|
|
260
|
+
suite, the packed package with `setup`/`doctor` against the real `%APPDATA%`,
|
|
261
|
+
starting the published package through `npx`, and the printed VS Code/Claude
|
|
262
|
+
Code commands pasted into PowerShell 5.1, PowerShell 7 and cmd.exe. See
|
|
263
|
+
[COMPATIBILITY.md](COMPATIBILITY.md).
|
|
249
264
|
|
|
250
265
|
Release: bump `version` in `package.json`, push a protected tag `vX.Y.Z`;
|
|
251
266
|
GitLab CI tests and publishes to npm with provenance.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sentinel-ir-mcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "MCP server for sentinel-ir: lets AI agents (Claude Desktop, Cursor, Windsurf, ...) read a compact structural IR of JavaScript and TypeScript files instead of the raw source. Read-only, sandboxed to allowed folders.",
|
|
5
5
|
"main": "src/server.js",
|
|
6
6
|
"bin": {
|
package/src/clients.js
CHANGED
|
@@ -11,6 +11,10 @@ function homeOf(env) {
|
|
|
11
11
|
return env.HOME || env.USERPROFILE || os.homedir();
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
function configHome(env) {
|
|
15
|
+
return env.XDG_CONFIG_HOME || path.join(homeOf(env), ".config");
|
|
16
|
+
}
|
|
17
|
+
|
|
14
18
|
// Clients whose MCP config file is documented as JSON with a top-level
|
|
15
19
|
// "mcpServers" object ("file"), and clients that ship their own install
|
|
16
20
|
// command ("command"), for which setup only prints that command.
|
|
@@ -27,7 +31,12 @@ const CLIENTS = Object.freeze({
|
|
|
27
31
|
windsurf: {
|
|
28
32
|
label: "Windsurf",
|
|
29
33
|
kind: "file",
|
|
30
|
-
|
|
34
|
+
// Windsurf 3 (Devin Desktop) reads this file and offers to copy the
|
|
35
|
+
// old ~/.codeium/windsurf one; setup copies it the same way.
|
|
36
|
+
configPath: ({ env, platform }) => platform === "win32"
|
|
37
|
+
? (env.APPDATA ? path.join(env.APPDATA, "devin", "mcp_config.json") : null)
|
|
38
|
+
: path.join(configHome(env), "devin", "mcp_config.json"),
|
|
39
|
+
legacyConfigPath: ({ env }) => path.join(homeOf(env), ".codeium", "windsurf", "mcp_config.json")
|
|
31
40
|
},
|
|
32
41
|
"claude-desktop": {
|
|
33
42
|
label: "Claude Desktop",
|
|
@@ -47,22 +56,65 @@ const CLIENTS = Object.freeze({
|
|
|
47
56
|
const CLIENT_NAMES = Object.freeze(Object.keys(CLIENTS));
|
|
48
57
|
const FILE_CLIENTS = Object.freeze(CLIENT_NAMES.filter((name) => CLIENTS[name].kind === "file"));
|
|
49
58
|
|
|
50
|
-
function
|
|
59
|
+
function npxEntry(folder) {
|
|
51
60
|
return { command: "npx", args: ["-y", PACKAGE_SPEC, folder] };
|
|
52
61
|
}
|
|
53
62
|
|
|
63
|
+
// On Windows npx is npx.cmd, which a client cannot start without a shell
|
|
64
|
+
// (measured: spawn("npx") fails with ENOENT), so the entry goes through cmd /c.
|
|
65
|
+
function serverEntry(folder, platform = process.platform) {
|
|
66
|
+
const { command, args } = npxEntry(folder);
|
|
67
|
+
return platform === "win32" ? { command: "cmd", args: ["/c", command, ...args] } : { command, args };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function isWindowsNpxWrapper(entry) {
|
|
71
|
+
return Boolean(entry) && typeof entry.command === "string" && /^cmd(\.exe)?$/i.test(entry.command) &&
|
|
72
|
+
Array.isArray(entry.args) && String(entry.args[0]).toLowerCase() === "/c" && /^npx(\.cmd)?$/i.test(String(entry.args[1]));
|
|
73
|
+
}
|
|
74
|
+
|
|
54
75
|
function shellQuote(value) {
|
|
55
76
|
return /^[\w@%+=:,./-]+$/.test(value) ? value : `'${value.replace(/'/g, "'\\''")}'`;
|
|
56
77
|
}
|
|
57
78
|
|
|
79
|
+
function powershellQuote(value) {
|
|
80
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// One argument quoted by the Windows command-line rules (CommandLineToArgvW).
|
|
84
|
+
function windowsArgQuote(value) {
|
|
85
|
+
return `"${value.replace(/(\\*)"/g, '$1$1\\"').replace(/(\\*)$/, "$1$1")}"`;
|
|
86
|
+
}
|
|
87
|
+
|
|
58
88
|
function vscodeConfig(project) {
|
|
59
|
-
return { name: SERVER_NAME, type: "stdio", ...
|
|
89
|
+
return { name: SERVER_NAME, type: "stdio", ...npxEntry(project) };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// code.cmd/claude.cmd re-read their arguments through cmd.exe, which acts on
|
|
93
|
+
// these characters (measured: "&" breaks the line, "%OS%" is expanded); JSON
|
|
94
|
+
// \u escapes keep them out of the command line.
|
|
95
|
+
function cmdSafeJson(value) {
|
|
96
|
+
return JSON.stringify(value).replace(/[&|<>^%!]/g, (c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, "0")}`);
|
|
60
97
|
}
|
|
61
98
|
|
|
62
|
-
|
|
63
|
-
|
|
99
|
+
// Windows forms were checked in CI against a .cmd shim (like code.cmd and
|
|
100
|
+
// claude.cmd) from PowerShell 5.1, PowerShell 7 and cmd.exe.
|
|
101
|
+
function installCommand(client, project, platform = process.platform, shell = "powershell") {
|
|
102
|
+
const json = JSON.stringify(vscodeConfig(project));
|
|
103
|
+
const win = platform === "win32";
|
|
104
|
+
if (client === "vscode") {
|
|
105
|
+
if (!win) return `code --add-mcp ${shellQuote(json)}`;
|
|
106
|
+
return `code ${shell === "cmd" ? "" : "--% "}--add-mcp ${windowsArgQuote(cmdSafeJson(vscodeConfig(project)))}`;
|
|
107
|
+
}
|
|
64
108
|
if (client === "claude-code") {
|
|
65
|
-
|
|
109
|
+
if (win && shell !== "cmd") {
|
|
110
|
+
// PowerShell prefers npm's claude.ps1 shim: a script gets `--` only when quoted, and --% does not apply to it.
|
|
111
|
+
return `claude mcp add --transport stdio ${SERVER_NAME} '--' cmd /c npx -y ${PACKAGE_SPEC} ${powershellQuote(project)}`;
|
|
112
|
+
}
|
|
113
|
+
if (win && project.includes("%")) {
|
|
114
|
+
const config = { type: "stdio", ...serverEntry(project, "win32") };
|
|
115
|
+
return `claude mcp add-json ${SERVER_NAME} ${windowsArgQuote(cmdSafeJson(config))}`;
|
|
116
|
+
}
|
|
117
|
+
return `claude mcp add --transport stdio ${SERVER_NAME} -- ${win ? "cmd /c npx" : "npx"} -y ${PACKAGE_SPEC} ${win ? `"${project}"` : shellQuote(project)}`;
|
|
66
118
|
}
|
|
67
119
|
throw new Error(`No install command for ${client}`);
|
|
68
120
|
}
|
|
@@ -70,7 +122,7 @@ function installCommand(client, project) {
|
|
|
70
122
|
// One-click links documented by Cursor and VS Code. A link cannot know the
|
|
71
123
|
// project path, so it uses ${workspaceFolder}.
|
|
72
124
|
function installLinks() {
|
|
73
|
-
const entry =
|
|
125
|
+
const entry = npxEntry(WORKSPACE_VAR);
|
|
74
126
|
return {
|
|
75
127
|
cursor: `cursor://anysphere.cursor-deeplink/mcp/install?name=${SERVER_NAME}&config=${Buffer.from(JSON.stringify(entry)).toString("base64")}`,
|
|
76
128
|
vscode: `vscode:mcp/install?${encodeURIComponent(JSON.stringify({ name: SERVER_NAME, type: "stdio", ...entry }))}`
|
|
@@ -79,5 +131,5 @@ function installLinks() {
|
|
|
79
131
|
|
|
80
132
|
module.exports = {
|
|
81
133
|
SERVER_NAME, PACKAGE_SPEC, WORKSPACE_VAR, CLIENTS, CLIENT_NAMES, FILE_CLIENTS,
|
|
82
|
-
homeOf, serverEntry, shellQuote, installCommand, installLinks
|
|
134
|
+
homeOf, npxEntry, serverEntry, isWindowsNpxWrapper, shellQuote, powershellQuote, installCommand, installLinks
|
|
83
135
|
};
|
package/src/doctor.js
CHANGED
|
@@ -5,7 +5,7 @@ const path = require("path");
|
|
|
5
5
|
const { Client } = require("@modelcontextprotocol/sdk/client/index.js");
|
|
6
6
|
const { StdioClientTransport } = require("@modelcontextprotocol/sdk/client/stdio.js");
|
|
7
7
|
const { version } = require("../package.json");
|
|
8
|
-
const { SERVER_NAME, PACKAGE_SPEC, WORKSPACE_VAR, CLIENTS, FILE_CLIENTS } = require("./clients");
|
|
8
|
+
const { SERVER_NAME, PACKAGE_SPEC, WORKSPACE_VAR, CLIENTS, FILE_CLIENTS, isWindowsNpxWrapper } = require("./clients");
|
|
9
9
|
const { readConfig } = require("./setup");
|
|
10
10
|
|
|
11
11
|
const BIN = path.join(__dirname, "..", "bin", "sentinel-ir-mcp.js");
|
|
@@ -33,7 +33,7 @@ function entryFolders(args) {
|
|
|
33
33
|
return folders;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
function checkConfig(client, file, explicit, project, add) {
|
|
36
|
+
function checkConfig(client, file, explicit, project, add, platform) {
|
|
37
37
|
const label = CLIENTS[client].label;
|
|
38
38
|
if (!file) {
|
|
39
39
|
if (explicit) add("fail", `${client}.config`, `${label}: config location unknown on this system`, "Pass the config path with --config <file>.");
|
|
@@ -64,8 +64,14 @@ function checkConfig(client, file, explicit, project, add) {
|
|
|
64
64
|
`Run: npx -y ${PACKAGE_SPEC} setup --client ${client}`);
|
|
65
65
|
return null;
|
|
66
66
|
}
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
const runs = `${label}: "${SERVER_NAME}" runs ${entry.command} ${args.join(" ")}`;
|
|
68
|
+
if (entry.command === "npx" && platform === "win32") {
|
|
69
|
+
add("warn", `${client}.entry`, runs, `On Windows a client may not find "npx" without a shell. Run: npx -y ${PACKAGE_SPEC} setup --client ${client}`);
|
|
70
|
+
} else if (entry.command === "npx" || isWindowsNpxWrapper(entry)) {
|
|
71
|
+
add("ok", `${client}.entry`, runs);
|
|
72
|
+
} else {
|
|
73
|
+
add("warn", `${client}.entry`, runs, `Expected "npx"; make sure "${entry.command}" starts sentinel-ir-mcp.`);
|
|
74
|
+
}
|
|
69
75
|
const resolved = [];
|
|
70
76
|
for (const f of folders) {
|
|
71
77
|
const real = f === WORKSPACE_VAR ? project : path.resolve(f);
|
|
@@ -130,8 +136,14 @@ async function runDoctor(opts, io) {
|
|
|
130
136
|
let folders = null;
|
|
131
137
|
for (const client of names) {
|
|
132
138
|
const ctx = { project, global: Boolean(opts.global), env: io.env, platform: io.platform };
|
|
133
|
-
|
|
134
|
-
const
|
|
139
|
+
let file = opts.config ? path.resolve(io.cwd, opts.config) : CLIENTS[client].configPath(ctx);
|
|
140
|
+
const legacy = !opts.config && CLIENTS[client].legacyConfigPath ? CLIENTS[client].legacyConfigPath(ctx) : null;
|
|
141
|
+
if (legacy && !(file && fs.existsSync(file)) && fs.existsSync(legacy)) {
|
|
142
|
+
add("warn", `${client}.location`, `${CLIENTS[client].label}: only the old config ${legacy} exists`,
|
|
143
|
+
`Current ${CLIENTS[client].label} reads ${file || "a new location"}. Run: npx -y ${PACKAGE_SPEC} setup --client ${client}`);
|
|
144
|
+
file = legacy;
|
|
145
|
+
}
|
|
146
|
+
const found = checkConfig(client, file, Boolean(opts.client), project, add, io.platform);
|
|
135
147
|
if (found && folders === null) folders = found;
|
|
136
148
|
}
|
|
137
149
|
if (folders === null) {
|
package/src/setup.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const fs = require("fs");
|
|
4
4
|
const path = require("path");
|
|
5
5
|
const { isDeepStrictEqual } = require("util");
|
|
6
|
-
const { SERVER_NAME, PACKAGE_SPEC, CLIENTS, homeOf, serverEntry, installCommand } = require("./clients");
|
|
6
|
+
const { SERVER_NAME, PACKAGE_SPEC, CLIENTS, homeOf, serverEntry, installCommand, shellQuote, powershellQuote } = require("./clients");
|
|
7
7
|
|
|
8
8
|
class SetupError extends Error {}
|
|
9
9
|
|
|
@@ -57,13 +57,17 @@ function readConfig(file) {
|
|
|
57
57
|
return { exists: true, original, data };
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
// seedFile: an older config location whose servers are copied into a new file.
|
|
61
|
+
function planFileChange(file, entry, seedFile = null) {
|
|
61
62
|
const target = fs.existsSync(file) ? fs.realpathSync(file) : file;
|
|
62
|
-
const
|
|
63
|
+
const read = readConfig(target);
|
|
64
|
+
const seeded = !read.exists && seedFile && fs.existsSync(seedFile) ? seedFile : null;
|
|
65
|
+
const { exists, original } = read;
|
|
66
|
+
const data = seeded ? readConfig(seeded).data : read.data;
|
|
63
67
|
const servers = data.mcpServers || {};
|
|
64
68
|
const previous = Object.prototype.hasOwnProperty.call(servers, SERVER_NAME) ? servers[SERVER_NAME] : undefined;
|
|
65
69
|
const others = Object.keys(servers).filter((name) => name !== SERVER_NAME);
|
|
66
|
-
const base = { file, target, entry, previous, others };
|
|
70
|
+
const base = { file, target, entry, previous, others, seeded };
|
|
67
71
|
if (previous !== undefined && isDeepStrictEqual(previous, entry)) return { ...base, action: "unchanged" };
|
|
68
72
|
const next = { ...data, mcpServers: { ...servers, [SERVER_NAME]: entry } };
|
|
69
73
|
return {
|
|
@@ -112,12 +116,25 @@ function describePlan(client, plan, project) {
|
|
|
112
116
|
`Access: ${project} (only this folder, read-only)`,
|
|
113
117
|
`Change: ${{ create: "create the file with", add: "add", update: "replace", unchanged: "none, already set up:" }[plan.action]} "${SERVER_NAME}"`
|
|
114
118
|
];
|
|
119
|
+
if (plan.seeded) lines.push(`Copied from: ${plan.seeded} (old location; that file stays unchanged)`);
|
|
115
120
|
if (plan.action === "update") lines.push(` before: ${JSON.stringify(plan.previous)}`);
|
|
116
121
|
lines.push(` ${plan.action === "update" ? "after: " : ""}${JSON.stringify(plan.entry)}`);
|
|
117
122
|
if (plan.others.length) lines.push(`Kept unchanged: ${plan.others.join(", ")}`);
|
|
118
123
|
return lines.join("\n") + "\n";
|
|
119
124
|
}
|
|
120
125
|
|
|
126
|
+
function percentNote(project) {
|
|
127
|
+
return project.includes("%")
|
|
128
|
+
? "Note: this folder path contains %. npm 10.8 (measured: 10.8.2, 10.8.3) fails with \"npm error URI malformed\" " +
|
|
129
|
+
"when npx is started inside such a folder; npm 10.9.9 and 11.19.1 work. If the client reports that error, update npm " +
|
|
130
|
+
"or use a folder without % in its path.\n"
|
|
131
|
+
: "";
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function hintArg(value, platform) {
|
|
135
|
+
return platform === "win32" ? powershellQuote(value) : shellQuote(value);
|
|
136
|
+
}
|
|
137
|
+
|
|
121
138
|
async function runSetup(opts, io) {
|
|
122
139
|
const out = (s) => io.stdout.write(s);
|
|
123
140
|
const err = (s) => io.stderr.write(s);
|
|
@@ -140,9 +157,15 @@ async function runSetup(opts, io) {
|
|
|
140
157
|
|
|
141
158
|
if (spec.kind === "command") {
|
|
142
159
|
if (opts.config) throw new SetupError(`--config does not apply to ${spec.label}; it installs servers with its own command.`);
|
|
143
|
-
|
|
160
|
+
if (io.platform === "win32") {
|
|
161
|
+
out(`${spec.label} adds MCP servers with its own command. Nothing was written. Run in PowerShell:\n\n ${installCommand(client, project, "win32", "powershell")}\n\n` +
|
|
162
|
+
`or in Command Prompt (cmd.exe):\n\n ${installCommand(client, project, "win32", "cmd")}\n\n`);
|
|
163
|
+
} else {
|
|
164
|
+
out(`${spec.label} adds MCP servers with its own command. Nothing was written. Run:\n\n ${installCommand(client, project, io.platform)}\n\n`);
|
|
165
|
+
}
|
|
144
166
|
out(`Access: ${project} (only this folder, read-only)\n`);
|
|
145
|
-
out(`Then check the connection: npx -y ${PACKAGE_SPEC} doctor --project ${project}\n`);
|
|
167
|
+
out(`Then check the connection${io.platform === "win32" ? " (PowerShell)" : ""}: npx -y ${PACKAGE_SPEC} doctor --client ${client} --project ${hintArg(project, io.platform)}\n`);
|
|
168
|
+
out(percentNote(project));
|
|
146
169
|
return 0;
|
|
147
170
|
}
|
|
148
171
|
|
|
@@ -152,8 +175,10 @@ async function runSetup(opts, io) {
|
|
|
152
175
|
throw new SetupError(`The ${spec.label} config location is documented for macOS and Windows only. Pass its path with --config <file>.`);
|
|
153
176
|
}
|
|
154
177
|
const folder = opts.config || !spec.folderArg ? project : spec.folderArg(ctx);
|
|
155
|
-
const
|
|
178
|
+
const seed = !opts.config && spec.legacyConfigPath ? spec.legacyConfigPath(ctx) : null;
|
|
179
|
+
const plan = planFileChange(file, serverEntry(folder, io.platform), seed);
|
|
156
180
|
out(describePlan(client, plan, project));
|
|
181
|
+
out(percentNote(project));
|
|
157
182
|
|
|
158
183
|
if (plan.action === "unchanged") {
|
|
159
184
|
out("Already set up. Nothing was written.\n");
|
|
@@ -176,8 +201,8 @@ async function runSetup(opts, io) {
|
|
|
176
201
|
}
|
|
177
202
|
const { backup } = applyPlan(plan, io.now ? io.now() : Date.now());
|
|
178
203
|
out(`Written.${backup ? ` Backup: ${backup}` : ""}\n`);
|
|
179
|
-
out(`Next: restart ${spec.label}, then check the connection:\n npx -y ${PACKAGE_SPEC} doctor --client ${client}` +
|
|
180
|
-
`${opts.config ? ` --config ${file}` : ""}${client === "cursor" && !opts.global ? ` --project ${project}` : ""}\n`);
|
|
204
|
+
out(`Next: restart ${spec.label}, then check the connection${io.platform === "win32" ? " (PowerShell)" : ""}:\n npx -y ${PACKAGE_SPEC} doctor --client ${client}` +
|
|
205
|
+
`${opts.config ? ` --config ${hintArg(file, io.platform)}` : ""}${client === "cursor" && !opts.global ? ` --project ${hintArg(project, io.platform)}` : ""}\n`);
|
|
181
206
|
out(`Optional instructions for your agent: npx -y ${PACKAGE_SPEC} guide\n`);
|
|
182
207
|
return 0;
|
|
183
208
|
} catch (e) {
|