multicorn-shield 1.4.1 → 1.7.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.
@@ -6,6 +6,7 @@ import { homedir } from 'os';
6
6
  import { fileURLToPath } from 'url';
7
7
  import { createRequire } from 'module';
8
8
  import { createInterface } from 'readline';
9
+ import { parse, stringify } from 'yaml';
9
10
  import { spawn } from 'child_process';
10
11
  import { createHash } from 'crypto';
11
12
  import 'stream';
@@ -1360,9 +1361,6 @@ function getClineMcpSettingsPath() {
1360
1361
  );
1361
1362
  }
1362
1363
  }
1363
- function getContinueConfigJsonPath() {
1364
- return join(homedir(), ".continue", "config.json");
1365
- }
1366
1364
  var INIT_WIZARD_PLATFORM_REGISTRY = [
1367
1365
  { slug: "openclaw", displayName: "OpenClaw", section: "native" },
1368
1366
  { slug: "claude-code", displayName: "Claude Code", section: "native" },
@@ -1483,6 +1481,12 @@ async function promptWindsurfIntegrationMode(ask) {
1483
1481
  return choice === 1 ? "native" : "hosted";
1484
1482
  }
1485
1483
  async function arrowSelect(options, ask, fallbackLabel) {
1484
+ if (options.length === 1) {
1485
+ const only = options[0] ?? "";
1486
+ process.stderr.write(`${style.violet("\u276F")} ${style.cyan(only)}
1487
+ `);
1488
+ return 0;
1489
+ }
1486
1490
  const canRaw = process.stdin.isTTY && typeof process.stdin.setRawMode === "function";
1487
1491
  if (!canRaw) {
1488
1492
  for (let i = 0; i < options.length; i++) {
@@ -1535,7 +1539,7 @@ async function arrowSelect(options, ask, fallbackLabel) {
1535
1539
  cleanup();
1536
1540
  clearLines();
1537
1541
  const chosen = options.at(idx);
1538
- if (chosen !== void 0) {
1542
+ if (chosen !== void 0 && options.length > 1) {
1539
1543
  process.stderr.write(`${style.violet("\u276F")} ${style.cyan(chosen)}
1540
1544
  `);
1541
1545
  }
@@ -1724,13 +1728,56 @@ function writeMcpAddedLine(shortName, filePath) {
1724
1728
  style.green("\u2713") + ' MCP server "' + shortName + '" added to ' + style.cyan(filePath) + "\n"
1725
1729
  );
1726
1730
  }
1727
- async function mergeMcpServersObjectStyle(filePath, shortName, entry) {
1731
+ function sanitiseYamlValue(value) {
1732
+ if (value.length === 0) {
1733
+ return "''";
1734
+ }
1735
+ const needsQuoting = /[:#\n{}]/.test(value) || value.includes("[") || value.includes("]") || value !== value.trim() || value.includes("'");
1736
+ if (!needsQuoting) {
1737
+ return value;
1738
+ }
1739
+ return `'${value.replace(/'/g, "''")}'`;
1740
+ }
1741
+ function gitignoreLikelyCoversPath(relPosixPath, gitignoreBody) {
1742
+ const norm = relPosixPath.replace(/\\/g, "/").replace(/^\.\//, "");
1743
+ const lines = gitignoreBody.split(/\r?\n/);
1744
+ for (const raw of lines) {
1745
+ const line = raw.trim();
1746
+ if (!line || line.startsWith("#") || line.startsWith("!")) continue;
1747
+ const pat = line.replace(/^\//, "");
1748
+ if (pat === norm || pat === `./${norm}`) return true;
1749
+ if (!pat.includes("*")) {
1750
+ if (pat.endsWith("/")) {
1751
+ const dir = pat.slice(0, -1);
1752
+ if (norm === dir || norm.startsWith(`${dir}/`)) return true;
1753
+ }
1754
+ }
1755
+ }
1756
+ return false;
1757
+ }
1758
+ async function warnIfApiKeyFileNotGitignored(workspaceRoot, relativePosixPath) {
1759
+ const gitignorePath = join(workspaceRoot, ".gitignore");
1760
+ let content;
1761
+ try {
1762
+ content = await readFile(gitignorePath, "utf8");
1763
+ } catch (e) {
1764
+ if (isErrnoException(e) && e.code === "ENOENT") return;
1765
+ throw e;
1766
+ }
1767
+ const norm = relativePosixPath.replace(/\\/g, "/").replace(/^\.\//, "");
1768
+ if (gitignoreLikelyCoversPath(norm, content)) return;
1769
+ process.stderr.write(
1770
+ style.yellow("\u26A0") + " Config contains your API key. Add " + style.cyan(norm) + " to .gitignore to avoid committing credentials.\n"
1771
+ );
1772
+ }
1773
+ async function mergeTopLevelKeyedJsonFile(filePath, topLevelKey, shortName, entry, options) {
1728
1774
  let root = {};
1729
1775
  try {
1730
1776
  const raw = await readFile(filePath, "utf8");
1777
+ const toParse = options.stripJsonComments ? raw.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "") : raw;
1731
1778
  let parsed;
1732
1779
  try {
1733
- parsed = JSON.parse(raw);
1780
+ parsed = JSON.parse(toParse);
1734
1781
  } catch {
1735
1782
  return "parse-error";
1736
1783
  }
@@ -1746,15 +1793,25 @@ async function mergeMcpServersObjectStyle(filePath, shortName, entry) {
1746
1793
  throw e;
1747
1794
  }
1748
1795
  }
1749
- const mcpRaw = root["mcpServers"];
1750
- const mcpServers = typeof mcpRaw === "object" && mcpRaw !== null && !Array.isArray(mcpRaw) ? { ...mcpRaw } : {};
1751
- mcpServers[shortName] = entry;
1752
- root["mcpServers"] = mcpServers;
1796
+ const bucketRaw = root[topLevelKey];
1797
+ const bucket = typeof bucketRaw === "object" && bucketRaw !== null && !Array.isArray(bucketRaw) ? { ...bucketRaw } : {};
1798
+ if (options.onExisting === "skip" && bucket[shortName] !== void 0) {
1799
+ return "unchanged";
1800
+ }
1801
+ bucket[shortName] = entry;
1802
+ root[topLevelKey] = bucket;
1753
1803
  await mkdir(dirname(filePath), { recursive: true });
1754
1804
  await writeFile(filePath, JSON.stringify(root, null, 2) + "\n", SECRET_JSON_FILE_OPTIONS);
1755
1805
  writeMcpAddedLine(shortName, filePath);
1756
1806
  return "ok";
1757
1807
  }
1808
+ async function mergeMcpServersObjectStyle(filePath, shortName, entry) {
1809
+ const result = await mergeTopLevelKeyedJsonFile(filePath, "mcpServers", shortName, entry, {
1810
+ stripJsonComments: false,
1811
+ onExisting: "overwrite"
1812
+ });
1813
+ return result === "parse-error" ? "parse-error" : "ok";
1814
+ }
1758
1815
  async function mergeClaudeDesktopHostedMcpRemote(shortName, proxyUrl, apiKey) {
1759
1816
  const entry = {
1760
1817
  command: "npx",
@@ -1762,8 +1819,30 @@ async function mergeClaudeDesktopHostedMcpRemote(shortName, proxyUrl, apiKey) {
1762
1819
  };
1763
1820
  return mergeMcpServersObjectStyle(getClaudeDesktopConfigPath(), shortName, entry);
1764
1821
  }
1765
- async function mergeContinueHostedMcp(shortName, proxyUrl, apiKey) {
1766
- const filePath = getContinueConfigJsonPath();
1822
+ async function mergeContinueHostedMcp(workspacePath, shortName, proxyUrl, apiKey) {
1823
+ const dir = join(workspacePath, ".continue", "mcpServers");
1824
+ const filePath = join(dir, `${shortName}.yaml`);
1825
+ const sn = sanitiseYamlValue(shortName);
1826
+ const urlEsc = sanitiseYamlValue(proxyUrl);
1827
+ const authEsc = sanitiseYamlValue(`Bearer ${apiKey}`);
1828
+ const yaml = `name: ${sn}
1829
+ version: 0.0.1
1830
+ schema: v1
1831
+ mcpServers:
1832
+ - name: ${sn}
1833
+ type: streamable-http
1834
+ url: ${urlEsc}
1835
+ headers:
1836
+ Authorization: ${authEsc}
1837
+ `;
1838
+ await mkdir(dir, { recursive: true });
1839
+ await writeFile(filePath, yaml, SECRET_JSON_FILE_OPTIONS);
1840
+ writeMcpAddedLine(shortName, filePath);
1841
+ await warnIfApiKeyFileNotGitignored(workspacePath, `.continue/mcpServers/${shortName}.yaml`);
1842
+ return "ok";
1843
+ }
1844
+ async function mergeCopilotVscodeMcp(workspacePath, shortName, proxyUrl, apiKey) {
1845
+ const filePath = join(workspacePath, ".vscode", "mcp.json");
1767
1846
  let root = {};
1768
1847
  try {
1769
1848
  const raw = await readFile(filePath, "utf8");
@@ -1785,43 +1864,49 @@ async function mergeContinueHostedMcp(shortName, proxyUrl, apiKey) {
1785
1864
  throw e;
1786
1865
  }
1787
1866
  }
1788
- const rawServers = root["mcpServers"];
1789
- if (rawServers !== void 0) {
1790
- if (Array.isArray(rawServers)) ; else if (typeof rawServers === "object" && rawServers !== null) {
1791
- return "parse-error";
1792
- } else {
1793
- return "parse-error";
1794
- }
1795
- }
1796
- const servers = Array.isArray(rawServers) ? rawServers.map((s) => ({ ...s })) : [];
1797
- const entry = {
1798
- name: shortName,
1799
- type: "streamable-http",
1867
+ const serversRaw = root["servers"];
1868
+ const servers = typeof serversRaw === "object" && serversRaw !== null && !Array.isArray(serversRaw) ? { ...serversRaw } : {};
1869
+ const existed = servers[shortName] !== void 0;
1870
+ servers[shortName] = {
1871
+ type: "http",
1800
1872
  url: proxyUrl,
1801
- headers: {
1802
- Authorization: `Bearer ${apiKey}`
1803
- }
1873
+ headers: { Authorization: `Bearer ${apiKey}` }
1804
1874
  };
1805
- const idx = servers.findIndex((s) => s["name"] === shortName);
1806
- if (idx >= 0) {
1807
- servers[idx] = entry;
1808
- } else {
1809
- servers.push(entry);
1810
- }
1811
- root["mcpServers"] = servers;
1875
+ root["servers"] = servers;
1812
1876
  await mkdir(dirname(filePath), { recursive: true });
1813
1877
  await writeFile(filePath, JSON.stringify(root, null, 2) + "\n", SECRET_JSON_FILE_OPTIONS);
1814
- writeMcpAddedLine(shortName, filePath);
1878
+ if (existed) {
1879
+ process.stderr.write(
1880
+ style.dim(`Updated existing server entry for ${shortName} in .vscode/mcp.json`) + "\n"
1881
+ );
1882
+ } else {
1883
+ writeMcpAddedLine(shortName, filePath);
1884
+ }
1885
+ await warnIfApiKeyFileNotGitignored(workspacePath, ".vscode/mcp.json");
1815
1886
  return "ok";
1816
1887
  }
1817
1888
  async function mergeKiloCodeProjectMcp(workspacePath, shortName, proxyUrl, apiKey) {
1818
- const filePath = join(workspacePath, ".kilocode", "mcp.json");
1819
- return mergeMcpServersObjectStyle(filePath, shortName, {
1820
- url: proxyUrl,
1821
- headers: {
1822
- Authorization: `Bearer ${apiKey}`
1889
+ const filePath = join(workspacePath, ".kilo", "kilo.jsonc");
1890
+ const result = await mergeTopLevelKeyedJsonFile(
1891
+ filePath,
1892
+ "mcp",
1893
+ shortName,
1894
+ {
1895
+ type: "remote",
1896
+ url: proxyUrl,
1897
+ headers: { Authorization: `Bearer ${apiKey}` },
1898
+ enabled: true
1899
+ },
1900
+ {
1901
+ stripJsonComments: true,
1902
+ onExisting: "skip"
1823
1903
  }
1824
- });
1904
+ );
1905
+ if (result === "parse-error") return "parse-error";
1906
+ if (result === "ok") {
1907
+ await warnIfApiKeyFileNotGitignored(workspacePath, ".kilo/kilo.jsonc");
1908
+ }
1909
+ return "ok";
1825
1910
  }
1826
1911
  function printHostedProxyJsonParseWarning(filePath) {
1827
1912
  process.stderr.write(
@@ -1862,7 +1947,7 @@ function printHostedProxyPostWriteHints(platform, shortName) {
1862
1947
  }
1863
1948
  if (platform === "kilo-code") {
1864
1949
  process.stderr.write(
1865
- style.dim("Restart Kilo Code or reload the window so it picks up .kilocode/mcp.json.") + "\n"
1950
+ style.dim("Restart Kilo Code or reload the window so it picks up .kilo/kilo.jsonc.") + "\n"
1866
1951
  );
1867
1952
  }
1868
1953
  }
@@ -1879,15 +1964,40 @@ async function applyHostedProxyMcpConfig(platform, proxyUrl, shortName, apiKey,
1879
1964
  return;
1880
1965
  }
1881
1966
  if (platform === "github-copilot") {
1882
- process.stderr.write(
1883
- "\n" + style.dim(
1884
- "GitHub Copilot uses VS Code settings - paste the snippet below into your VS Code Settings (JSON)."
1885
- ) + "\n"
1886
- );
1967
+ try {
1968
+ const result = await mergeCopilotVscodeMcp(
1969
+ workspacePath,
1970
+ shortName,
1971
+ proxyUrlWithKeyWhenNeeded,
1972
+ apiKey
1973
+ );
1974
+ if (result === "ok") {
1975
+ printHostedProxyPostWriteHints(platform, shortName);
1976
+ return;
1977
+ }
1978
+ printHostedProxyJsonParseWarning(join(workspacePath, ".vscode", "mcp.json"));
1979
+ } catch (err) {
1980
+ process.stderr.write(
1981
+ `${style.yellow("!")} Could not auto-write config: ${err instanceof Error ? err.message : String(err)}
1982
+ `
1983
+ );
1984
+ }
1887
1985
  printPlatformSnippet(platform, proxyUrl, shortName, apiKey);
1888
1986
  return;
1889
1987
  }
1890
1988
  if (platform === "goose") {
1989
+ try {
1990
+ const result = await mergeGooseConfig(shortName, proxyUrlWithKeyWhenNeeded, apiKey);
1991
+ if (result === "ok") {
1992
+ printHostedProxyPostWriteHints(platform, shortName);
1993
+ return;
1994
+ }
1995
+ } catch (err) {
1996
+ process.stderr.write(
1997
+ `${style.yellow("!")} Could not auto-write config: ${err instanceof Error ? err.message : String(err)}
1998
+ `
1999
+ );
2000
+ }
1891
2001
  printPlatformSnippet(platform, proxyUrl, shortName, apiKey);
1892
2002
  return;
1893
2003
  }
@@ -1934,12 +2044,19 @@ async function applyHostedProxyMcpConfig(platform, proxyUrl, shortName, apiKey,
1934
2044
  apiKey
1935
2045
  );
1936
2046
  if (result === "parse-error") {
1937
- printHostedProxyJsonParseWarning(join(workspacePath, ".kilocode", "mcp.json"));
2047
+ printHostedProxyJsonParseWarning(join(workspacePath, ".kilo", "kilo.jsonc"));
1938
2048
  }
1939
2049
  } else if (platform === "continue-dev") {
1940
- result = await mergeContinueHostedMcp(shortName, proxyUrlWithKeyWhenNeeded, apiKey);
2050
+ result = await mergeContinueHostedMcp(
2051
+ workspacePath,
2052
+ shortName,
2053
+ proxyUrlWithKeyWhenNeeded,
2054
+ apiKey
2055
+ );
1941
2056
  if (result === "parse-error") {
1942
- printHostedProxyJsonParseWarning(getContinueConfigJsonPath());
2057
+ printHostedProxyJsonParseWarning(
2058
+ join(workspacePath, ".continue", "mcpServers", `${shortName}.yaml`)
2059
+ );
1943
2060
  }
1944
2061
  } else {
1945
2062
  result = await mergeMcpServersObjectStyle(getCursorMcpJsonPath(), shortName, {
@@ -1963,17 +2080,96 @@ async function applyHostedProxyMcpConfig(platform, proxyUrl, shortName, apiKey,
1963
2080
  }
1964
2081
  printPlatformSnippet(platform, proxyUrl, shortName, apiKey);
1965
2082
  }
1966
- function gooseHostedProxyYaml(shortName, proxyUrl, bearerHeader) {
1967
- return `extensions:
1968
- ${shortName}:
2083
+ function printGooseConfigYamlParseErrorToStderr() {
2084
+ process.stderr.write(
2085
+ style.yellow("!") + " Could not parse ~/.config/goose/config.yaml - check for syntax errors or invalid YAML\n"
2086
+ );
2087
+ }
2088
+ function gooseExtensionYaml(shortName, proxyUrl, bearerHeader) {
2089
+ const sn = sanitiseYamlValue(shortName);
2090
+ const urlEsc = sanitiseYamlValue(proxyUrl);
2091
+ const authEsc = sanitiseYamlValue(bearerHeader);
2092
+ return ` ${sn}:
2093
+ enabled: true
1969
2094
  type: streamable_http
1970
- url: ${proxyUrl}
2095
+ name: ${sn}
2096
+ description: ''
2097
+ uri: ${urlEsc}
2098
+ envs: {}
2099
+ env_keys: []
1971
2100
  headers:
1972
- Authorization: ${bearerHeader}
1973
- enabled: true
2101
+ Authorization: ${authEsc}
1974
2102
  timeout: 300
2103
+ socket: null
2104
+ bundled: null
2105
+ available_tools: []
1975
2106
  `;
1976
2107
  }
2108
+ function gooseHostedProxyYaml(shortName, proxyUrl, bearerHeader) {
2109
+ return `extensions:
2110
+ ` + gooseExtensionYaml(shortName, proxyUrl, bearerHeader);
2111
+ }
2112
+ function isYamlPlainObject(value) {
2113
+ return value !== null && typeof value === "object" && !Array.isArray(value);
2114
+ }
2115
+ async function mergeGooseConfig(shortName, proxyUrl, apiKey) {
2116
+ const filePath = join(homedir(), ".config", "goose", "config.yaml");
2117
+ const bearerHeader = `Bearer ${apiKey}`;
2118
+ let content = "";
2119
+ try {
2120
+ content = await readFile(filePath, "utf8");
2121
+ } catch (e) {
2122
+ if (isErrnoException(e) && e.code === "ENOENT") {
2123
+ content = "";
2124
+ } else {
2125
+ throw e;
2126
+ }
2127
+ }
2128
+ let root;
2129
+ try {
2130
+ const data = content.trim().length === 0 ? {} : parse(content);
2131
+ if (data === null || typeof data !== "object" || Array.isArray(data)) {
2132
+ printGooseConfigYamlParseErrorToStderr();
2133
+ return "parse-error";
2134
+ }
2135
+ root = data;
2136
+ } catch {
2137
+ printGooseConfigYamlParseErrorToStderr();
2138
+ return "parse-error";
2139
+ }
2140
+ const extensionsRaw = root["extensions"];
2141
+ let extensions;
2142
+ if (isYamlPlainObject(extensionsRaw)) {
2143
+ extensions = { ...extensionsRaw };
2144
+ } else if (extensionsRaw === void 0) {
2145
+ extensions = {};
2146
+ } else {
2147
+ printGooseConfigYamlParseErrorToStderr();
2148
+ return "parse-error";
2149
+ }
2150
+ extensions[shortName] = {
2151
+ enabled: true,
2152
+ type: "streamable_http",
2153
+ name: shortName,
2154
+ description: "",
2155
+ uri: proxyUrl,
2156
+ envs: {},
2157
+ env_keys: [],
2158
+ headers: { Authorization: bearerHeader },
2159
+ timeout: 300,
2160
+ socket: null,
2161
+ bundled: null,
2162
+ available_tools: []
2163
+ };
2164
+ root["extensions"] = extensions;
2165
+ const out = stringify(root, { indent: 2, lineWidth: 0 });
2166
+ const body = out.endsWith("\n") ? out : `${out}
2167
+ `;
2168
+ await mkdir(dirname(filePath), { recursive: true });
2169
+ await writeFile(filePath, body, SECRET_JSON_FILE_OPTIONS);
2170
+ writeMcpAddedLine(shortName, filePath);
2171
+ return "ok";
2172
+ }
1977
2173
  function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
1978
2174
  const hostedInlinePlatforms = /* @__PURE__ */ new Set([
1979
2175
  "cursor",
@@ -1993,14 +2189,12 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
1993
2189
  if (platform === "github-copilot") {
1994
2190
  snippetText = JSON.stringify(
1995
2191
  {
1996
- mcp: {
1997
- servers: {
1998
- [shortName]: {
1999
- type: "http",
2000
- url: urlInSnippet,
2001
- headers: {
2002
- Authorization: authHeader
2003
- }
2192
+ servers: {
2193
+ [shortName]: {
2194
+ type: "http",
2195
+ url: urlInSnippet,
2196
+ headers: {
2197
+ Authorization: authHeader
2004
2198
  }
2005
2199
  }
2006
2200
  }
@@ -2039,18 +2233,32 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
2039
2233
  2
2040
2234
  );
2041
2235
  } else if (platform === "continue-dev") {
2236
+ const sn = sanitiseYamlValue(shortName);
2237
+ const urlEsc = sanitiseYamlValue(urlInSnippet);
2238
+ const authEsc = sanitiseYamlValue(authHeader);
2239
+ snippetText = `name: ${sn}
2240
+ version: 0.0.1
2241
+ schema: v1
2242
+ mcpServers:
2243
+ - name: ${sn}
2244
+ type: streamable-http
2245
+ url: ${urlEsc}
2246
+ headers:
2247
+ Authorization: ${authEsc}
2248
+ `;
2249
+ } else if (platform === "kilo-code") {
2042
2250
  snippetText = JSON.stringify(
2043
2251
  {
2044
- mcpServers: [
2045
- {
2046
- name: shortName,
2047
- type: "streamable-http",
2252
+ mcp: {
2253
+ [shortName]: {
2254
+ type: "remote",
2048
2255
  url: urlInSnippet,
2049
2256
  headers: {
2050
2257
  Authorization: authHeader
2051
- }
2258
+ },
2259
+ enabled: true
2052
2260
  }
2053
- ]
2261
+ }
2054
2262
  },
2055
2263
  null,
2056
2264
  2
@@ -2090,16 +2298,18 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
2090
2298
  );
2091
2299
  } else if (platform === "kilo-code") {
2092
2300
  process.stderr.write(
2093
- "\n" + style.dim(`Add this to ${join(resolve(process.cwd()), ".kilocode", "mcp.json")}:`) + "\n\n"
2301
+ "\n" + style.dim(`Add this to ${join(resolve(process.cwd()), ".kilo", "kilo.jsonc")}:`) + "\n\n"
2094
2302
  );
2095
2303
  } else if (platform === "github-copilot") {
2096
2304
  process.stderr.write(
2097
2305
  "\n" + style.dim(
2098
- "Merge this snippet under the mcp key in your VS Code Settings (JSON). If you do not have an mcp section yet, add one. Copilot picks up MCP servers when you use Agent mode."
2306
+ "Create .vscode/mcp.json in your workspace root (create the .vscode folder if it does not exist). After saving, reload VS Code and confirm the server appears in Copilot Agent mode under Tools."
2099
2307
  ) + "\n\n"
2100
2308
  );
2101
2309
  } else if (platform === "continue-dev") {
2102
- process.stderr.write("\n" + style.dim(`Add this to ${getContinueConfigJsonPath()}:`) + "\n\n");
2310
+ process.stderr.write(
2311
+ "\n" + style.dim(`Save this as .continue/mcpServers/${shortName}.yaml in your workspace root.`) + "\n\n"
2312
+ );
2103
2313
  } else if (platform === "goose") {
2104
2314
  process.stderr.write(
2105
2315
  "\n" + style.dim("Add this to ~/.config/goose/config.yaml under the extensions key.") + "\n\n"
@@ -2276,7 +2486,7 @@ async function runInit(explicitBaseUrl, options) {
2276
2486
  }
2277
2487
  await warnIfInstalledShieldIsOutdated();
2278
2488
  const configuredAgents = [];
2279
- let currentAgents = collectAgentsFromConfig(existing);
2489
+ let currentAgents = mergeAgentsForUniqueNames(collectAgentsFromConfig(existing));
2280
2490
  let lastConfig = {
2281
2491
  apiKey,
2282
2492
  baseUrl: resolvedBaseUrl,
@@ -2894,6 +3104,14 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2894
3104
  }
2895
3105
  rl.close();
2896
3106
  if (configuredAgents.length > 0) {
3107
+ let mcpPromptLabel2 = function(platformSlug) {
3108
+ const rows = configuredAgents.filter((a) => a.platform === platformSlug);
3109
+ const last = rows[rows.length - 1];
3110
+ if (last === void 0) return "shield-mcp";
3111
+ const s = typeof last.shortName === "string" ? last.shortName.trim() : "";
3112
+ if (s.length > 0) return s;
3113
+ return last.agentName.trim().length > 0 ? last.agentName.trim() : "shield-mcp";
3114
+ };
2897
3115
  process.stderr.write("\n" + style.bold(style.violet("Setup complete")) + "\n\n");
2898
3116
  for (const agent of configuredAgents) {
2899
3117
  const namePart = agent.agentName.length > 0 ? ` - ${style.cyan(agent.agentName)}` : "";
@@ -2905,14 +3123,6 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2905
3123
  }
2906
3124
  process.stderr.write("\n");
2907
3125
  const configuredPlatforms = new Set(configuredAgents.map((a) => a.platform));
2908
- const cursorMcpPromptLabel = (() => {
2909
- const rows = configuredAgents.filter((a) => a.platform === "cursor");
2910
- const last = rows[rows.length - 1];
2911
- if (last === void 0) return "shield-mcp";
2912
- const s = typeof last.shortName === "string" ? last.shortName.trim() : "";
2913
- if (s.length > 0) return s;
2914
- return last.agentName.trim().length > 0 ? last.agentName.trim() : "shield-mcp";
2915
- })();
2916
3126
  const blocks = [];
2917
3127
  if (configuredPlatforms.has("openclaw")) {
2918
3128
  blocks.push(
@@ -2925,33 +3135,39 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2925
3135
  );
2926
3136
  }
2927
3137
  if (configuredPlatforms.has("claude-desktop")) {
3138
+ const cdLabel = mcpPromptLabel2("claude-desktop");
2928
3139
  blocks.push(
2929
- "\n" + style.bold("Claude Desktop") + "\n \u2192 Restart Claude Desktop to pick up config changes\n \u2192 Try it: make a request in Claude Desktop - Shield will intercept the first tool call and ask for your consent\n"
3140
+ "\n" + style.bold("Claude Desktop") + '\n \u2192 Restart Claude Desktop to load the updated configuration\n \u2192 Confirm connection: click your profile (bottom-left) \u2192 Settings \u2192 Developer\n Check that "' + cdLabel + '" shows a green "running" status\n \u2192 Try it: paste this into Claude Desktop:\n "Use the ' + cdLabel + ' MCP server to list my GitHub repositories"\n'
2930
3141
  );
2931
3142
  }
2932
3143
  if (configuredPlatforms.has("cursor")) {
3144
+ const cursorLabel = mcpPromptLabel2("cursor");
2933
3145
  blocks.push(
2934
- "\n" + style.bold("Cursor") + "\n \u2192 If needed, download Cursor from " + style.cyan("https://www.cursor.com/downloads") + '\n \u2192 Restart Cursor so it loads the MCP server\n \u2192 Try it: make a request in Cursor - Shield will intercept the first tool call and ask for your consent\n \u2192 Example: "Use the ' + cursorMcpPromptLabel + ' MCP server to list my GitHub repositories"\n'
3146
+ "\n" + style.bold("Cursor") + "\n \u2192 If needed, download Cursor from " + style.cyan("https://www.cursor.com/downloads") + '\n \u2192 Restart Cursor so it loads the MCP server\n \u2192 Confirm connection: open Settings \u2192 Tools & MCPs\n Check that "' + cursorLabel + '" shows a green status indicator\n \u2192 Try it: paste this into Cursor:\n "Use the ' + cursorLabel + ' MCP server to list my GitHub repositories"\n'
2935
3147
  );
2936
3148
  }
2937
3149
  if (configuredPlatforms.has("kilo-code")) {
3150
+ const kiloLabel = mcpPromptLabel2("kilo-code");
2938
3151
  blocks.push(
2939
- "\n" + style.bold("Kilo Code") + "\n \u2192 Restart the editor or reload the window if the MCP server does not appear\n \u2192 Try it: make a request in Kilo Code - Shield will intercept the first tool call and ask for your consent\n"
3152
+ "\n" + style.bold("Kilo Code") + '\n \u2192 Restart the editor or reload the window if the MCP server does not appear\n \u2192 Confirm connection: Settings \u2192 Agent Behaviour \u2192 MCP Servers\n \u2192 Try it: paste this into Kilo Code:\n "Use the ' + kiloLabel + ' MCP server to list my GitHub repositories"\n'
2940
3153
  );
2941
3154
  }
2942
3155
  if (configuredPlatforms.has("github-copilot")) {
3156
+ const copilotLabel = mcpPromptLabel2("github-copilot");
2943
3157
  blocks.push(
2944
- "\n" + style.bold("GitHub Copilot") + "\n \u2192 Reload the editor window if the MCP server does not appear\n \u2192 Open Copilot Agent mode and confirm the MCP server connects\n \u2192 Try it: make a request in GitHub Copilot - Shield will intercept the first tool call and ask for your consent\n"
3158
+ "\n" + style.bold("GitHub Copilot") + '\n \u2192 Reload the editor window if the MCP server does not appear\n \u2192 Confirm connection: open Copilot chat in Agent mode and confirm the server appears under Tools\n \u2192 Try it: paste this into GitHub Copilot:\n "Use the ' + copilotLabel + ' MCP server to list my GitHub repositories"\n'
2945
3159
  );
2946
3160
  }
2947
3161
  if (configuredPlatforms.has("continue-dev")) {
3162
+ const continueLabel = mcpPromptLabel2("continue-dev");
2948
3163
  blocks.push(
2949
- "\n" + style.bold("Continue") + "\n \u2192 If needed, install Continue from " + style.cyan("https://docs.continue.dev/ide-extensions/install") + "\n \u2192 Reload VS Code and open Continue agent mode\n \u2192 Try it: make a request in Continue - Shield will intercept the first tool call and ask for your consent\n"
3164
+ "\n" + style.bold("Continue") + "\n \u2192 If needed, install Continue from " + style.cyan("https://docs.continue.dev/ide-extensions/install") + '\n \u2192 Reload VS Code and open Continue agent mode\n \u2192 Confirm connection: Settings \u2192 Tools \u2192 MCP Servers\n \u2192 Try it: paste this into Continue:\n "Use the ' + continueLabel + ' MCP server to list my GitHub repositories"\n'
2950
3165
  );
2951
3166
  }
2952
3167
  if (configuredPlatforms.has("goose")) {
3168
+ const gooseLabel = mcpPromptLabel2("goose");
2953
3169
  blocks.push(
2954
- "\n" + style.bold("Goose") + "\n \u2192 Start a new Goose session after updating config\n \u2192 Try it: make a request in Goose - Shield will intercept the first tool call and ask for your consent\n"
3170
+ "\n" + style.bold("Goose") + '\n \u2192 Start a new Goose session after updating config\n \u2192 Confirm connection: check the Extensions page in the sidebar\n \u2192 Try it: paste this into Goose:\n "Use the ' + gooseLabel + ' MCP server to list my GitHub repositories"\n'
2955
3171
  );
2956
3172
  }
2957
3173
  const windsurfNativeConfigured = configuredAgents.some(
@@ -3397,9 +3613,13 @@ function extractToolCallParams(request) {
3397
3613
  if (typeof args !== "object" || args === null) return null;
3398
3614
  return { name, arguments: args };
3399
3615
  }
3400
- function buildBlockedResponse(id, service, permissionLevel, dashboardUrl) {
3616
+ function buildBlockedResponse(id, service, _permissionLevel, dashboardUrl) {
3401
3617
  const displayService = capitalize(service);
3402
- const message = `Action blocked by Multicorn Shield: agent does not have ${permissionLevel} access to ${displayService}. Configure permissions at ${dashboardUrl}`;
3618
+ const message = `Action blocked by Shield
3619
+
3620
+ This agent cannot use ${displayService}.
3621
+
3622
+ Configure permissions: ${dashboardUrl}`;
3403
3623
  return {
3404
3624
  jsonrpc: "2.0",
3405
3625
  id,
@@ -3901,6 +4121,13 @@ async function restoreClaudeDesktopMcpFromBackup() {
3901
4121
  });
3902
4122
  }
3903
4123
 
4124
+ // package.json
4125
+ var package_default = {
4126
+ version: "1.7.0"};
4127
+
4128
+ // src/package-meta.ts
4129
+ var PACKAGE_VERSION = package_default.version;
4130
+
3904
4131
  // bin/multicorn-shield.ts
3905
4132
  function parseArgs(argv) {
3906
4133
  const args = argv.slice(2);
@@ -4053,6 +4280,7 @@ function printHelp() {
4053
4280
  " Shield's permission layer.",
4054
4281
  "",
4055
4282
  "Options:",
4283
+ " --version, -v Print version and exit",
4056
4284
  " --verbose, --debug Print extra diagnostics during init (menu selection, agent counts)",
4057
4285
  " --api-key <key> Multicorn API key (overrides MULTICORN_API_KEY env var and config file)",
4058
4286
  " --log-level <level> Log level: debug | info | warn | error (default: info)",
@@ -4077,6 +4305,11 @@ async function runCli() {
4077
4305
  );
4078
4306
  return;
4079
4307
  }
4308
+ if (first === "--version" || first === "-v") {
4309
+ process.stdout.write(`${PACKAGE_VERSION}
4310
+ `);
4311
+ process.exit(0);
4312
+ }
4080
4313
  const cli = parseArgs(process.argv);
4081
4314
  const logger = createLogger(cli.logLevel);
4082
4315
  if (cli.subcommand === "help") {
package/dist/proxy.cjs CHANGED
@@ -30,9 +30,13 @@ function extractToolCallParams(request) {
30
30
  if (typeof args !== "object" || args === null) return null;
31
31
  return { name, arguments: args };
32
32
  }
33
- function buildBlockedResponse(id, service, permissionLevel, dashboardUrl) {
33
+ function buildBlockedResponse(id, service, _permissionLevel, dashboardUrl) {
34
34
  const displayService = capitalize(service);
35
- const message = `Action blocked by Multicorn Shield: agent does not have ${permissionLevel} access to ${displayService}. Configure permissions at ${dashboardUrl}`;
35
+ const message = `Action blocked by Shield
36
+
37
+ This agent cannot use ${displayService}.
38
+
39
+ Configure permissions: ${dashboardUrl}`;
36
40
  return {
37
41
  jsonrpc: "2.0",
38
42
  id,
package/dist/proxy.d.cts CHANGED
@@ -31,7 +31,7 @@ interface ToolCallParams {
31
31
  }
32
32
  declare function parseJsonRpcLine(line: string): JsonRpcRequest | null;
33
33
  declare function extractToolCallParams(request: JsonRpcRequest): ToolCallParams | null;
34
- declare function buildBlockedResponse(id: string | number | null, service: string, permissionLevel: string, dashboardUrl: string): JsonRpcResponse;
34
+ declare function buildBlockedResponse(id: string | number | null, service: string, _permissionLevel: string, dashboardUrl: string): JsonRpcResponse;
35
35
  declare function buildSpendingBlockedResponse(id: string | number | null, reason: string, dashboardUrl: string): JsonRpcResponse;
36
36
  /**
37
37
  * Internal error: Shield could not verify permissions due to an exception.