multicorn-shield 1.3.1 → 1.3.4
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/CHANGELOG.md +27 -0
- package/dist/multicorn-proxy.js +495 -79
- package/dist/multicorn-shield.js +494 -78
- package/dist/openclaw-plugin/multicorn-shield.js +9 -0
- package/dist/shield-extension.js +1 -1
- package/package.json +1 -1
package/dist/multicorn-shield.js
CHANGED
|
@@ -344,6 +344,7 @@ function isPermissionShape(value) {
|
|
|
344
344
|
}
|
|
345
345
|
|
|
346
346
|
// src/proxy/config.ts
|
|
347
|
+
var SECRET_JSON_FILE_OPTIONS = { encoding: "utf8", mode: 384 };
|
|
347
348
|
var style = {
|
|
348
349
|
violet: (s) => `\x1B[38;2;124;58;237m${s}\x1B[0m`,
|
|
349
350
|
violetLight: (s) => `\x1B[38;2;167;139;250m${s}\x1B[0m`,
|
|
@@ -703,9 +704,11 @@ async function updateOpenClawConfigIfPresent(apiKey, baseUrl, agentName) {
|
|
|
703
704
|
}
|
|
704
705
|
}
|
|
705
706
|
}
|
|
706
|
-
await writeFile(
|
|
707
|
-
|
|
708
|
-
|
|
707
|
+
await writeFile(
|
|
708
|
+
OPENCLAW_CONFIG_PATH,
|
|
709
|
+
JSON.stringify(obj, null, 2) + "\n",
|
|
710
|
+
SECRET_JSON_FILE_OPTIONS
|
|
711
|
+
);
|
|
709
712
|
return "updated";
|
|
710
713
|
}
|
|
711
714
|
async function validateApiKey(apiKey, baseUrl) {
|
|
@@ -867,7 +870,7 @@ async function installWindsurfNativeHooks() {
|
|
|
867
870
|
base["hooks"] = nextHooks;
|
|
868
871
|
const hooksDir = dirname(hooksPath);
|
|
869
872
|
await mkdir(hooksDir, { recursive: true });
|
|
870
|
-
await writeFile(hooksPath, JSON.stringify(base, null, 2) + "\n",
|
|
873
|
+
await writeFile(hooksPath, JSON.stringify(base, null, 2) + "\n", SECRET_JSON_FILE_OPTIONS);
|
|
871
874
|
}
|
|
872
875
|
function getClineHooksInstallDir() {
|
|
873
876
|
return join(homedir(), ".multicorn", "cline-hooks");
|
|
@@ -946,6 +949,37 @@ function getGeminiCliHooksInstallDir() {
|
|
|
946
949
|
function getGeminiCliSettingsPath() {
|
|
947
950
|
return join(homedir(), ".gemini", "settings.json");
|
|
948
951
|
}
|
|
952
|
+
async function mergeGeminiHostedMcpServersIntoSettings(shortName, proxyUrl, apiKey) {
|
|
953
|
+
const settingsPath = getGeminiCliSettingsPath();
|
|
954
|
+
let existing = {};
|
|
955
|
+
try {
|
|
956
|
+
const rawText = await readFile(settingsPath, "utf8");
|
|
957
|
+
const parsed = JSON.parse(rawText);
|
|
958
|
+
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
959
|
+
existing = parsed;
|
|
960
|
+
}
|
|
961
|
+
} catch (err) {
|
|
962
|
+
if (isErrnoException(err) && err.code === "ENOENT") {
|
|
963
|
+
existing = {};
|
|
964
|
+
} else {
|
|
965
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
966
|
+
throw new Error(`Could not read or parse Gemini CLI settings at ${settingsPath}: ${detail}`);
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
const mcpRaw = existing["mcpServers"];
|
|
970
|
+
const mcpServers = typeof mcpRaw === "object" && mcpRaw !== null && !Array.isArray(mcpRaw) ? { ...mcpRaw } : {};
|
|
971
|
+
mcpServers[shortName] = {
|
|
972
|
+
httpUrl: proxyUrl,
|
|
973
|
+
headers: {
|
|
974
|
+
Authorization: `Bearer ${apiKey}`
|
|
975
|
+
}
|
|
976
|
+
};
|
|
977
|
+
const out = { ...existing, mcpServers };
|
|
978
|
+
await mkdir(dirname(settingsPath), { recursive: true });
|
|
979
|
+
const serialized = JSON.stringify(out, null, 2) + "\n";
|
|
980
|
+
await writeFile(settingsPath, serialized, SECRET_JSON_FILE_OPTIONS);
|
|
981
|
+
writeMcpAddedLine(shortName, settingsPath);
|
|
982
|
+
}
|
|
949
983
|
function geminiInnerHooksReferenceShield(inner, multicornName) {
|
|
950
984
|
if (!Array.isArray(inner)) return false;
|
|
951
985
|
for (const h of inner) {
|
|
@@ -1130,7 +1164,7 @@ async function installClaudeCodeUserSettingsHooks(ask) {
|
|
|
1130
1164
|
hooksObj["PostToolUse"] = postArr;
|
|
1131
1165
|
const out = { ...existing, hooks: hooksObj };
|
|
1132
1166
|
const serialized = JSON.stringify(out, null, 2) + "\n";
|
|
1133
|
-
await writeFile(settingsPath, serialized,
|
|
1167
|
+
await writeFile(settingsPath, serialized, SECRET_JSON_FILE_OPTIONS);
|
|
1134
1168
|
process.stderr.write(
|
|
1135
1169
|
"\n" + style.dim("Wrote ") + style.cyan(settingsPath) + style.dim(":") + "\n"
|
|
1136
1170
|
);
|
|
@@ -1232,7 +1266,7 @@ async function installGeminiCliNativeHooks(ask) {
|
|
|
1232
1266
|
AfterTool: afterArr
|
|
1233
1267
|
};
|
|
1234
1268
|
await mkdir(dirname(settingsPath), { recursive: true });
|
|
1235
|
-
await writeFile(settingsPath, JSON.stringify(existing, null, 2) + "\n",
|
|
1269
|
+
await writeFile(settingsPath, JSON.stringify(existing, null, 2) + "\n", SECRET_JSON_FILE_OPTIONS);
|
|
1236
1270
|
}
|
|
1237
1271
|
async function promptGeminiCliIntegrationMode(ask) {
|
|
1238
1272
|
process.stderr.write("\n" + style.bold("Gemini CLI integration") + "\n");
|
|
@@ -1271,6 +1305,52 @@ function getClaudeDesktopConfigPath() {
|
|
|
1271
1305
|
);
|
|
1272
1306
|
}
|
|
1273
1307
|
}
|
|
1308
|
+
function getCursorMcpJsonPath() {
|
|
1309
|
+
return join(homedir(), ".cursor", "mcp.json");
|
|
1310
|
+
}
|
|
1311
|
+
function getWindsurfMcpConfigPath() {
|
|
1312
|
+
return join(homedir(), ".codeium", "windsurf", "mcp_config.json");
|
|
1313
|
+
}
|
|
1314
|
+
function getClineMcpSettingsPath() {
|
|
1315
|
+
switch (process.platform) {
|
|
1316
|
+
case "win32":
|
|
1317
|
+
return join(
|
|
1318
|
+
process.env["APPDATA"] ?? join(homedir(), "AppData", "Roaming"),
|
|
1319
|
+
"Code",
|
|
1320
|
+
"User",
|
|
1321
|
+
"globalStorage",
|
|
1322
|
+
"saoudrizwan.claude-dev",
|
|
1323
|
+
"settings",
|
|
1324
|
+
"cline_mcp_settings.json"
|
|
1325
|
+
);
|
|
1326
|
+
case "linux":
|
|
1327
|
+
return join(
|
|
1328
|
+
homedir(),
|
|
1329
|
+
".config",
|
|
1330
|
+
"Code",
|
|
1331
|
+
"User",
|
|
1332
|
+
"globalStorage",
|
|
1333
|
+
"saoudrizwan.claude-dev",
|
|
1334
|
+
"settings",
|
|
1335
|
+
"cline_mcp_settings.json"
|
|
1336
|
+
);
|
|
1337
|
+
default:
|
|
1338
|
+
return join(
|
|
1339
|
+
homedir(),
|
|
1340
|
+
"Library",
|
|
1341
|
+
"Application Support",
|
|
1342
|
+
"Code",
|
|
1343
|
+
"User",
|
|
1344
|
+
"globalStorage",
|
|
1345
|
+
"saoudrizwan.claude-dev",
|
|
1346
|
+
"settings",
|
|
1347
|
+
"cline_mcp_settings.json"
|
|
1348
|
+
);
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
function getContinueConfigJsonPath() {
|
|
1352
|
+
return join(homedir(), ".continue", "config.json");
|
|
1353
|
+
}
|
|
1274
1354
|
var INIT_WIZARD_PLATFORM_REGISTRY = [
|
|
1275
1355
|
{ slug: "openclaw", displayName: "OpenClaw", section: "native" },
|
|
1276
1356
|
{ slug: "claude-code", displayName: "Claude Code", section: "native" },
|
|
@@ -1460,9 +1540,11 @@ async function arrowSelect(options, ask, fallbackLabel) {
|
|
|
1460
1540
|
process.stdin.on("data", onData);
|
|
1461
1541
|
});
|
|
1462
1542
|
}
|
|
1463
|
-
async function promptAgentName(ask, platform) {
|
|
1543
|
+
async function promptAgentName(ask, platform, defaultNameOverride) {
|
|
1464
1544
|
const dirPart = normalizeAgentName(basename(process.cwd()));
|
|
1465
|
-
const
|
|
1545
|
+
const computedDefault = dirPart.length > 0 ? normalizeAgentName(`${dirPart}-${platform}`) || platform : normalizeAgentName(platform) || platform;
|
|
1546
|
+
const fromOverride = defaultNameOverride !== void 0 && defaultNameOverride.trim().length > 0 ? normalizeAgentName(defaultNameOverride.trim()) : "";
|
|
1547
|
+
const defaultAgentName = fromOverride.length > 0 ? fromOverride : computedDefault;
|
|
1466
1548
|
let agentName = "";
|
|
1467
1549
|
while (agentName.length === 0) {
|
|
1468
1550
|
const input = await ask(
|
|
@@ -1553,6 +1635,240 @@ async function createProxyConfig(baseUrl, apiKey, agentName, targetUrl, serverNa
|
|
|
1553
1635
|
const data = envelope["data"];
|
|
1554
1636
|
return typeof data?.["proxy_url"] === "string" ? data["proxy_url"] : "";
|
|
1555
1637
|
}
|
|
1638
|
+
function writeMcpAddedLine(shortName, filePath) {
|
|
1639
|
+
process.stderr.write(
|
|
1640
|
+
style.green("\u2713") + ' MCP server "' + shortName + '" added to ' + style.cyan(filePath) + "\n"
|
|
1641
|
+
);
|
|
1642
|
+
}
|
|
1643
|
+
async function mergeMcpServersObjectStyle(filePath, shortName, entry) {
|
|
1644
|
+
let root = {};
|
|
1645
|
+
try {
|
|
1646
|
+
const raw = await readFile(filePath, "utf8");
|
|
1647
|
+
let parsed;
|
|
1648
|
+
try {
|
|
1649
|
+
parsed = JSON.parse(raw);
|
|
1650
|
+
} catch {
|
|
1651
|
+
return "parse-error";
|
|
1652
|
+
}
|
|
1653
|
+
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
1654
|
+
root = parsed;
|
|
1655
|
+
} else {
|
|
1656
|
+
return "parse-error";
|
|
1657
|
+
}
|
|
1658
|
+
} catch (e) {
|
|
1659
|
+
if (isErrnoException(e) && e.code === "ENOENT") {
|
|
1660
|
+
root = {};
|
|
1661
|
+
} else {
|
|
1662
|
+
throw e;
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
const mcpRaw = root["mcpServers"];
|
|
1666
|
+
const mcpServers = typeof mcpRaw === "object" && mcpRaw !== null && !Array.isArray(mcpRaw) ? { ...mcpRaw } : {};
|
|
1667
|
+
mcpServers[shortName] = entry;
|
|
1668
|
+
root["mcpServers"] = mcpServers;
|
|
1669
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
1670
|
+
await writeFile(filePath, JSON.stringify(root, null, 2) + "\n", SECRET_JSON_FILE_OPTIONS);
|
|
1671
|
+
writeMcpAddedLine(shortName, filePath);
|
|
1672
|
+
return "ok";
|
|
1673
|
+
}
|
|
1674
|
+
async function mergeClaudeDesktopHostedMcpRemote(shortName, proxyUrl, apiKey) {
|
|
1675
|
+
const entry = {
|
|
1676
|
+
command: "npx",
|
|
1677
|
+
args: ["-y", "mcp-remote", proxyUrl, "--header", `Authorization: Bearer ${apiKey}`]
|
|
1678
|
+
};
|
|
1679
|
+
return mergeMcpServersObjectStyle(getClaudeDesktopConfigPath(), shortName, entry);
|
|
1680
|
+
}
|
|
1681
|
+
async function mergeContinueHostedMcp(shortName, proxyUrl, apiKey) {
|
|
1682
|
+
const filePath = getContinueConfigJsonPath();
|
|
1683
|
+
let root = {};
|
|
1684
|
+
try {
|
|
1685
|
+
const raw = await readFile(filePath, "utf8");
|
|
1686
|
+
let parsed;
|
|
1687
|
+
try {
|
|
1688
|
+
parsed = JSON.parse(raw);
|
|
1689
|
+
} catch {
|
|
1690
|
+
return "parse-error";
|
|
1691
|
+
}
|
|
1692
|
+
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
1693
|
+
root = parsed;
|
|
1694
|
+
} else {
|
|
1695
|
+
return "parse-error";
|
|
1696
|
+
}
|
|
1697
|
+
} catch (e) {
|
|
1698
|
+
if (isErrnoException(e) && e.code === "ENOENT") {
|
|
1699
|
+
root = {};
|
|
1700
|
+
} else {
|
|
1701
|
+
throw e;
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
const rawServers = root["mcpServers"];
|
|
1705
|
+
if (rawServers !== void 0) {
|
|
1706
|
+
if (Array.isArray(rawServers)) ; else if (typeof rawServers === "object" && rawServers !== null) {
|
|
1707
|
+
return "parse-error";
|
|
1708
|
+
} else {
|
|
1709
|
+
return "parse-error";
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
const servers = Array.isArray(rawServers) ? rawServers.map((s) => ({ ...s })) : [];
|
|
1713
|
+
const entry = {
|
|
1714
|
+
name: shortName,
|
|
1715
|
+
type: "streamable-http",
|
|
1716
|
+
url: proxyUrl,
|
|
1717
|
+
headers: {
|
|
1718
|
+
Authorization: `Bearer ${apiKey}`
|
|
1719
|
+
}
|
|
1720
|
+
};
|
|
1721
|
+
const idx = servers.findIndex((s) => s["name"] === shortName);
|
|
1722
|
+
if (idx >= 0) {
|
|
1723
|
+
servers[idx] = entry;
|
|
1724
|
+
} else {
|
|
1725
|
+
servers.push(entry);
|
|
1726
|
+
}
|
|
1727
|
+
root["mcpServers"] = servers;
|
|
1728
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
1729
|
+
await writeFile(filePath, JSON.stringify(root, null, 2) + "\n", SECRET_JSON_FILE_OPTIONS);
|
|
1730
|
+
writeMcpAddedLine(shortName, filePath);
|
|
1731
|
+
return "ok";
|
|
1732
|
+
}
|
|
1733
|
+
async function mergeKiloCodeProjectMcp(workspacePath, shortName, proxyUrl, apiKey) {
|
|
1734
|
+
const filePath = join(workspacePath, ".kilocode", "mcp.json");
|
|
1735
|
+
return mergeMcpServersObjectStyle(filePath, shortName, {
|
|
1736
|
+
url: proxyUrl,
|
|
1737
|
+
headers: {
|
|
1738
|
+
Authorization: `Bearer ${apiKey}`
|
|
1739
|
+
}
|
|
1740
|
+
});
|
|
1741
|
+
}
|
|
1742
|
+
function printHostedProxyJsonParseWarning(filePath) {
|
|
1743
|
+
process.stderr.write(
|
|
1744
|
+
style.yellow("\u26A0") + " Could not parse JSON at " + style.cyan(filePath) + style.dim(" - showing paste snippet instead.") + "\n"
|
|
1745
|
+
);
|
|
1746
|
+
}
|
|
1747
|
+
function printHostedProxyPostWriteHints(platform, shortName) {
|
|
1748
|
+
if (platform === "cursor") {
|
|
1749
|
+
process.stderr.write(
|
|
1750
|
+
style.dim("Restart Cursor and check Settings > Tools & MCPs for a green status indicator. ") + style.dim(`Ask Cursor to use your MCP server by its short name (e.g. ${shortName}).`) + "\n"
|
|
1751
|
+
);
|
|
1752
|
+
}
|
|
1753
|
+
if (platform === "claude-desktop") {
|
|
1754
|
+
process.stderr.write(style.dim("Restart Claude Desktop to load the MCP server.") + "\n");
|
|
1755
|
+
}
|
|
1756
|
+
if (platform === "cline") {
|
|
1757
|
+
process.stderr.write(
|
|
1758
|
+
style.dim(
|
|
1759
|
+
"Restart Cline or reload the VS Code window. Cline will discover the Shield tools automatically."
|
|
1760
|
+
) + "\n"
|
|
1761
|
+
);
|
|
1762
|
+
}
|
|
1763
|
+
if (platform === "windsurf") {
|
|
1764
|
+
process.stderr.write(style.dim("Restart Windsurf (Cmd/Ctrl+Q, then reopen).") + "\n");
|
|
1765
|
+
process.stderr.write(
|
|
1766
|
+
style.dim(
|
|
1767
|
+
"Open the Cascade panel and verify the server appears with a green status indicator."
|
|
1768
|
+
) + "\n"
|
|
1769
|
+
);
|
|
1770
|
+
}
|
|
1771
|
+
if (platform === "github-copilot" || platform === "continue-dev") {
|
|
1772
|
+
process.stderr.write(
|
|
1773
|
+
style.dim("Reload the editor window if the MCP server does not appear immediately.") + "\n"
|
|
1774
|
+
);
|
|
1775
|
+
}
|
|
1776
|
+
if (platform === "goose") {
|
|
1777
|
+
process.stderr.write(style.dim("Start a new Goose session after updating config.") + "\n");
|
|
1778
|
+
}
|
|
1779
|
+
if (platform === "kilo-code") {
|
|
1780
|
+
process.stderr.write(
|
|
1781
|
+
style.dim("Restart Kilo Code or reload the window so it picks up .kilocode/mcp.json.") + "\n"
|
|
1782
|
+
);
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
async function applyHostedProxyMcpConfig(platform, proxyUrl, shortName, apiKey, workspacePath) {
|
|
1786
|
+
const authHeader = `Bearer ${apiKey}`;
|
|
1787
|
+
if (platform === "gemini-cli") {
|
|
1788
|
+
await mergeGeminiHostedMcpServersIntoSettings(shortName, proxyUrl, apiKey);
|
|
1789
|
+
process.stderr.write(
|
|
1790
|
+
style.dim(
|
|
1791
|
+
"For project-specific config, copy the mcpServers entry into .gemini/settings.json in your project root. Restart Gemini CLI if it is already running."
|
|
1792
|
+
) + "\n"
|
|
1793
|
+
);
|
|
1794
|
+
return;
|
|
1795
|
+
}
|
|
1796
|
+
if (platform === "github-copilot") {
|
|
1797
|
+
process.stderr.write(
|
|
1798
|
+
"\n" + style.dim(
|
|
1799
|
+
"GitHub Copilot uses VS Code settings - paste the snippet below into your VS Code Settings (JSON)."
|
|
1800
|
+
) + "\n"
|
|
1801
|
+
);
|
|
1802
|
+
printPlatformSnippet(platform, proxyUrl, shortName, apiKey);
|
|
1803
|
+
return;
|
|
1804
|
+
}
|
|
1805
|
+
if (platform === "goose") {
|
|
1806
|
+
printPlatformSnippet(platform, proxyUrl, shortName, apiKey);
|
|
1807
|
+
return;
|
|
1808
|
+
}
|
|
1809
|
+
try {
|
|
1810
|
+
let result = "parse-error";
|
|
1811
|
+
if (platform === "cursor") {
|
|
1812
|
+
result = await mergeMcpServersObjectStyle(getCursorMcpJsonPath(), shortName, {
|
|
1813
|
+
url: proxyUrl,
|
|
1814
|
+
headers: { Authorization: authHeader }
|
|
1815
|
+
});
|
|
1816
|
+
if (result === "parse-error") {
|
|
1817
|
+
printHostedProxyJsonParseWarning(getCursorMcpJsonPath());
|
|
1818
|
+
}
|
|
1819
|
+
} else if (platform === "claude-desktop") {
|
|
1820
|
+
result = await mergeClaudeDesktopHostedMcpRemote(shortName, proxyUrl, apiKey);
|
|
1821
|
+
if (result === "parse-error") {
|
|
1822
|
+
printHostedProxyJsonParseWarning(getClaudeDesktopConfigPath());
|
|
1823
|
+
}
|
|
1824
|
+
} else if (platform === "windsurf") {
|
|
1825
|
+
result = await mergeMcpServersObjectStyle(getWindsurfMcpConfigPath(), shortName, {
|
|
1826
|
+
serverUrl: proxyUrl,
|
|
1827
|
+
headers: { Authorization: authHeader }
|
|
1828
|
+
});
|
|
1829
|
+
if (result === "parse-error") {
|
|
1830
|
+
printHostedProxyJsonParseWarning(getWindsurfMcpConfigPath());
|
|
1831
|
+
}
|
|
1832
|
+
} else if (platform === "cline") {
|
|
1833
|
+
result = await mergeMcpServersObjectStyle(getClineMcpSettingsPath(), shortName, {
|
|
1834
|
+
url: proxyUrl,
|
|
1835
|
+
headers: { Authorization: authHeader }
|
|
1836
|
+
});
|
|
1837
|
+
if (result === "parse-error") {
|
|
1838
|
+
printHostedProxyJsonParseWarning(getClineMcpSettingsPath());
|
|
1839
|
+
}
|
|
1840
|
+
} else if (platform === "kilo-code") {
|
|
1841
|
+
result = await mergeKiloCodeProjectMcp(workspacePath, shortName, proxyUrl, apiKey);
|
|
1842
|
+
if (result === "parse-error") {
|
|
1843
|
+
printHostedProxyJsonParseWarning(join(workspacePath, ".kilocode", "mcp.json"));
|
|
1844
|
+
}
|
|
1845
|
+
} else if (platform === "continue-dev") {
|
|
1846
|
+
result = await mergeContinueHostedMcp(shortName, proxyUrl, apiKey);
|
|
1847
|
+
if (result === "parse-error") {
|
|
1848
|
+
printHostedProxyJsonParseWarning(getContinueConfigJsonPath());
|
|
1849
|
+
}
|
|
1850
|
+
} else {
|
|
1851
|
+
result = await mergeMcpServersObjectStyle(getCursorMcpJsonPath(), shortName, {
|
|
1852
|
+
url: proxyUrl,
|
|
1853
|
+
headers: { Authorization: authHeader }
|
|
1854
|
+
});
|
|
1855
|
+
if (result === "parse-error") {
|
|
1856
|
+
printHostedProxyJsonParseWarning(getCursorMcpJsonPath());
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
if (result === "ok") {
|
|
1860
|
+
printHostedProxyPostWriteHints(platform, shortName);
|
|
1861
|
+
return;
|
|
1862
|
+
}
|
|
1863
|
+
} catch (e) {
|
|
1864
|
+
const detail = e instanceof Error ? e.message : String(e);
|
|
1865
|
+
process.stderr.write(
|
|
1866
|
+
style.yellow("\u26A0") + ` Could not write MCP config automatically (${detail}).
|
|
1867
|
+
`
|
|
1868
|
+
);
|
|
1869
|
+
}
|
|
1870
|
+
printPlatformSnippet(platform, proxyUrl, shortName, apiKey);
|
|
1871
|
+
}
|
|
1556
1872
|
function gooseHostedProxyYaml(shortName, proxyUrl, bearerHeader) {
|
|
1557
1873
|
return `extensions:
|
|
1558
1874
|
${shortName}:
|
|
@@ -1614,6 +1930,36 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
|
|
|
1614
1930
|
null,
|
|
1615
1931
|
2
|
|
1616
1932
|
);
|
|
1933
|
+
} else if (platform === "claude-desktop") {
|
|
1934
|
+
snippetText = JSON.stringify(
|
|
1935
|
+
{
|
|
1936
|
+
mcpServers: {
|
|
1937
|
+
[shortName]: {
|
|
1938
|
+
command: "npx",
|
|
1939
|
+
args: ["-y", "mcp-remote", routingToken, "--header", `Authorization: ${authHeader}`]
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
},
|
|
1943
|
+
null,
|
|
1944
|
+
2
|
|
1945
|
+
);
|
|
1946
|
+
} else if (platform === "continue-dev") {
|
|
1947
|
+
snippetText = JSON.stringify(
|
|
1948
|
+
{
|
|
1949
|
+
mcpServers: [
|
|
1950
|
+
{
|
|
1951
|
+
name: shortName,
|
|
1952
|
+
type: "streamable-http",
|
|
1953
|
+
url: routingToken,
|
|
1954
|
+
headers: {
|
|
1955
|
+
Authorization: authHeader
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
]
|
|
1959
|
+
},
|
|
1960
|
+
null,
|
|
1961
|
+
2
|
|
1962
|
+
);
|
|
1617
1963
|
} else {
|
|
1618
1964
|
const urlKey = platform === "windsurf" ? "serverUrl" : "url";
|
|
1619
1965
|
snippetText = JSON.stringify(
|
|
@@ -1638,52 +1984,33 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
|
|
|
1638
1984
|
} else if (platform === "claude-desktop") {
|
|
1639
1985
|
process.stderr.write("\n" + style.dim(`Add this to ${getClaudeDesktopConfigPath()}:`) + "\n\n");
|
|
1640
1986
|
} else if (platform === "windsurf") {
|
|
1641
|
-
process.stderr.write(
|
|
1642
|
-
"\n" + style.dim("Add this to ~/.codeium/windsurf/mcp_config.json:") + "\n\n"
|
|
1643
|
-
);
|
|
1987
|
+
process.stderr.write("\n" + style.dim(`Add this to ${getWindsurfMcpConfigPath()}:`) + "\n\n");
|
|
1644
1988
|
} else if (platform === "cline") {
|
|
1645
|
-
process.stderr.write("\n" + style.dim(
|
|
1646
|
-
process.stderr.write(
|
|
1647
|
-
style.dim(
|
|
1648
|
-
" macOS: ~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json"
|
|
1649
|
-
) + "\n"
|
|
1650
|
-
);
|
|
1651
|
-
process.stderr.write(
|
|
1652
|
-
style.dim(
|
|
1653
|
-
" Windows: %APPDATA%\\Code\\User\\globalStorage\\saoudrizwan.claude-dev\\settings\\cline_mcp_settings.json"
|
|
1654
|
-
) + "\n"
|
|
1655
|
-
);
|
|
1656
|
-
process.stderr.write(
|
|
1657
|
-
style.dim(
|
|
1658
|
-
" Linux: ~/.config/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json"
|
|
1659
|
-
) + "\n\n"
|
|
1660
|
-
);
|
|
1989
|
+
process.stderr.write("\n" + style.dim(`Add this to ${getClineMcpSettingsPath()}:`) + "\n\n");
|
|
1661
1990
|
} else if (platform === "gemini-cli") {
|
|
1662
1991
|
process.stderr.write(
|
|
1663
1992
|
"\n" + style.dim(
|
|
1664
|
-
|
|
1993
|
+
`Merge the snippet below into ${getGeminiCliSettingsPath()} (keep existing hooks and other keys). Restart Gemini CLI if it is already running.`
|
|
1665
1994
|
) + "\n\n"
|
|
1666
1995
|
);
|
|
1667
1996
|
} else if (platform === "kilo-code") {
|
|
1668
1997
|
process.stderr.write(
|
|
1669
|
-
"\n" + style.dim(
|
|
1998
|
+
"\n" + style.dim(`Add this to ${join(resolve(process.cwd()), ".kilocode", "mcp.json")}:`) + "\n\n"
|
|
1670
1999
|
);
|
|
1671
2000
|
} else if (platform === "github-copilot") {
|
|
1672
2001
|
process.stderr.write(
|
|
1673
2002
|
"\n" + style.dim(
|
|
1674
|
-
"
|
|
2003
|
+
"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."
|
|
1675
2004
|
) + "\n\n"
|
|
1676
2005
|
);
|
|
1677
2006
|
} else if (platform === "continue-dev") {
|
|
1678
|
-
process.stderr.write(
|
|
1679
|
-
"\n" + style.dim("Save this as .continue/mcpServers/shield.json in your workspace root.") + "\n\n"
|
|
1680
|
-
);
|
|
2007
|
+
process.stderr.write("\n" + style.dim(`Add this to ${getContinueConfigJsonPath()}:`) + "\n\n");
|
|
1681
2008
|
} else if (platform === "goose") {
|
|
1682
2009
|
process.stderr.write(
|
|
1683
2010
|
"\n" + style.dim("Add this to ~/.config/goose/config.yaml under the extensions key.") + "\n\n"
|
|
1684
2011
|
);
|
|
1685
2012
|
} else {
|
|
1686
|
-
process.stderr.write("\n" + style.dim(
|
|
2013
|
+
process.stderr.write("\n" + style.dim(`Add this to ${getCursorMcpJsonPath()}:`) + "\n\n");
|
|
1687
2014
|
}
|
|
1688
2015
|
process.stderr.write(style.cyan(snippetText) + "\n\n");
|
|
1689
2016
|
if (!usesInlineKey) {
|
|
@@ -1732,20 +2059,35 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
|
|
|
1732
2059
|
process.stderr.write(style.dim("Start a new Goose session after updating config.") + "\n");
|
|
1733
2060
|
}
|
|
1734
2061
|
}
|
|
2062
|
+
function dedupeAgentsByName(agents) {
|
|
2063
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2064
|
+
const out = [];
|
|
2065
|
+
for (const a of agents) {
|
|
2066
|
+
if (seen.has(a.name)) continue;
|
|
2067
|
+
seen.add(a.name);
|
|
2068
|
+
out.push(a);
|
|
2069
|
+
}
|
|
2070
|
+
return out;
|
|
2071
|
+
}
|
|
1735
2072
|
function mergeAgentsForPlatform(localAgents, remoteAgents, selectedPlatform) {
|
|
1736
|
-
const
|
|
1737
|
-
const
|
|
1738
|
-
|
|
2073
|
+
const byName = /* @__PURE__ */ new Map();
|
|
2074
|
+
for (const a of localAgents) {
|
|
2075
|
+
if (a.platform !== selectedPlatform) continue;
|
|
2076
|
+
if (!byName.has(a.name)) {
|
|
2077
|
+
byName.set(a.name, { ...a });
|
|
2078
|
+
}
|
|
2079
|
+
}
|
|
1739
2080
|
for (const r of remoteAgents) {
|
|
1740
2081
|
if (r.platform !== selectedPlatform) continue;
|
|
1741
|
-
if (
|
|
1742
|
-
|
|
1743
|
-
|
|
2082
|
+
if (!byName.has(r.name)) {
|
|
2083
|
+
byName.set(r.name, { name: r.name, platform: selectedPlatform });
|
|
2084
|
+
}
|
|
1744
2085
|
}
|
|
1745
|
-
return
|
|
2086
|
+
return [...byName.values()];
|
|
1746
2087
|
}
|
|
1747
2088
|
var DEFAULT_SHIELD_API_BASE_URL = "https://api.multicorn.ai";
|
|
1748
|
-
async function runInit(explicitBaseUrl) {
|
|
2089
|
+
async function runInit(explicitBaseUrl, options) {
|
|
2090
|
+
const verbose = options?.verbose === true;
|
|
1749
2091
|
if (!process.stdin.isTTY) {
|
|
1750
2092
|
process.stderr.write(
|
|
1751
2093
|
style.red("Error: interactive terminal required. Cannot run init with piped input.") + "\n"
|
|
@@ -1795,6 +2137,16 @@ async function runInit(explicitBaseUrl) {
|
|
|
1795
2137
|
apiKey = existing.apiKey;
|
|
1796
2138
|
}
|
|
1797
2139
|
}
|
|
2140
|
+
if (apiKey.length === 0) {
|
|
2141
|
+
const signupDashboardUrl = deriveDashboardUrl(resolvedBaseUrl).replace(/\/+$/, "");
|
|
2142
|
+
console.log("");
|
|
2143
|
+
console.log(" Multicorn Shield controls what your AI agents can do.");
|
|
2144
|
+
console.log(" You need a free account to get an API key.");
|
|
2145
|
+
console.log("");
|
|
2146
|
+
console.log(` 1. Sign up or log in \u2192 ${signupDashboardUrl}`);
|
|
2147
|
+
console.log(" 2. Go to Settings \u2192 API Keys to create a key");
|
|
2148
|
+
console.log("");
|
|
2149
|
+
}
|
|
1798
2150
|
while (apiKey.length === 0) {
|
|
1799
2151
|
const input = await ask("API key (starts with mcs_): ");
|
|
1800
2152
|
const key = input.trim();
|
|
@@ -1850,7 +2202,7 @@ async function runInit(explicitBaseUrl) {
|
|
|
1850
2202
|
`
|
|
1851
2203
|
);
|
|
1852
2204
|
process.stderr.write(
|
|
1853
|
-
"\n" + style.bold("Try it:") + " " + style.cyan(
|
|
2205
|
+
"\n" + style.bold("Try it:") + " make a request in your coding agent - Shield will intercept the first tool call and ask for your consent.\n" + style.dim("Example wrap command: ") + style.cyan(
|
|
1854
2206
|
"npx multicorn-shield --wrap npx @modelcontextprotocol/server-filesystem /tmp"
|
|
1855
2207
|
) + "\n"
|
|
1856
2208
|
);
|
|
@@ -1871,10 +2223,8 @@ async function runInit(explicitBaseUrl) {
|
|
|
1871
2223
|
continue;
|
|
1872
2224
|
}
|
|
1873
2225
|
const remoteAccountAgents = await fetchRemoteAgentsSummaries(apiKey, resolvedBaseUrl);
|
|
1874
|
-
const agentsForPlatform =
|
|
1875
|
-
currentAgents,
|
|
1876
|
-
remoteAccountAgents,
|
|
1877
|
-
selectedPlatform
|
|
2226
|
+
const agentsForPlatform = dedupeAgentsByName(
|
|
2227
|
+
mergeAgentsForPlatform(currentAgents, remoteAccountAgents, selectedPlatform)
|
|
1878
2228
|
);
|
|
1879
2229
|
const localForPlatformCount = currentAgents.filter(
|
|
1880
2230
|
(a) => a.platform === selectedPlatform
|
|
@@ -1883,11 +2233,13 @@ async function runInit(explicitBaseUrl) {
|
|
|
1883
2233
|
(r) => r.platform === selectedPlatform
|
|
1884
2234
|
).length;
|
|
1885
2235
|
const savedSummary = currentAgents.length === 0 ? "none on disk" : currentAgents.map((a) => `${a.name} (${a.platform})`).join(", ");
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
2236
|
+
if (verbose) {
|
|
2237
|
+
process.stderr.write(
|
|
2238
|
+
style.dim(
|
|
2239
|
+
`[shield init] Menu option ${String(selection)} -> platform slug "${selectedPlatform}". ${String(agentsForPlatform.length)} agent(s) for this platform (local file: ${String(localForPlatformCount)}, account API: ${String(accountForPlatformCount)}). On-disk entries: ${savedSummary}.`
|
|
2240
|
+
) + "\n"
|
|
2241
|
+
);
|
|
2242
|
+
}
|
|
1891
2243
|
if (agentsForPlatform.length > 0) {
|
|
1892
2244
|
process.stderr.write(
|
|
1893
2245
|
`
|
|
@@ -1927,6 +2279,14 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
1927
2279
|
}
|
|
1928
2280
|
}
|
|
1929
2281
|
}
|
|
2282
|
+
if (selectedPlatform === "cursor" || selectedPlatform === "github-copilot") {
|
|
2283
|
+
const where = selectedPlatform === "cursor" ? "Cursor" : "GitHub Copilot";
|
|
2284
|
+
process.stderr.write(
|
|
2285
|
+
"\n" + style.dim(
|
|
2286
|
+
`Using Claude models (Sonnet, Opus, Haiku) in ${where}? The Claude Code native plugin is recommended - it governs all tool calls, not just MCP traffic. Run init again and select Claude Code.`
|
|
2287
|
+
) + "\n\n"
|
|
2288
|
+
);
|
|
2289
|
+
}
|
|
1930
2290
|
const prereqEntry = INIT_WIZARD_PLATFORM_REGISTRY.find((e) => e.slug === selectedPlatform);
|
|
1931
2291
|
if (prereqEntry?.prereqUrl !== void 0) {
|
|
1932
2292
|
const proceed = await promptHostedProxyInstallPrereq(
|
|
@@ -1942,7 +2302,7 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
1942
2302
|
continue;
|
|
1943
2303
|
}
|
|
1944
2304
|
}
|
|
1945
|
-
const agentName = await promptAgentName(ask, selectedPlatform);
|
|
2305
|
+
const agentName = await promptAgentName(ask, selectedPlatform, removeAgentNameBeforeSave);
|
|
1946
2306
|
let setupSucceeded = false;
|
|
1947
2307
|
if (selectedPlatform === "openclaw") {
|
|
1948
2308
|
let detection;
|
|
@@ -2068,7 +2428,9 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
2068
2428
|
) + style.cyan("~/.multicorn/windsurf-hooks") + style.dim(" if that is a concern.") + "\n\n"
|
|
2069
2429
|
);
|
|
2070
2430
|
process.stderr.write(
|
|
2071
|
-
style.dim(
|
|
2431
|
+
style.dim(
|
|
2432
|
+
"Try it: make a request in Windsurf - Shield will intercept the first tool call and ask for your consent."
|
|
2433
|
+
) + "\n"
|
|
2072
2434
|
);
|
|
2073
2435
|
configuredAgents.push({
|
|
2074
2436
|
selection,
|
|
@@ -2125,7 +2487,13 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
2125
2487
|
if (created && proxyUrl.length > 0) {
|
|
2126
2488
|
process.stderr.write("\n" + style.bold("Your Shield proxy URL:") + "\n");
|
|
2127
2489
|
process.stderr.write(" " + style.cyan(proxyUrl) + "\n");
|
|
2128
|
-
|
|
2490
|
+
await applyHostedProxyMcpConfig(
|
|
2491
|
+
selectedPlatform,
|
|
2492
|
+
proxyUrl,
|
|
2493
|
+
shortName,
|
|
2494
|
+
apiKey,
|
|
2495
|
+
initWorkspacePath
|
|
2496
|
+
);
|
|
2129
2497
|
configuredAgents.push({
|
|
2130
2498
|
selection,
|
|
2131
2499
|
platform: selectedPlatform,
|
|
@@ -2210,7 +2578,13 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
2210
2578
|
if (created && proxyUrl.length > 0) {
|
|
2211
2579
|
process.stderr.write("\n" + style.bold("Your Shield proxy URL:") + "\n");
|
|
2212
2580
|
process.stderr.write(" " + style.cyan(proxyUrl) + "\n");
|
|
2213
|
-
|
|
2581
|
+
await applyHostedProxyMcpConfig(
|
|
2582
|
+
selectedPlatform,
|
|
2583
|
+
proxyUrl,
|
|
2584
|
+
shortName,
|
|
2585
|
+
apiKey,
|
|
2586
|
+
initWorkspacePath
|
|
2587
|
+
);
|
|
2214
2588
|
configuredAgents.push({
|
|
2215
2589
|
selection,
|
|
2216
2590
|
platform: selectedPlatform,
|
|
@@ -2289,7 +2663,13 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
2289
2663
|
if (created && proxyUrl.length > 0) {
|
|
2290
2664
|
process.stderr.write("\n" + style.bold("Your Shield proxy URL:") + "\n");
|
|
2291
2665
|
process.stderr.write(" " + style.cyan(proxyUrl) + "\n");
|
|
2292
|
-
|
|
2666
|
+
await applyHostedProxyMcpConfig(
|
|
2667
|
+
selectedPlatform,
|
|
2668
|
+
proxyUrl,
|
|
2669
|
+
shortName,
|
|
2670
|
+
apiKey,
|
|
2671
|
+
initWorkspacePath
|
|
2672
|
+
);
|
|
2293
2673
|
configuredAgents.push({
|
|
2294
2674
|
selection,
|
|
2295
2675
|
platform: selectedPlatform,
|
|
@@ -2331,7 +2711,13 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
2331
2711
|
if (created && proxyUrl.length > 0) {
|
|
2332
2712
|
process.stderr.write("\n" + style.bold("Your Shield proxy URL:") + "\n");
|
|
2333
2713
|
process.stderr.write(" " + style.cyan(proxyUrl) + "\n");
|
|
2334
|
-
|
|
2714
|
+
await applyHostedProxyMcpConfig(
|
|
2715
|
+
selectedPlatform,
|
|
2716
|
+
proxyUrl,
|
|
2717
|
+
shortName,
|
|
2718
|
+
apiKey,
|
|
2719
|
+
initWorkspacePath
|
|
2720
|
+
);
|
|
2335
2721
|
configuredAgents.push({
|
|
2336
2722
|
selection,
|
|
2337
2723
|
platform: selectedPlatform,
|
|
@@ -2397,42 +2783,42 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
2397
2783
|
const blocks = [];
|
|
2398
2784
|
if (configuredPlatforms.has("openclaw")) {
|
|
2399
2785
|
blocks.push(
|
|
2400
|
-
"\n" + style.bold("OpenClaw") + "\n \u2192 Restart your gateway: " + style.cyan("openclaw gateway restart") + "\n \u2192 Start a session: " + style.cyan("openclaw tui") + "\n"
|
|
2786
|
+
"\n" + style.bold("OpenClaw") + "\n \u2192 Restart your gateway: " + style.cyan("openclaw gateway restart") + "\n \u2192 Start a session: " + style.cyan("openclaw tui") + "\n \u2192 Try it: make a request in OpenClaw - Shield will intercept the first tool call and ask for your consent\n"
|
|
2401
2787
|
);
|
|
2402
2788
|
}
|
|
2403
2789
|
if (configuredPlatforms.has("claude-code")) {
|
|
2404
2790
|
blocks.push(
|
|
2405
|
-
"\n" + style.bold("Claude Code") + "\n \u2192 Start
|
|
2791
|
+
"\n" + style.bold("Claude Code") + "\n \u2192 Start coding: run " + style.cyan("claude") + " in the terminal, or use Cursor with a Claude model - Shield hooks apply to both\n \u2192 Try it: make a request in Claude Code - Shield will intercept the first tool call and ask for your consent\n"
|
|
2406
2792
|
);
|
|
2407
2793
|
}
|
|
2408
2794
|
if (configuredPlatforms.has("claude-desktop")) {
|
|
2409
2795
|
blocks.push(
|
|
2410
|
-
"\n" + style.bold("Claude Desktop") + "\n \u2192 Restart Claude Desktop to pick up config changes\n"
|
|
2796
|
+
"\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"
|
|
2411
2797
|
);
|
|
2412
2798
|
}
|
|
2413
2799
|
if (configuredPlatforms.has("cursor")) {
|
|
2414
2800
|
blocks.push(
|
|
2415
|
-
"\n" + style.bold("Cursor") + "\n \u2192 If needed, download Cursor from " + style.cyan("https://cursor.com/downloads") + "\n \u2192 Restart Cursor so it loads the MCP server\n \u2192
|
|
2801
|
+
"\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"
|
|
2416
2802
|
);
|
|
2417
2803
|
}
|
|
2418
2804
|
if (configuredPlatforms.has("kilo-code")) {
|
|
2419
2805
|
blocks.push(
|
|
2420
|
-
"\n" + style.bold("Kilo Code") + "\n \u2192 Restart the editor or reload the window if the MCP server does not appear\n \u2192
|
|
2806
|
+
"\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"
|
|
2421
2807
|
);
|
|
2422
2808
|
}
|
|
2423
2809
|
if (configuredPlatforms.has("github-copilot")) {
|
|
2424
2810
|
blocks.push(
|
|
2425
|
-
"\n" + style.bold("GitHub Copilot") + "\n \u2192 Reload the editor window if the MCP server does not appear\n \u2192
|
|
2811
|
+
"\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"
|
|
2426
2812
|
);
|
|
2427
2813
|
}
|
|
2428
2814
|
if (configuredPlatforms.has("continue-dev")) {
|
|
2429
2815
|
blocks.push(
|
|
2430
|
-
"\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"
|
|
2816
|
+
"\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"
|
|
2431
2817
|
);
|
|
2432
2818
|
}
|
|
2433
2819
|
if (configuredPlatforms.has("goose")) {
|
|
2434
2820
|
blocks.push(
|
|
2435
|
-
"\n" + style.bold("Goose") + "\n \u2192 Start a new Goose session after updating config\n"
|
|
2821
|
+
"\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"
|
|
2436
2822
|
);
|
|
2437
2823
|
}
|
|
2438
2824
|
const windsurfNativeConfigured = configuredAgents.some(
|
|
@@ -2443,12 +2829,12 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
2443
2829
|
);
|
|
2444
2830
|
if (windsurfNativeConfigured) {
|
|
2445
2831
|
blocks.push(
|
|
2446
|
-
"\n" + style.bold("Windsurf (native)") + "\n \u2192
|
|
2832
|
+
"\n" + style.bold("Windsurf (native)") + "\n \u2192 Open Windsurf (or restart if it is already running)\n \u2192 Try it: make a request in Windsurf - Shield will intercept the first tool call and ask for your consent\n"
|
|
2447
2833
|
);
|
|
2448
2834
|
}
|
|
2449
2835
|
if (windsurfHostedConfigured) {
|
|
2450
2836
|
blocks.push(
|
|
2451
|
-
"\n" + style.bold("Windsurf (hosted)") + "\n \u2192 If needed, install from " + style.cyan("https://windsurf.com/download") + "\n \u2192 Restart Windsurf so it loads the MCP server\n"
|
|
2837
|
+
"\n" + style.bold("Windsurf (hosted)") + "\n \u2192 If needed, install from " + style.cyan("https://windsurf.com/download") + "\n \u2192 Restart Windsurf so it loads the MCP server\n \u2192 In Windsurf, open the three-dot menu (top-right of Cascade panel), find your Shield server at the bottom of the list, and toggle it on\n \u2192 Try it: make a request in Windsurf - Shield will intercept the first tool call and ask for your consent\n"
|
|
2452
2838
|
);
|
|
2453
2839
|
}
|
|
2454
2840
|
const clineNativeConfigured = configuredAgents.some(
|
|
@@ -2459,12 +2845,12 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
2459
2845
|
);
|
|
2460
2846
|
if (clineNativeConfigured) {
|
|
2461
2847
|
blocks.push(
|
|
2462
|
-
"\n" + style.bold("Cline (native)") + "\n \u2192
|
|
2848
|
+
"\n" + style.bold("Cline (native)") + "\n \u2192 In Cline, click the settings icon \u2192 Feature Settings \u2192 scroll down to Advanced \u2192 enable Hooks, then reload the VS Code window\n \u2192 Try it: make a request in Cline - Shield will intercept the first tool call and ask for your consent\n"
|
|
2463
2849
|
);
|
|
2464
2850
|
}
|
|
2465
2851
|
if (clineHostedConfigured) {
|
|
2466
2852
|
blocks.push(
|
|
2467
|
-
"\n" + style.bold("Cline (hosted)") + "\n \u2192 Restart Cline or reload the VS Code window\n"
|
|
2853
|
+
"\n" + style.bold("Cline (hosted)") + "\n \u2192 Restart Cline or reload the VS Code window\n \u2192 In Cline, open server settings using the plug icon, open the Configure tab, and confirm your Shield server is listed and toggled on\n \u2192 Try it: make a request in Cline - Shield will intercept the first tool call and ask for your consent\n"
|
|
2468
2854
|
);
|
|
2469
2855
|
}
|
|
2470
2856
|
const geminiCliNativeConfigured = configuredAgents.some(
|
|
@@ -2475,18 +2861,30 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
2475
2861
|
);
|
|
2476
2862
|
if (geminiCliNativeConfigured) {
|
|
2477
2863
|
blocks.push(
|
|
2478
|
-
"\n" + style.bold("Gemini CLI (native)") + "\n \u2192
|
|
2864
|
+
"\n" + style.bold("Gemini CLI (native)") + "\n \u2192 Start Gemini CLI: run " + style.cyan("gemini") + " in your terminal (exit any existing session first)\n \u2192 Try it: make a request in Gemini CLI - Shield will intercept the first tool call and ask for your consent\n"
|
|
2479
2865
|
);
|
|
2480
2866
|
}
|
|
2481
2867
|
if (geminiCliHostedConfigured) {
|
|
2482
2868
|
blocks.push(
|
|
2483
|
-
"\n" + style.bold("Gemini CLI (hosted)") + "\n \u2192
|
|
2869
|
+
"\n" + style.bold("Gemini CLI (hosted)") + "\n \u2192 Try it: make a request in Gemini CLI - Shield will intercept the first tool call and ask for your consent\n"
|
|
2870
|
+
);
|
|
2871
|
+
}
|
|
2872
|
+
if (configuredPlatforms.has("other-mcp")) {
|
|
2873
|
+
blocks.push(
|
|
2874
|
+
"\n" + style.bold("Local MCP / Other") + "\n \u2192 Run your configured wrap command (for example " + style.cyan("npx multicorn-shield --wrap ...") + ")\n \u2192 Try it: make a request in your coding agent - Shield will intercept the first tool call and ask for your consent\n"
|
|
2484
2875
|
);
|
|
2485
2876
|
}
|
|
2486
2877
|
if (blocks.length > 0) {
|
|
2487
2878
|
process.stderr.write("\n" + style.bold(style.violet("Next steps")) + "\n");
|
|
2488
2879
|
process.stderr.write(blocks.join("") + "\n");
|
|
2489
2880
|
}
|
|
2881
|
+
const dashboardUrl = deriveDashboardUrl(resolvedBaseUrl).replace(/\/+$/, "");
|
|
2882
|
+
console.log("");
|
|
2883
|
+
console.log(" Your dashboard");
|
|
2884
|
+
console.log(` \u2192 ${dashboardUrl}/agents`);
|
|
2885
|
+
console.log("");
|
|
2886
|
+
console.log(" Use any tool in your agent to see it appear.");
|
|
2887
|
+
console.log("");
|
|
2490
2888
|
}
|
|
2491
2889
|
return lastConfig;
|
|
2492
2890
|
}
|
|
@@ -2964,6 +3362,7 @@ function createProxyServer(config) {
|
|
|
2964
3362
|
const pendingLines = [];
|
|
2965
3363
|
let draining = false;
|
|
2966
3364
|
let stopped = false;
|
|
3365
|
+
let hasLoggedFirstAction = false;
|
|
2967
3366
|
async function refreshScopes() {
|
|
2968
3367
|
if (stopped) return;
|
|
2969
3368
|
if (agentId.length === 0) return;
|
|
@@ -3069,8 +3468,10 @@ function createProxyServer(config) {
|
|
|
3069
3468
|
);
|
|
3070
3469
|
}
|
|
3071
3470
|
}
|
|
3471
|
+
const rawCostCents = extractCostCents(toolParams.arguments);
|
|
3472
|
+
const costCents = Number.isFinite(rawCostCents) && rawCostCents > 0 ? Math.min(rawCostCents, 1e8) : 0;
|
|
3473
|
+
const costUsd = costCents > 0 ? costCents / 100 : void 0;
|
|
3072
3474
|
if (spendingChecker !== null) {
|
|
3073
|
-
const costCents = extractCostCents(toolParams.arguments);
|
|
3074
3475
|
if (costCents > 0) {
|
|
3075
3476
|
const spendResult = spendingChecker.checkSpend(costCents);
|
|
3076
3477
|
if (!spendResult.allowed) {
|
|
@@ -3089,7 +3490,8 @@ function createProxyServer(config) {
|
|
|
3089
3490
|
agent: config.agentName,
|
|
3090
3491
|
service,
|
|
3091
3492
|
actionType: action,
|
|
3092
|
-
status: "blocked"
|
|
3493
|
+
status: "blocked",
|
|
3494
|
+
...costUsd !== void 0 ? { cost: costUsd } : {}
|
|
3093
3495
|
});
|
|
3094
3496
|
config.logger.debug("Spending-blocked action logged.", { tool: toolParams.name });
|
|
3095
3497
|
}
|
|
@@ -3117,9 +3519,15 @@ function createProxyServer(config) {
|
|
|
3117
3519
|
agent: config.agentName,
|
|
3118
3520
|
service,
|
|
3119
3521
|
actionType: action,
|
|
3120
|
-
status: "approved"
|
|
3522
|
+
status: "approved",
|
|
3523
|
+
...costUsd !== void 0 ? { cost: costUsd } : {}
|
|
3121
3524
|
});
|
|
3122
3525
|
config.logger.debug("Approved action logged.", { tool: toolParams.name });
|
|
3526
|
+
if (!hasLoggedFirstAction) {
|
|
3527
|
+
hasLoggedFirstAction = true;
|
|
3528
|
+
const dashUrl = deriveDashboardUrl(config.baseUrl).replace(/\/+$/, "");
|
|
3529
|
+
config.logger.info(`First action recorded. View activity \u2192 ${dashUrl}/agents`);
|
|
3530
|
+
}
|
|
3123
3531
|
}
|
|
3124
3532
|
}
|
|
3125
3533
|
return null;
|
|
@@ -3354,7 +3762,10 @@ async function restoreClaudeDesktopMcpFromBackup() {
|
|
|
3354
3762
|
}
|
|
3355
3763
|
root["mcpServers"] = backup.mcpServers;
|
|
3356
3764
|
await mkdir(dirname(configPath), { recursive: true });
|
|
3357
|
-
await writeFile(configPath, JSON.stringify(root, null, 2) + "\n", {
|
|
3765
|
+
await writeFile(configPath, JSON.stringify(root, null, 2) + "\n", {
|
|
3766
|
+
encoding: "utf8",
|
|
3767
|
+
mode: 384
|
|
3768
|
+
});
|
|
3358
3769
|
}
|
|
3359
3770
|
|
|
3360
3771
|
// bin/multicorn-shield.ts
|
|
@@ -3369,6 +3780,7 @@ function parseArgs(argv) {
|
|
|
3369
3780
|
let agentName = "";
|
|
3370
3781
|
let deleteAgentName = "";
|
|
3371
3782
|
let apiKey = void 0;
|
|
3783
|
+
let verbose = false;
|
|
3372
3784
|
for (let i = 0; i < args.length; i++) {
|
|
3373
3785
|
const arg = args[i];
|
|
3374
3786
|
if (arg === "init") {
|
|
@@ -3468,6 +3880,8 @@ function parseArgs(argv) {
|
|
|
3468
3880
|
apiKey = next;
|
|
3469
3881
|
i++;
|
|
3470
3882
|
}
|
|
3883
|
+
} else if (arg === "--verbose" || arg === "--debug") {
|
|
3884
|
+
verbose = true;
|
|
3471
3885
|
}
|
|
3472
3886
|
}
|
|
3473
3887
|
return {
|
|
@@ -3479,7 +3893,8 @@ function parseArgs(argv) {
|
|
|
3479
3893
|
dashboardUrl,
|
|
3480
3894
|
agentName,
|
|
3481
3895
|
deleteAgentName,
|
|
3482
|
-
apiKey
|
|
3896
|
+
apiKey,
|
|
3897
|
+
verbose
|
|
3483
3898
|
};
|
|
3484
3899
|
}
|
|
3485
3900
|
function printHelp() {
|
|
@@ -3505,6 +3920,7 @@ function printHelp() {
|
|
|
3505
3920
|
" Shield's permission layer.",
|
|
3506
3921
|
"",
|
|
3507
3922
|
"Options:",
|
|
3923
|
+
" --verbose, --debug Print extra diagnostics during init (menu selection, agent counts)",
|
|
3508
3924
|
" --api-key <key> Multicorn API key (overrides MULTICORN_API_KEY env var and config file)",
|
|
3509
3925
|
" --log-level <level> Log level: debug | info | warn | error (default: info)",
|
|
3510
3926
|
" --base-url <url> Multicorn API base URL (default: https://api.multicorn.ai)",
|
|
@@ -3535,7 +3951,7 @@ async function runCli() {
|
|
|
3535
3951
|
process.exit(0);
|
|
3536
3952
|
}
|
|
3537
3953
|
if (cli.subcommand === "init") {
|
|
3538
|
-
await runInit(cli.baseUrl);
|
|
3954
|
+
await runInit(cli.baseUrl, { verbose: cli.verbose });
|
|
3539
3955
|
return;
|
|
3540
3956
|
}
|
|
3541
3957
|
if (cli.subcommand === "agents") {
|