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.
@@ -8,6 +8,7 @@ import { readFileSync, existsSync, statSync } from 'fs';
8
8
  import { fileURLToPath } from 'url';
9
9
  import { createRequire } from 'module';
10
10
  import { createInterface } from 'readline';
11
+ import { parse, stringify } from 'yaml';
11
12
  import 'stream';
12
13
 
13
14
  var __defProp = Object.defineProperty;
@@ -1346,9 +1347,6 @@ function getClineMcpSettingsPath() {
1346
1347
  );
1347
1348
  }
1348
1349
  }
1349
- function getContinueConfigJsonPath() {
1350
- return join(homedir(), ".continue", "config.json");
1351
- }
1352
1350
  function platformMenuLabelForSelection(sel) {
1353
1351
  const slug = PLATFORM_BY_SELECTION[sel];
1354
1352
  if (slug === void 0) return "Unknown";
@@ -1411,6 +1409,12 @@ async function promptWindsurfIntegrationMode(ask) {
1411
1409
  return choice === 1 ? "native" : "hosted";
1412
1410
  }
1413
1411
  async function arrowSelect(options, ask, fallbackLabel) {
1412
+ if (options.length === 1) {
1413
+ const only = options[0] ?? "";
1414
+ process.stderr.write(`${style.violet("\u276F")} ${style.cyan(only)}
1415
+ `);
1416
+ return 0;
1417
+ }
1414
1418
  const canRaw = process.stdin.isTTY && typeof process.stdin.setRawMode === "function";
1415
1419
  if (!canRaw) {
1416
1420
  for (let i = 0; i < options.length; i++) {
@@ -1463,7 +1467,7 @@ async function arrowSelect(options, ask, fallbackLabel) {
1463
1467
  cleanup();
1464
1468
  clearLines();
1465
1469
  const chosen = options.at(idx);
1466
- if (chosen !== void 0) {
1470
+ if (chosen !== void 0 && options.length > 1) {
1467
1471
  process.stderr.write(`${style.violet("\u276F")} ${style.cyan(chosen)}
1468
1472
  `);
1469
1473
  }
@@ -1644,13 +1648,56 @@ function writeMcpAddedLine(shortName, filePath) {
1644
1648
  style.green("\u2713") + ' MCP server "' + shortName + '" added to ' + style.cyan(filePath) + "\n"
1645
1649
  );
1646
1650
  }
1647
- async function mergeMcpServersObjectStyle(filePath, shortName, entry) {
1651
+ function sanitiseYamlValue(value) {
1652
+ if (value.length === 0) {
1653
+ return "''";
1654
+ }
1655
+ const needsQuoting = /[:#\n{}]/.test(value) || value.includes("[") || value.includes("]") || value !== value.trim() || value.includes("'");
1656
+ if (!needsQuoting) {
1657
+ return value;
1658
+ }
1659
+ return `'${value.replace(/'/g, "''")}'`;
1660
+ }
1661
+ function gitignoreLikelyCoversPath(relPosixPath, gitignoreBody) {
1662
+ const norm = relPosixPath.replace(/\\/g, "/").replace(/^\.\//, "");
1663
+ const lines = gitignoreBody.split(/\r?\n/);
1664
+ for (const raw of lines) {
1665
+ const line = raw.trim();
1666
+ if (!line || line.startsWith("#") || line.startsWith("!")) continue;
1667
+ const pat = line.replace(/^\//, "");
1668
+ if (pat === norm || pat === `./${norm}`) return true;
1669
+ if (!pat.includes("*")) {
1670
+ if (pat.endsWith("/")) {
1671
+ const dir = pat.slice(0, -1);
1672
+ if (norm === dir || norm.startsWith(`${dir}/`)) return true;
1673
+ }
1674
+ }
1675
+ }
1676
+ return false;
1677
+ }
1678
+ async function warnIfApiKeyFileNotGitignored(workspaceRoot, relativePosixPath) {
1679
+ const gitignorePath = join(workspaceRoot, ".gitignore");
1680
+ let content;
1681
+ try {
1682
+ content = await readFile(gitignorePath, "utf8");
1683
+ } catch (e) {
1684
+ if (isErrnoException(e) && e.code === "ENOENT") return;
1685
+ throw e;
1686
+ }
1687
+ const norm = relativePosixPath.replace(/\\/g, "/").replace(/^\.\//, "");
1688
+ if (gitignoreLikelyCoversPath(norm, content)) return;
1689
+ process.stderr.write(
1690
+ style.yellow("\u26A0") + " Config contains your API key. Add " + style.cyan(norm) + " to .gitignore to avoid committing credentials.\n"
1691
+ );
1692
+ }
1693
+ async function mergeTopLevelKeyedJsonFile(filePath, topLevelKey, shortName, entry, options) {
1648
1694
  let root = {};
1649
1695
  try {
1650
1696
  const raw = await readFile(filePath, "utf8");
1697
+ const toParse = options.stripJsonComments ? raw.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "") : raw;
1651
1698
  let parsed;
1652
1699
  try {
1653
- parsed = JSON.parse(raw);
1700
+ parsed = JSON.parse(toParse);
1654
1701
  } catch {
1655
1702
  return "parse-error";
1656
1703
  }
@@ -1666,15 +1713,25 @@ async function mergeMcpServersObjectStyle(filePath, shortName, entry) {
1666
1713
  throw e;
1667
1714
  }
1668
1715
  }
1669
- const mcpRaw = root["mcpServers"];
1670
- const mcpServers = typeof mcpRaw === "object" && mcpRaw !== null && !Array.isArray(mcpRaw) ? { ...mcpRaw } : {};
1671
- mcpServers[shortName] = entry;
1672
- root["mcpServers"] = mcpServers;
1716
+ const bucketRaw = root[topLevelKey];
1717
+ const bucket = typeof bucketRaw === "object" && bucketRaw !== null && !Array.isArray(bucketRaw) ? { ...bucketRaw } : {};
1718
+ if (options.onExisting === "skip" && bucket[shortName] !== void 0) {
1719
+ return "unchanged";
1720
+ }
1721
+ bucket[shortName] = entry;
1722
+ root[topLevelKey] = bucket;
1673
1723
  await mkdir(dirname(filePath), { recursive: true });
1674
1724
  await writeFile(filePath, JSON.stringify(root, null, 2) + "\n", SECRET_JSON_FILE_OPTIONS);
1675
1725
  writeMcpAddedLine(shortName, filePath);
1676
1726
  return "ok";
1677
1727
  }
1728
+ async function mergeMcpServersObjectStyle(filePath, shortName, entry) {
1729
+ const result = await mergeTopLevelKeyedJsonFile(filePath, "mcpServers", shortName, entry, {
1730
+ stripJsonComments: false,
1731
+ onExisting: "overwrite"
1732
+ });
1733
+ return result === "parse-error" ? "parse-error" : "ok";
1734
+ }
1678
1735
  async function mergeClaudeDesktopHostedMcpRemote(shortName, proxyUrl, apiKey) {
1679
1736
  const entry = {
1680
1737
  command: "npx",
@@ -1682,8 +1739,30 @@ async function mergeClaudeDesktopHostedMcpRemote(shortName, proxyUrl, apiKey) {
1682
1739
  };
1683
1740
  return mergeMcpServersObjectStyle(getClaudeDesktopConfigPath(), shortName, entry);
1684
1741
  }
1685
- async function mergeContinueHostedMcp(shortName, proxyUrl, apiKey) {
1686
- const filePath = getContinueConfigJsonPath();
1742
+ async function mergeContinueHostedMcp(workspacePath, shortName, proxyUrl, apiKey) {
1743
+ const dir = join(workspacePath, ".continue", "mcpServers");
1744
+ const filePath = join(dir, `${shortName}.yaml`);
1745
+ const sn = sanitiseYamlValue(shortName);
1746
+ const urlEsc = sanitiseYamlValue(proxyUrl);
1747
+ const authEsc = sanitiseYamlValue(`Bearer ${apiKey}`);
1748
+ const yaml = `name: ${sn}
1749
+ version: 0.0.1
1750
+ schema: v1
1751
+ mcpServers:
1752
+ - name: ${sn}
1753
+ type: streamable-http
1754
+ url: ${urlEsc}
1755
+ headers:
1756
+ Authorization: ${authEsc}
1757
+ `;
1758
+ await mkdir(dir, { recursive: true });
1759
+ await writeFile(filePath, yaml, SECRET_JSON_FILE_OPTIONS);
1760
+ writeMcpAddedLine(shortName, filePath);
1761
+ await warnIfApiKeyFileNotGitignored(workspacePath, `.continue/mcpServers/${shortName}.yaml`);
1762
+ return "ok";
1763
+ }
1764
+ async function mergeCopilotVscodeMcp(workspacePath, shortName, proxyUrl, apiKey) {
1765
+ const filePath = join(workspacePath, ".vscode", "mcp.json");
1687
1766
  let root = {};
1688
1767
  try {
1689
1768
  const raw = await readFile(filePath, "utf8");
@@ -1705,43 +1784,49 @@ async function mergeContinueHostedMcp(shortName, proxyUrl, apiKey) {
1705
1784
  throw e;
1706
1785
  }
1707
1786
  }
1708
- const rawServers = root["mcpServers"];
1709
- if (rawServers !== void 0) {
1710
- if (Array.isArray(rawServers)) ; else if (typeof rawServers === "object" && rawServers !== null) {
1711
- return "parse-error";
1712
- } else {
1713
- return "parse-error";
1714
- }
1715
- }
1716
- const servers = Array.isArray(rawServers) ? rawServers.map((s) => ({ ...s })) : [];
1717
- const entry = {
1718
- name: shortName,
1719
- type: "streamable-http",
1787
+ const serversRaw = root["servers"];
1788
+ const servers = typeof serversRaw === "object" && serversRaw !== null && !Array.isArray(serversRaw) ? { ...serversRaw } : {};
1789
+ const existed = servers[shortName] !== void 0;
1790
+ servers[shortName] = {
1791
+ type: "http",
1720
1792
  url: proxyUrl,
1721
- headers: {
1722
- Authorization: `Bearer ${apiKey}`
1723
- }
1793
+ headers: { Authorization: `Bearer ${apiKey}` }
1724
1794
  };
1725
- const idx = servers.findIndex((s) => s["name"] === shortName);
1726
- if (idx >= 0) {
1727
- servers[idx] = entry;
1728
- } else {
1729
- servers.push(entry);
1730
- }
1731
- root["mcpServers"] = servers;
1795
+ root["servers"] = servers;
1732
1796
  await mkdir(dirname(filePath), { recursive: true });
1733
1797
  await writeFile(filePath, JSON.stringify(root, null, 2) + "\n", SECRET_JSON_FILE_OPTIONS);
1734
- writeMcpAddedLine(shortName, filePath);
1798
+ if (existed) {
1799
+ process.stderr.write(
1800
+ style.dim(`Updated existing server entry for ${shortName} in .vscode/mcp.json`) + "\n"
1801
+ );
1802
+ } else {
1803
+ writeMcpAddedLine(shortName, filePath);
1804
+ }
1805
+ await warnIfApiKeyFileNotGitignored(workspacePath, ".vscode/mcp.json");
1735
1806
  return "ok";
1736
1807
  }
1737
1808
  async function mergeKiloCodeProjectMcp(workspacePath, shortName, proxyUrl, apiKey) {
1738
- const filePath = join(workspacePath, ".kilocode", "mcp.json");
1739
- return mergeMcpServersObjectStyle(filePath, shortName, {
1740
- url: proxyUrl,
1741
- headers: {
1742
- Authorization: `Bearer ${apiKey}`
1809
+ const filePath = join(workspacePath, ".kilo", "kilo.jsonc");
1810
+ const result = await mergeTopLevelKeyedJsonFile(
1811
+ filePath,
1812
+ "mcp",
1813
+ shortName,
1814
+ {
1815
+ type: "remote",
1816
+ url: proxyUrl,
1817
+ headers: { Authorization: `Bearer ${apiKey}` },
1818
+ enabled: true
1819
+ },
1820
+ {
1821
+ stripJsonComments: true,
1822
+ onExisting: "skip"
1743
1823
  }
1744
- });
1824
+ );
1825
+ if (result === "parse-error") return "parse-error";
1826
+ if (result === "ok") {
1827
+ await warnIfApiKeyFileNotGitignored(workspacePath, ".kilo/kilo.jsonc");
1828
+ }
1829
+ return "ok";
1745
1830
  }
1746
1831
  function printHostedProxyJsonParseWarning(filePath) {
1747
1832
  process.stderr.write(
@@ -1782,7 +1867,7 @@ function printHostedProxyPostWriteHints(platform, shortName) {
1782
1867
  }
1783
1868
  if (platform === "kilo-code") {
1784
1869
  process.stderr.write(
1785
- style.dim("Restart Kilo Code or reload the window so it picks up .kilocode/mcp.json.") + "\n"
1870
+ style.dim("Restart Kilo Code or reload the window so it picks up .kilo/kilo.jsonc.") + "\n"
1786
1871
  );
1787
1872
  }
1788
1873
  }
@@ -1799,15 +1884,40 @@ async function applyHostedProxyMcpConfig(platform, proxyUrl, shortName, apiKey,
1799
1884
  return;
1800
1885
  }
1801
1886
  if (platform === "github-copilot") {
1802
- process.stderr.write(
1803
- "\n" + style.dim(
1804
- "GitHub Copilot uses VS Code settings - paste the snippet below into your VS Code Settings (JSON)."
1805
- ) + "\n"
1806
- );
1887
+ try {
1888
+ const result = await mergeCopilotVscodeMcp(
1889
+ workspacePath,
1890
+ shortName,
1891
+ proxyUrlWithKeyWhenNeeded,
1892
+ apiKey
1893
+ );
1894
+ if (result === "ok") {
1895
+ printHostedProxyPostWriteHints(platform, shortName);
1896
+ return;
1897
+ }
1898
+ printHostedProxyJsonParseWarning(join(workspacePath, ".vscode", "mcp.json"));
1899
+ } catch (err) {
1900
+ process.stderr.write(
1901
+ `${style.yellow("!")} Could not auto-write config: ${err instanceof Error ? err.message : String(err)}
1902
+ `
1903
+ );
1904
+ }
1807
1905
  printPlatformSnippet(platform, proxyUrl, shortName, apiKey);
1808
1906
  return;
1809
1907
  }
1810
1908
  if (platform === "goose") {
1909
+ try {
1910
+ const result = await mergeGooseConfig(shortName, proxyUrlWithKeyWhenNeeded, apiKey);
1911
+ if (result === "ok") {
1912
+ printHostedProxyPostWriteHints(platform, shortName);
1913
+ return;
1914
+ }
1915
+ } catch (err) {
1916
+ process.stderr.write(
1917
+ `${style.yellow("!")} Could not auto-write config: ${err instanceof Error ? err.message : String(err)}
1918
+ `
1919
+ );
1920
+ }
1811
1921
  printPlatformSnippet(platform, proxyUrl, shortName, apiKey);
1812
1922
  return;
1813
1923
  }
@@ -1854,12 +1964,19 @@ async function applyHostedProxyMcpConfig(platform, proxyUrl, shortName, apiKey,
1854
1964
  apiKey
1855
1965
  );
1856
1966
  if (result === "parse-error") {
1857
- printHostedProxyJsonParseWarning(join(workspacePath, ".kilocode", "mcp.json"));
1967
+ printHostedProxyJsonParseWarning(join(workspacePath, ".kilo", "kilo.jsonc"));
1858
1968
  }
1859
1969
  } else if (platform === "continue-dev") {
1860
- result = await mergeContinueHostedMcp(shortName, proxyUrlWithKeyWhenNeeded, apiKey);
1970
+ result = await mergeContinueHostedMcp(
1971
+ workspacePath,
1972
+ shortName,
1973
+ proxyUrlWithKeyWhenNeeded,
1974
+ apiKey
1975
+ );
1861
1976
  if (result === "parse-error") {
1862
- printHostedProxyJsonParseWarning(getContinueConfigJsonPath());
1977
+ printHostedProxyJsonParseWarning(
1978
+ join(workspacePath, ".continue", "mcpServers", `${shortName}.yaml`)
1979
+ );
1863
1980
  }
1864
1981
  } else {
1865
1982
  result = await mergeMcpServersObjectStyle(getCursorMcpJsonPath(), shortName, {
@@ -1883,17 +2000,96 @@ async function applyHostedProxyMcpConfig(platform, proxyUrl, shortName, apiKey,
1883
2000
  }
1884
2001
  printPlatformSnippet(platform, proxyUrl, shortName, apiKey);
1885
2002
  }
1886
- function gooseHostedProxyYaml(shortName, proxyUrl, bearerHeader) {
1887
- return `extensions:
1888
- ${shortName}:
2003
+ function printGooseConfigYamlParseErrorToStderr() {
2004
+ process.stderr.write(
2005
+ style.yellow("!") + " Could not parse ~/.config/goose/config.yaml - check for syntax errors or invalid YAML\n"
2006
+ );
2007
+ }
2008
+ function gooseExtensionYaml(shortName, proxyUrl, bearerHeader) {
2009
+ const sn = sanitiseYamlValue(shortName);
2010
+ const urlEsc = sanitiseYamlValue(proxyUrl);
2011
+ const authEsc = sanitiseYamlValue(bearerHeader);
2012
+ return ` ${sn}:
2013
+ enabled: true
1889
2014
  type: streamable_http
1890
- url: ${proxyUrl}
2015
+ name: ${sn}
2016
+ description: ''
2017
+ uri: ${urlEsc}
2018
+ envs: {}
2019
+ env_keys: []
1891
2020
  headers:
1892
- Authorization: ${bearerHeader}
1893
- enabled: true
2021
+ Authorization: ${authEsc}
1894
2022
  timeout: 300
2023
+ socket: null
2024
+ bundled: null
2025
+ available_tools: []
1895
2026
  `;
1896
2027
  }
2028
+ function gooseHostedProxyYaml(shortName, proxyUrl, bearerHeader) {
2029
+ return `extensions:
2030
+ ` + gooseExtensionYaml(shortName, proxyUrl, bearerHeader);
2031
+ }
2032
+ function isYamlPlainObject(value) {
2033
+ return value !== null && typeof value === "object" && !Array.isArray(value);
2034
+ }
2035
+ async function mergeGooseConfig(shortName, proxyUrl, apiKey) {
2036
+ const filePath = join(homedir(), ".config", "goose", "config.yaml");
2037
+ const bearerHeader = `Bearer ${apiKey}`;
2038
+ let content = "";
2039
+ try {
2040
+ content = await readFile(filePath, "utf8");
2041
+ } catch (e) {
2042
+ if (isErrnoException(e) && e.code === "ENOENT") {
2043
+ content = "";
2044
+ } else {
2045
+ throw e;
2046
+ }
2047
+ }
2048
+ let root;
2049
+ try {
2050
+ const data = content.trim().length === 0 ? {} : parse(content);
2051
+ if (data === null || typeof data !== "object" || Array.isArray(data)) {
2052
+ printGooseConfigYamlParseErrorToStderr();
2053
+ return "parse-error";
2054
+ }
2055
+ root = data;
2056
+ } catch {
2057
+ printGooseConfigYamlParseErrorToStderr();
2058
+ return "parse-error";
2059
+ }
2060
+ const extensionsRaw = root["extensions"];
2061
+ let extensions;
2062
+ if (isYamlPlainObject(extensionsRaw)) {
2063
+ extensions = { ...extensionsRaw };
2064
+ } else if (extensionsRaw === void 0) {
2065
+ extensions = {};
2066
+ } else {
2067
+ printGooseConfigYamlParseErrorToStderr();
2068
+ return "parse-error";
2069
+ }
2070
+ extensions[shortName] = {
2071
+ enabled: true,
2072
+ type: "streamable_http",
2073
+ name: shortName,
2074
+ description: "",
2075
+ uri: proxyUrl,
2076
+ envs: {},
2077
+ env_keys: [],
2078
+ headers: { Authorization: bearerHeader },
2079
+ timeout: 300,
2080
+ socket: null,
2081
+ bundled: null,
2082
+ available_tools: []
2083
+ };
2084
+ root["extensions"] = extensions;
2085
+ const out = stringify(root, { indent: 2, lineWidth: 0 });
2086
+ const body = out.endsWith("\n") ? out : `${out}
2087
+ `;
2088
+ await mkdir(dirname(filePath), { recursive: true });
2089
+ await writeFile(filePath, body, SECRET_JSON_FILE_OPTIONS);
2090
+ writeMcpAddedLine(shortName, filePath);
2091
+ return "ok";
2092
+ }
1897
2093
  function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
1898
2094
  const hostedInlinePlatforms = /* @__PURE__ */ new Set([
1899
2095
  "cursor",
@@ -1913,14 +2109,12 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
1913
2109
  if (platform === "github-copilot") {
1914
2110
  snippetText = JSON.stringify(
1915
2111
  {
1916
- mcp: {
1917
- servers: {
1918
- [shortName]: {
1919
- type: "http",
1920
- url: urlInSnippet,
1921
- headers: {
1922
- Authorization: authHeader
1923
- }
2112
+ servers: {
2113
+ [shortName]: {
2114
+ type: "http",
2115
+ url: urlInSnippet,
2116
+ headers: {
2117
+ Authorization: authHeader
1924
2118
  }
1925
2119
  }
1926
2120
  }
@@ -1959,18 +2153,32 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
1959
2153
  2
1960
2154
  );
1961
2155
  } else if (platform === "continue-dev") {
2156
+ const sn = sanitiseYamlValue(shortName);
2157
+ const urlEsc = sanitiseYamlValue(urlInSnippet);
2158
+ const authEsc = sanitiseYamlValue(authHeader);
2159
+ snippetText = `name: ${sn}
2160
+ version: 0.0.1
2161
+ schema: v1
2162
+ mcpServers:
2163
+ - name: ${sn}
2164
+ type: streamable-http
2165
+ url: ${urlEsc}
2166
+ headers:
2167
+ Authorization: ${authEsc}
2168
+ `;
2169
+ } else if (platform === "kilo-code") {
1962
2170
  snippetText = JSON.stringify(
1963
2171
  {
1964
- mcpServers: [
1965
- {
1966
- name: shortName,
1967
- type: "streamable-http",
2172
+ mcp: {
2173
+ [shortName]: {
2174
+ type: "remote",
1968
2175
  url: urlInSnippet,
1969
2176
  headers: {
1970
2177
  Authorization: authHeader
1971
- }
2178
+ },
2179
+ enabled: true
1972
2180
  }
1973
- ]
2181
+ }
1974
2182
  },
1975
2183
  null,
1976
2184
  2
@@ -2010,16 +2218,18 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
2010
2218
  );
2011
2219
  } else if (platform === "kilo-code") {
2012
2220
  process.stderr.write(
2013
- "\n" + style.dim(`Add this to ${join(resolve(process.cwd()), ".kilocode", "mcp.json")}:`) + "\n\n"
2221
+ "\n" + style.dim(`Add this to ${join(resolve(process.cwd()), ".kilo", "kilo.jsonc")}:`) + "\n\n"
2014
2222
  );
2015
2223
  } else if (platform === "github-copilot") {
2016
2224
  process.stderr.write(
2017
2225
  "\n" + style.dim(
2018
- "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."
2226
+ "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."
2019
2227
  ) + "\n\n"
2020
2228
  );
2021
2229
  } else if (platform === "continue-dev") {
2022
- process.stderr.write("\n" + style.dim(`Add this to ${getContinueConfigJsonPath()}:`) + "\n\n");
2230
+ process.stderr.write(
2231
+ "\n" + style.dim(`Save this as .continue/mcpServers/${shortName}.yaml in your workspace root.`) + "\n\n"
2232
+ );
2023
2233
  } else if (platform === "goose") {
2024
2234
  process.stderr.write(
2025
2235
  "\n" + style.dim("Add this to ~/.config/goose/config.yaml under the extensions key.") + "\n\n"
@@ -2195,7 +2405,7 @@ async function runInit(explicitBaseUrl, options) {
2195
2405
  }
2196
2406
  await warnIfInstalledShieldIsOutdated();
2197
2407
  const configuredAgents = [];
2198
- let currentAgents = collectAgentsFromConfig(existing);
2408
+ let currentAgents = mergeAgentsForUniqueNames(collectAgentsFromConfig(existing));
2199
2409
  let lastConfig = {
2200
2410
  apiKey,
2201
2411
  baseUrl: resolvedBaseUrl,
@@ -2813,6 +3023,14 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2813
3023
  }
2814
3024
  rl.close();
2815
3025
  if (configuredAgents.length > 0) {
3026
+ let mcpPromptLabel2 = function(platformSlug) {
3027
+ const rows = configuredAgents.filter((a) => a.platform === platformSlug);
3028
+ const last = rows[rows.length - 1];
3029
+ if (last === void 0) return "shield-mcp";
3030
+ const s = typeof last.shortName === "string" ? last.shortName.trim() : "";
3031
+ if (s.length > 0) return s;
3032
+ return last.agentName.trim().length > 0 ? last.agentName.trim() : "shield-mcp";
3033
+ };
2816
3034
  process.stderr.write("\n" + style.bold(style.violet("Setup complete")) + "\n\n");
2817
3035
  for (const agent of configuredAgents) {
2818
3036
  const namePart = agent.agentName.length > 0 ? ` - ${style.cyan(agent.agentName)}` : "";
@@ -2824,14 +3042,6 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2824
3042
  }
2825
3043
  process.stderr.write("\n");
2826
3044
  const configuredPlatforms = new Set(configuredAgents.map((a) => a.platform));
2827
- const cursorMcpPromptLabel = (() => {
2828
- const rows = configuredAgents.filter((a) => a.platform === "cursor");
2829
- const last = rows[rows.length - 1];
2830
- if (last === void 0) return "shield-mcp";
2831
- const s = typeof last.shortName === "string" ? last.shortName.trim() : "";
2832
- if (s.length > 0) return s;
2833
- return last.agentName.trim().length > 0 ? last.agentName.trim() : "shield-mcp";
2834
- })();
2835
3045
  const blocks = [];
2836
3046
  if (configuredPlatforms.has("openclaw")) {
2837
3047
  blocks.push(
@@ -2844,33 +3054,39 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2844
3054
  );
2845
3055
  }
2846
3056
  if (configuredPlatforms.has("claude-desktop")) {
3057
+ const cdLabel = mcpPromptLabel2("claude-desktop");
2847
3058
  blocks.push(
2848
- "\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"
3059
+ "\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'
2849
3060
  );
2850
3061
  }
2851
3062
  if (configuredPlatforms.has("cursor")) {
3063
+ const cursorLabel = mcpPromptLabel2("cursor");
2852
3064
  blocks.push(
2853
- "\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'
3065
+ "\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'
2854
3066
  );
2855
3067
  }
2856
3068
  if (configuredPlatforms.has("kilo-code")) {
3069
+ const kiloLabel = mcpPromptLabel2("kilo-code");
2857
3070
  blocks.push(
2858
- "\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"
3071
+ "\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'
2859
3072
  );
2860
3073
  }
2861
3074
  if (configuredPlatforms.has("github-copilot")) {
3075
+ const copilotLabel = mcpPromptLabel2("github-copilot");
2862
3076
  blocks.push(
2863
- "\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"
3077
+ "\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'
2864
3078
  );
2865
3079
  }
2866
3080
  if (configuredPlatforms.has("continue-dev")) {
3081
+ const continueLabel = mcpPromptLabel2("continue-dev");
2867
3082
  blocks.push(
2868
- "\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"
3083
+ "\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'
2869
3084
  );
2870
3085
  }
2871
3086
  if (configuredPlatforms.has("goose")) {
3087
+ const gooseLabel = mcpPromptLabel2("goose");
2872
3088
  blocks.push(
2873
- "\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"
3089
+ "\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'
2874
3090
  );
2875
3091
  }
2876
3092
  const windsurfNativeConfigured = configuredAgents.some(
@@ -3438,9 +3654,13 @@ function extractToolCallParams(request) {
3438
3654
  if (typeof args !== "object" || args === null) return null;
3439
3655
  return { name, arguments: args };
3440
3656
  }
3441
- function buildBlockedResponse(id, service, permissionLevel, dashboardUrl) {
3657
+ function buildBlockedResponse(id, service, _permissionLevel, dashboardUrl) {
3442
3658
  const displayService = capitalize(service);
3443
- const message = `Action blocked by Multicorn Shield: agent does not have ${permissionLevel} access to ${displayService}. Configure permissions at ${dashboardUrl}`;
3659
+ const message = `Action blocked by Shield
3660
+
3661
+ This agent cannot use ${displayService}.
3662
+
3663
+ Configure permissions: ${dashboardUrl}`;
3444
3664
  return {
3445
3665
  jsonrpc: "2.0",
3446
3666
  id,
@@ -3974,6 +4194,193 @@ var init_restore = __esm({
3974
4194
  }
3975
4195
  });
3976
4196
 
4197
+ // package.json
4198
+ var package_default;
4199
+ var init_package = __esm({
4200
+ "package.json"() {
4201
+ package_default = {
4202
+ name: "multicorn-shield",
4203
+ version: "1.7.0",
4204
+ description: "The control layer for AI agents: permissions, consent, spending limits, and audit logging.",
4205
+ license: "MIT",
4206
+ author: "Multicorn AI Pty Ltd",
4207
+ type: "module",
4208
+ main: "./dist/index.cjs",
4209
+ module: "./dist/index.js",
4210
+ types: "./dist/index.d.ts",
4211
+ exports: {
4212
+ ".": {
4213
+ import: {
4214
+ types: "./dist/index.d.ts",
4215
+ default: "./dist/index.js"
4216
+ },
4217
+ require: {
4218
+ types: "./dist/index.d.cts",
4219
+ default: "./dist/index.cjs"
4220
+ }
4221
+ },
4222
+ "./proxy": {
4223
+ import: {
4224
+ types: "./dist/proxy.d.ts",
4225
+ default: "./dist/proxy.js"
4226
+ },
4227
+ require: {
4228
+ types: "./dist/proxy.d.cts",
4229
+ default: "./dist/proxy.cjs"
4230
+ }
4231
+ }
4232
+ },
4233
+ bin: {
4234
+ "multicorn-shield": "./dist/multicorn-shield.js",
4235
+ "multicorn-proxy": "./dist/multicorn-proxy.js"
4236
+ },
4237
+ files: [
4238
+ "dist",
4239
+ "plugins/multicorn-shield",
4240
+ "plugins/windsurf",
4241
+ "plugins/cline",
4242
+ "plugins/gemini-cli",
4243
+ "LICENSE",
4244
+ "README.md",
4245
+ "CHANGELOG.md"
4246
+ ],
4247
+ publishConfig: {
4248
+ access: "public",
4249
+ provenance: true
4250
+ },
4251
+ sideEffects: [
4252
+ "dist/index.js",
4253
+ "dist/index.cjs",
4254
+ "dist/badge.js",
4255
+ "src/badge/multicorn-badge.ts"
4256
+ ],
4257
+ engines: {
4258
+ node: ">=20"
4259
+ },
4260
+ scripts: {
4261
+ build: "tsup",
4262
+ dev: "tsup --watch",
4263
+ lint: "eslint . --no-warn-ignored && prettier --check .",
4264
+ "lint:fix": "eslint --fix . --no-warn-ignored && prettier --write .",
4265
+ test: "vitest run",
4266
+ "test:watch": "vitest",
4267
+ "test:coverage": "vitest run --coverage",
4268
+ typecheck: "tsc --noEmit",
4269
+ docs: "typedoc",
4270
+ clean: "rm -rf dist coverage docs/api extension-pack",
4271
+ "stage-extension-pack": "rm -rf extension-pack && mkdir -p extension-pack/server && cp manifest.json extension-pack/ && cp icon.png extension-pack/ && cp dist/shield-extension.js extension-pack/server/index.js",
4272
+ "validate:extension": "pnpm run stage-extension-pack && mcpb validate extension-pack/manifest.json",
4273
+ "build:extension": "tsup",
4274
+ "pack:extension": "pnpm run build && pnpm run stage-extension-pack && mcpb validate extension-pack/manifest.json && mcpb pack extension-pack dist/multicorn-shield.mcpb",
4275
+ size: "size-limit",
4276
+ prepublishOnly: "pnpm run clean && pnpm run typecheck && pnpm run lint && pnpm run test && pnpm run build",
4277
+ prepare: "husky || true",
4278
+ "release:patch": "npm version patch && pnpm publish",
4279
+ "release:minor": "npm version minor && pnpm publish",
4280
+ "release:major": "npm version major && pnpm publish"
4281
+ },
4282
+ "lint-staged": {
4283
+ "*.ts": [
4284
+ "eslint --fix --no-warn-ignored",
4285
+ "prettier --write"
4286
+ ],
4287
+ "*.{json,md}": [
4288
+ "prettier --write"
4289
+ ]
4290
+ },
4291
+ dependencies: {
4292
+ "@modelcontextprotocol/sdk": "^1.27.1",
4293
+ lit: "^3.2.0",
4294
+ yaml: "^2.8.2",
4295
+ zod: "^4.3.6"
4296
+ },
4297
+ devDependencies: {
4298
+ "@anthropic-ai/mcpb": "^2.1.2",
4299
+ "@eslint/js": "^9.19.0",
4300
+ "@open-wc/testing-helpers": "^3.0.1",
4301
+ "@size-limit/file": "^11.1.6",
4302
+ "@types/node": "^22.0.0",
4303
+ "@vitest/coverage-v8": "^3.0.5",
4304
+ eslint: "^9.19.0",
4305
+ "eslint-config-prettier": "^10.0.1",
4306
+ "eslint-plugin-unicorn": "^57.0.0",
4307
+ globals: "^15.14.0",
4308
+ husky: "^9.1.7",
4309
+ jiti: "^2.4.2",
4310
+ jsdom: "^25.0.1",
4311
+ "lint-staged": "^16.2.7",
4312
+ prettier: "^3.4.2",
4313
+ "size-limit": "^11.1.6",
4314
+ tsup: "^8.3.6",
4315
+ typedoc: "^0.28.17",
4316
+ typescript: "^5.7.3",
4317
+ "typescript-eslint": "^8.22.0",
4318
+ vite: "^7.3.2",
4319
+ vitest: "^3.0.5"
4320
+ },
4321
+ "size-limit": [
4322
+ {
4323
+ path: "dist/index.js",
4324
+ limit: "50 kB",
4325
+ gzip: true
4326
+ },
4327
+ {
4328
+ path: "dist/index.cjs",
4329
+ limit: "50 kB",
4330
+ gzip: true
4331
+ },
4332
+ {
4333
+ path: "dist/badge.js",
4334
+ limit: "5 kB",
4335
+ gzip: true
4336
+ }
4337
+ ],
4338
+ keywords: [
4339
+ "ai",
4340
+ "agents",
4341
+ "permissions",
4342
+ "sdk",
4343
+ "typescript",
4344
+ "consent",
4345
+ "spending-limits",
4346
+ "audit-log",
4347
+ "mcp",
4348
+ "shield",
4349
+ "multicorn"
4350
+ ],
4351
+ repository: {
4352
+ type: "git",
4353
+ url: "git+https://github.com/Multicorn-AI/multicorn-shield.git"
4354
+ },
4355
+ bugs: {
4356
+ url: "https://github.com/Multicorn-AI/multicorn-shield/issues"
4357
+ },
4358
+ homepage: "https://multicorn.ai",
4359
+ pnpm: {
4360
+ overrides: {
4361
+ vite: ">=7.3.2",
4362
+ flatted: ">=3.4.2",
4363
+ minimatch: ">=10.2.3",
4364
+ rollup: ">=4.59.0",
4365
+ picomatch: ">=4.0.4",
4366
+ "path-to-regexp": ">=8.4.0",
4367
+ "node-forge": ">=1.4.0",
4368
+ "fast-uri": ">=3.1.2"
4369
+ }
4370
+ }
4371
+ };
4372
+ }
4373
+ });
4374
+
4375
+ // src/package-meta.ts
4376
+ var PACKAGE_VERSION;
4377
+ var init_package_meta = __esm({
4378
+ "src/package-meta.ts"() {
4379
+ init_package();
4380
+ PACKAGE_VERSION = package_default.version;
4381
+ }
4382
+ });
4383
+
3977
4384
  // bin/multicorn-shield.ts
3978
4385
  var multicorn_shield_exports = {};
3979
4386
  __export(multicorn_shield_exports, {
@@ -4132,6 +4539,7 @@ function printHelp() {
4132
4539
  " Shield's permission layer.",
4133
4540
  "",
4134
4541
  "Options:",
4542
+ " --version, -v Print version and exit",
4135
4543
  " --verbose, --debug Print extra diagnostics during init (menu selection, agent counts)",
4136
4544
  " --api-key <key> Multicorn API key (overrides MULTICORN_API_KEY env var and config file)",
4137
4545
  " --log-level <level> Log level: debug | info | warn | error (default: info)",
@@ -4156,6 +4564,11 @@ async function runCli() {
4156
4564
  );
4157
4565
  return;
4158
4566
  }
4567
+ if (first === "--version" || first === "-v") {
4568
+ process.stdout.write(`${PACKAGE_VERSION}
4569
+ `);
4570
+ process.exit(0);
4571
+ }
4159
4572
  const cli = parseArgs(process.argv);
4160
4573
  const logger = createLogger(cli.logLevel);
4161
4574
  if (cli.subcommand === "help") {
@@ -4302,6 +4715,7 @@ var init_multicorn_shield = __esm({
4302
4715
  init_logger();
4303
4716
  init_consent();
4304
4717
  init_restore();
4718
+ init_package_meta();
4305
4719
  isDirectRun = process.argv[1] !== void 0 && (import.meta.url.endsWith(process.argv[1]) || import.meta.url === `file://${process.argv[1]}` || import.meta.url.endsWith("/multicorn-shield.js") || import.meta.url.endsWith("/multicorn-shield.ts"));
4306
4720
  if (isDirectRun && process.env["VITEST"] === void 0) {
4307
4721
  runCli().catch((error) => {