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-proxy.js
CHANGED
|
@@ -691,9 +691,11 @@ async function updateOpenClawConfigIfPresent(apiKey, baseUrl, agentName) {
|
|
|
691
691
|
}
|
|
692
692
|
}
|
|
693
693
|
}
|
|
694
|
-
await writeFile(
|
|
695
|
-
|
|
696
|
-
|
|
694
|
+
await writeFile(
|
|
695
|
+
OPENCLAW_CONFIG_PATH,
|
|
696
|
+
JSON.stringify(obj, null, 2) + "\n",
|
|
697
|
+
SECRET_JSON_FILE_OPTIONS
|
|
698
|
+
);
|
|
697
699
|
return "updated";
|
|
698
700
|
}
|
|
699
701
|
async function validateApiKey(apiKey, baseUrl) {
|
|
@@ -855,7 +857,7 @@ async function installWindsurfNativeHooks() {
|
|
|
855
857
|
base["hooks"] = nextHooks;
|
|
856
858
|
const hooksDir = dirname(hooksPath);
|
|
857
859
|
await mkdir(hooksDir, { recursive: true });
|
|
858
|
-
await writeFile(hooksPath, JSON.stringify(base, null, 2) + "\n",
|
|
860
|
+
await writeFile(hooksPath, JSON.stringify(base, null, 2) + "\n", SECRET_JSON_FILE_OPTIONS);
|
|
859
861
|
}
|
|
860
862
|
function getClineHooksInstallDir() {
|
|
861
863
|
return join(homedir(), ".multicorn", "cline-hooks");
|
|
@@ -934,6 +936,37 @@ function getGeminiCliHooksInstallDir() {
|
|
|
934
936
|
function getGeminiCliSettingsPath() {
|
|
935
937
|
return join(homedir(), ".gemini", "settings.json");
|
|
936
938
|
}
|
|
939
|
+
async function mergeGeminiHostedMcpServersIntoSettings(shortName, proxyUrl, apiKey) {
|
|
940
|
+
const settingsPath = getGeminiCliSettingsPath();
|
|
941
|
+
let existing = {};
|
|
942
|
+
try {
|
|
943
|
+
const rawText = await readFile(settingsPath, "utf8");
|
|
944
|
+
const parsed = JSON.parse(rawText);
|
|
945
|
+
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
946
|
+
existing = parsed;
|
|
947
|
+
}
|
|
948
|
+
} catch (err) {
|
|
949
|
+
if (isErrnoException(err) && err.code === "ENOENT") {
|
|
950
|
+
existing = {};
|
|
951
|
+
} else {
|
|
952
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
953
|
+
throw new Error(`Could not read or parse Gemini CLI settings at ${settingsPath}: ${detail}`);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
const mcpRaw = existing["mcpServers"];
|
|
957
|
+
const mcpServers = typeof mcpRaw === "object" && mcpRaw !== null && !Array.isArray(mcpRaw) ? { ...mcpRaw } : {};
|
|
958
|
+
mcpServers[shortName] = {
|
|
959
|
+
httpUrl: proxyUrl,
|
|
960
|
+
headers: {
|
|
961
|
+
Authorization: `Bearer ${apiKey}`
|
|
962
|
+
}
|
|
963
|
+
};
|
|
964
|
+
const out = { ...existing, mcpServers };
|
|
965
|
+
await mkdir(dirname(settingsPath), { recursive: true });
|
|
966
|
+
const serialized = JSON.stringify(out, null, 2) + "\n";
|
|
967
|
+
await writeFile(settingsPath, serialized, SECRET_JSON_FILE_OPTIONS);
|
|
968
|
+
writeMcpAddedLine(shortName, settingsPath);
|
|
969
|
+
}
|
|
937
970
|
function geminiInnerHooksReferenceShield(inner, multicornName) {
|
|
938
971
|
if (!Array.isArray(inner)) return false;
|
|
939
972
|
for (const h of inner) {
|
|
@@ -1118,7 +1151,7 @@ async function installClaudeCodeUserSettingsHooks(ask) {
|
|
|
1118
1151
|
hooksObj["PostToolUse"] = postArr;
|
|
1119
1152
|
const out = { ...existing, hooks: hooksObj };
|
|
1120
1153
|
const serialized = JSON.stringify(out, null, 2) + "\n";
|
|
1121
|
-
await writeFile(settingsPath, serialized,
|
|
1154
|
+
await writeFile(settingsPath, serialized, SECRET_JSON_FILE_OPTIONS);
|
|
1122
1155
|
process.stderr.write(
|
|
1123
1156
|
"\n" + style.dim("Wrote ") + style.cyan(settingsPath) + style.dim(":") + "\n"
|
|
1124
1157
|
);
|
|
@@ -1220,7 +1253,7 @@ async function installGeminiCliNativeHooks(ask) {
|
|
|
1220
1253
|
AfterTool: afterArr
|
|
1221
1254
|
};
|
|
1222
1255
|
await mkdir(dirname(settingsPath), { recursive: true });
|
|
1223
|
-
await writeFile(settingsPath, JSON.stringify(existing, null, 2) + "\n",
|
|
1256
|
+
await writeFile(settingsPath, JSON.stringify(existing, null, 2) + "\n", SECRET_JSON_FILE_OPTIONS);
|
|
1224
1257
|
}
|
|
1225
1258
|
async function promptGeminiCliIntegrationMode(ask) {
|
|
1226
1259
|
process.stderr.write("\n" + style.bold("Gemini CLI integration") + "\n");
|
|
@@ -1259,6 +1292,52 @@ function getClaudeDesktopConfigPath() {
|
|
|
1259
1292
|
);
|
|
1260
1293
|
}
|
|
1261
1294
|
}
|
|
1295
|
+
function getCursorMcpJsonPath() {
|
|
1296
|
+
return join(homedir(), ".cursor", "mcp.json");
|
|
1297
|
+
}
|
|
1298
|
+
function getWindsurfMcpConfigPath() {
|
|
1299
|
+
return join(homedir(), ".codeium", "windsurf", "mcp_config.json");
|
|
1300
|
+
}
|
|
1301
|
+
function getClineMcpSettingsPath() {
|
|
1302
|
+
switch (process.platform) {
|
|
1303
|
+
case "win32":
|
|
1304
|
+
return join(
|
|
1305
|
+
process.env["APPDATA"] ?? join(homedir(), "AppData", "Roaming"),
|
|
1306
|
+
"Code",
|
|
1307
|
+
"User",
|
|
1308
|
+
"globalStorage",
|
|
1309
|
+
"saoudrizwan.claude-dev",
|
|
1310
|
+
"settings",
|
|
1311
|
+
"cline_mcp_settings.json"
|
|
1312
|
+
);
|
|
1313
|
+
case "linux":
|
|
1314
|
+
return join(
|
|
1315
|
+
homedir(),
|
|
1316
|
+
".config",
|
|
1317
|
+
"Code",
|
|
1318
|
+
"User",
|
|
1319
|
+
"globalStorage",
|
|
1320
|
+
"saoudrizwan.claude-dev",
|
|
1321
|
+
"settings",
|
|
1322
|
+
"cline_mcp_settings.json"
|
|
1323
|
+
);
|
|
1324
|
+
default:
|
|
1325
|
+
return join(
|
|
1326
|
+
homedir(),
|
|
1327
|
+
"Library",
|
|
1328
|
+
"Application Support",
|
|
1329
|
+
"Code",
|
|
1330
|
+
"User",
|
|
1331
|
+
"globalStorage",
|
|
1332
|
+
"saoudrizwan.claude-dev",
|
|
1333
|
+
"settings",
|
|
1334
|
+
"cline_mcp_settings.json"
|
|
1335
|
+
);
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
function getContinueConfigJsonPath() {
|
|
1339
|
+
return join(homedir(), ".continue", "config.json");
|
|
1340
|
+
}
|
|
1262
1341
|
function platformMenuLabelForSelection(sel) {
|
|
1263
1342
|
const slug = PLATFORM_BY_SELECTION[sel];
|
|
1264
1343
|
if (slug === void 0) return "Unknown";
|
|
@@ -1390,9 +1469,11 @@ async function arrowSelect(options, ask, fallbackLabel) {
|
|
|
1390
1469
|
process.stdin.on("data", onData);
|
|
1391
1470
|
});
|
|
1392
1471
|
}
|
|
1393
|
-
async function promptAgentName(ask, platform) {
|
|
1472
|
+
async function promptAgentName(ask, platform, defaultNameOverride) {
|
|
1394
1473
|
const dirPart = normalizeAgentName(basename(process.cwd()));
|
|
1395
|
-
const
|
|
1474
|
+
const computedDefault = dirPart.length > 0 ? normalizeAgentName(`${dirPart}-${platform}`) || platform : normalizeAgentName(platform) || platform;
|
|
1475
|
+
const fromOverride = defaultNameOverride !== void 0 && defaultNameOverride.trim().length > 0 ? normalizeAgentName(defaultNameOverride.trim()) : "";
|
|
1476
|
+
const defaultAgentName = fromOverride.length > 0 ? fromOverride : computedDefault;
|
|
1396
1477
|
let agentName = "";
|
|
1397
1478
|
while (agentName.length === 0) {
|
|
1398
1479
|
const input = await ask(
|
|
@@ -1483,6 +1564,240 @@ async function createProxyConfig(baseUrl, apiKey, agentName, targetUrl, serverNa
|
|
|
1483
1564
|
const data = envelope["data"];
|
|
1484
1565
|
return typeof data?.["proxy_url"] === "string" ? data["proxy_url"] : "";
|
|
1485
1566
|
}
|
|
1567
|
+
function writeMcpAddedLine(shortName, filePath) {
|
|
1568
|
+
process.stderr.write(
|
|
1569
|
+
style.green("\u2713") + ' MCP server "' + shortName + '" added to ' + style.cyan(filePath) + "\n"
|
|
1570
|
+
);
|
|
1571
|
+
}
|
|
1572
|
+
async function mergeMcpServersObjectStyle(filePath, shortName, entry) {
|
|
1573
|
+
let root = {};
|
|
1574
|
+
try {
|
|
1575
|
+
const raw = await readFile(filePath, "utf8");
|
|
1576
|
+
let parsed;
|
|
1577
|
+
try {
|
|
1578
|
+
parsed = JSON.parse(raw);
|
|
1579
|
+
} catch {
|
|
1580
|
+
return "parse-error";
|
|
1581
|
+
}
|
|
1582
|
+
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
1583
|
+
root = parsed;
|
|
1584
|
+
} else {
|
|
1585
|
+
return "parse-error";
|
|
1586
|
+
}
|
|
1587
|
+
} catch (e) {
|
|
1588
|
+
if (isErrnoException(e) && e.code === "ENOENT") {
|
|
1589
|
+
root = {};
|
|
1590
|
+
} else {
|
|
1591
|
+
throw e;
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
const mcpRaw = root["mcpServers"];
|
|
1595
|
+
const mcpServers = typeof mcpRaw === "object" && mcpRaw !== null && !Array.isArray(mcpRaw) ? { ...mcpRaw } : {};
|
|
1596
|
+
mcpServers[shortName] = entry;
|
|
1597
|
+
root["mcpServers"] = mcpServers;
|
|
1598
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
1599
|
+
await writeFile(filePath, JSON.stringify(root, null, 2) + "\n", SECRET_JSON_FILE_OPTIONS);
|
|
1600
|
+
writeMcpAddedLine(shortName, filePath);
|
|
1601
|
+
return "ok";
|
|
1602
|
+
}
|
|
1603
|
+
async function mergeClaudeDesktopHostedMcpRemote(shortName, proxyUrl, apiKey) {
|
|
1604
|
+
const entry = {
|
|
1605
|
+
command: "npx",
|
|
1606
|
+
args: ["-y", "mcp-remote", proxyUrl, "--header", `Authorization: Bearer ${apiKey}`]
|
|
1607
|
+
};
|
|
1608
|
+
return mergeMcpServersObjectStyle(getClaudeDesktopConfigPath(), shortName, entry);
|
|
1609
|
+
}
|
|
1610
|
+
async function mergeContinueHostedMcp(shortName, proxyUrl, apiKey) {
|
|
1611
|
+
const filePath = getContinueConfigJsonPath();
|
|
1612
|
+
let root = {};
|
|
1613
|
+
try {
|
|
1614
|
+
const raw = await readFile(filePath, "utf8");
|
|
1615
|
+
let parsed;
|
|
1616
|
+
try {
|
|
1617
|
+
parsed = JSON.parse(raw);
|
|
1618
|
+
} catch {
|
|
1619
|
+
return "parse-error";
|
|
1620
|
+
}
|
|
1621
|
+
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
1622
|
+
root = parsed;
|
|
1623
|
+
} else {
|
|
1624
|
+
return "parse-error";
|
|
1625
|
+
}
|
|
1626
|
+
} catch (e) {
|
|
1627
|
+
if (isErrnoException(e) && e.code === "ENOENT") {
|
|
1628
|
+
root = {};
|
|
1629
|
+
} else {
|
|
1630
|
+
throw e;
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
const rawServers = root["mcpServers"];
|
|
1634
|
+
if (rawServers !== void 0) {
|
|
1635
|
+
if (Array.isArray(rawServers)) ; else if (typeof rawServers === "object" && rawServers !== null) {
|
|
1636
|
+
return "parse-error";
|
|
1637
|
+
} else {
|
|
1638
|
+
return "parse-error";
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
const servers = Array.isArray(rawServers) ? rawServers.map((s) => ({ ...s })) : [];
|
|
1642
|
+
const entry = {
|
|
1643
|
+
name: shortName,
|
|
1644
|
+
type: "streamable-http",
|
|
1645
|
+
url: proxyUrl,
|
|
1646
|
+
headers: {
|
|
1647
|
+
Authorization: `Bearer ${apiKey}`
|
|
1648
|
+
}
|
|
1649
|
+
};
|
|
1650
|
+
const idx = servers.findIndex((s) => s["name"] === shortName);
|
|
1651
|
+
if (idx >= 0) {
|
|
1652
|
+
servers[idx] = entry;
|
|
1653
|
+
} else {
|
|
1654
|
+
servers.push(entry);
|
|
1655
|
+
}
|
|
1656
|
+
root["mcpServers"] = servers;
|
|
1657
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
1658
|
+
await writeFile(filePath, JSON.stringify(root, null, 2) + "\n", SECRET_JSON_FILE_OPTIONS);
|
|
1659
|
+
writeMcpAddedLine(shortName, filePath);
|
|
1660
|
+
return "ok";
|
|
1661
|
+
}
|
|
1662
|
+
async function mergeKiloCodeProjectMcp(workspacePath, shortName, proxyUrl, apiKey) {
|
|
1663
|
+
const filePath = join(workspacePath, ".kilocode", "mcp.json");
|
|
1664
|
+
return mergeMcpServersObjectStyle(filePath, shortName, {
|
|
1665
|
+
url: proxyUrl,
|
|
1666
|
+
headers: {
|
|
1667
|
+
Authorization: `Bearer ${apiKey}`
|
|
1668
|
+
}
|
|
1669
|
+
});
|
|
1670
|
+
}
|
|
1671
|
+
function printHostedProxyJsonParseWarning(filePath) {
|
|
1672
|
+
process.stderr.write(
|
|
1673
|
+
style.yellow("\u26A0") + " Could not parse JSON at " + style.cyan(filePath) + style.dim(" - showing paste snippet instead.") + "\n"
|
|
1674
|
+
);
|
|
1675
|
+
}
|
|
1676
|
+
function printHostedProxyPostWriteHints(platform, shortName) {
|
|
1677
|
+
if (platform === "cursor") {
|
|
1678
|
+
process.stderr.write(
|
|
1679
|
+
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"
|
|
1680
|
+
);
|
|
1681
|
+
}
|
|
1682
|
+
if (platform === "claude-desktop") {
|
|
1683
|
+
process.stderr.write(style.dim("Restart Claude Desktop to load the MCP server.") + "\n");
|
|
1684
|
+
}
|
|
1685
|
+
if (platform === "cline") {
|
|
1686
|
+
process.stderr.write(
|
|
1687
|
+
style.dim(
|
|
1688
|
+
"Restart Cline or reload the VS Code window. Cline will discover the Shield tools automatically."
|
|
1689
|
+
) + "\n"
|
|
1690
|
+
);
|
|
1691
|
+
}
|
|
1692
|
+
if (platform === "windsurf") {
|
|
1693
|
+
process.stderr.write(style.dim("Restart Windsurf (Cmd/Ctrl+Q, then reopen).") + "\n");
|
|
1694
|
+
process.stderr.write(
|
|
1695
|
+
style.dim(
|
|
1696
|
+
"Open the Cascade panel and verify the server appears with a green status indicator."
|
|
1697
|
+
) + "\n"
|
|
1698
|
+
);
|
|
1699
|
+
}
|
|
1700
|
+
if (platform === "github-copilot" || platform === "continue-dev") {
|
|
1701
|
+
process.stderr.write(
|
|
1702
|
+
style.dim("Reload the editor window if the MCP server does not appear immediately.") + "\n"
|
|
1703
|
+
);
|
|
1704
|
+
}
|
|
1705
|
+
if (platform === "goose") {
|
|
1706
|
+
process.stderr.write(style.dim("Start a new Goose session after updating config.") + "\n");
|
|
1707
|
+
}
|
|
1708
|
+
if (platform === "kilo-code") {
|
|
1709
|
+
process.stderr.write(
|
|
1710
|
+
style.dim("Restart Kilo Code or reload the window so it picks up .kilocode/mcp.json.") + "\n"
|
|
1711
|
+
);
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
async function applyHostedProxyMcpConfig(platform, proxyUrl, shortName, apiKey, workspacePath) {
|
|
1715
|
+
const authHeader = `Bearer ${apiKey}`;
|
|
1716
|
+
if (platform === "gemini-cli") {
|
|
1717
|
+
await mergeGeminiHostedMcpServersIntoSettings(shortName, proxyUrl, apiKey);
|
|
1718
|
+
process.stderr.write(
|
|
1719
|
+
style.dim(
|
|
1720
|
+
"For project-specific config, copy the mcpServers entry into .gemini/settings.json in your project root. Restart Gemini CLI if it is already running."
|
|
1721
|
+
) + "\n"
|
|
1722
|
+
);
|
|
1723
|
+
return;
|
|
1724
|
+
}
|
|
1725
|
+
if (platform === "github-copilot") {
|
|
1726
|
+
process.stderr.write(
|
|
1727
|
+
"\n" + style.dim(
|
|
1728
|
+
"GitHub Copilot uses VS Code settings - paste the snippet below into your VS Code Settings (JSON)."
|
|
1729
|
+
) + "\n"
|
|
1730
|
+
);
|
|
1731
|
+
printPlatformSnippet(platform, proxyUrl, shortName, apiKey);
|
|
1732
|
+
return;
|
|
1733
|
+
}
|
|
1734
|
+
if (platform === "goose") {
|
|
1735
|
+
printPlatformSnippet(platform, proxyUrl, shortName, apiKey);
|
|
1736
|
+
return;
|
|
1737
|
+
}
|
|
1738
|
+
try {
|
|
1739
|
+
let result = "parse-error";
|
|
1740
|
+
if (platform === "cursor") {
|
|
1741
|
+
result = await mergeMcpServersObjectStyle(getCursorMcpJsonPath(), shortName, {
|
|
1742
|
+
url: proxyUrl,
|
|
1743
|
+
headers: { Authorization: authHeader }
|
|
1744
|
+
});
|
|
1745
|
+
if (result === "parse-error") {
|
|
1746
|
+
printHostedProxyJsonParseWarning(getCursorMcpJsonPath());
|
|
1747
|
+
}
|
|
1748
|
+
} else if (platform === "claude-desktop") {
|
|
1749
|
+
result = await mergeClaudeDesktopHostedMcpRemote(shortName, proxyUrl, apiKey);
|
|
1750
|
+
if (result === "parse-error") {
|
|
1751
|
+
printHostedProxyJsonParseWarning(getClaudeDesktopConfigPath());
|
|
1752
|
+
}
|
|
1753
|
+
} else if (platform === "windsurf") {
|
|
1754
|
+
result = await mergeMcpServersObjectStyle(getWindsurfMcpConfigPath(), shortName, {
|
|
1755
|
+
serverUrl: proxyUrl,
|
|
1756
|
+
headers: { Authorization: authHeader }
|
|
1757
|
+
});
|
|
1758
|
+
if (result === "parse-error") {
|
|
1759
|
+
printHostedProxyJsonParseWarning(getWindsurfMcpConfigPath());
|
|
1760
|
+
}
|
|
1761
|
+
} else if (platform === "cline") {
|
|
1762
|
+
result = await mergeMcpServersObjectStyle(getClineMcpSettingsPath(), shortName, {
|
|
1763
|
+
url: proxyUrl,
|
|
1764
|
+
headers: { Authorization: authHeader }
|
|
1765
|
+
});
|
|
1766
|
+
if (result === "parse-error") {
|
|
1767
|
+
printHostedProxyJsonParseWarning(getClineMcpSettingsPath());
|
|
1768
|
+
}
|
|
1769
|
+
} else if (platform === "kilo-code") {
|
|
1770
|
+
result = await mergeKiloCodeProjectMcp(workspacePath, shortName, proxyUrl, apiKey);
|
|
1771
|
+
if (result === "parse-error") {
|
|
1772
|
+
printHostedProxyJsonParseWarning(join(workspacePath, ".kilocode", "mcp.json"));
|
|
1773
|
+
}
|
|
1774
|
+
} else if (platform === "continue-dev") {
|
|
1775
|
+
result = await mergeContinueHostedMcp(shortName, proxyUrl, apiKey);
|
|
1776
|
+
if (result === "parse-error") {
|
|
1777
|
+
printHostedProxyJsonParseWarning(getContinueConfigJsonPath());
|
|
1778
|
+
}
|
|
1779
|
+
} else {
|
|
1780
|
+
result = await mergeMcpServersObjectStyle(getCursorMcpJsonPath(), shortName, {
|
|
1781
|
+
url: proxyUrl,
|
|
1782
|
+
headers: { Authorization: authHeader }
|
|
1783
|
+
});
|
|
1784
|
+
if (result === "parse-error") {
|
|
1785
|
+
printHostedProxyJsonParseWarning(getCursorMcpJsonPath());
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
if (result === "ok") {
|
|
1789
|
+
printHostedProxyPostWriteHints(platform, shortName);
|
|
1790
|
+
return;
|
|
1791
|
+
}
|
|
1792
|
+
} catch (e) {
|
|
1793
|
+
const detail = e instanceof Error ? e.message : String(e);
|
|
1794
|
+
process.stderr.write(
|
|
1795
|
+
style.yellow("\u26A0") + ` Could not write MCP config automatically (${detail}).
|
|
1796
|
+
`
|
|
1797
|
+
);
|
|
1798
|
+
}
|
|
1799
|
+
printPlatformSnippet(platform, proxyUrl, shortName, apiKey);
|
|
1800
|
+
}
|
|
1486
1801
|
function gooseHostedProxyYaml(shortName, proxyUrl, bearerHeader) {
|
|
1487
1802
|
return `extensions:
|
|
1488
1803
|
${shortName}:
|
|
@@ -1544,6 +1859,36 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
|
|
|
1544
1859
|
null,
|
|
1545
1860
|
2
|
|
1546
1861
|
);
|
|
1862
|
+
} else if (platform === "claude-desktop") {
|
|
1863
|
+
snippetText = JSON.stringify(
|
|
1864
|
+
{
|
|
1865
|
+
mcpServers: {
|
|
1866
|
+
[shortName]: {
|
|
1867
|
+
command: "npx",
|
|
1868
|
+
args: ["-y", "mcp-remote", routingToken, "--header", `Authorization: ${authHeader}`]
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
},
|
|
1872
|
+
null,
|
|
1873
|
+
2
|
|
1874
|
+
);
|
|
1875
|
+
} else if (platform === "continue-dev") {
|
|
1876
|
+
snippetText = JSON.stringify(
|
|
1877
|
+
{
|
|
1878
|
+
mcpServers: [
|
|
1879
|
+
{
|
|
1880
|
+
name: shortName,
|
|
1881
|
+
type: "streamable-http",
|
|
1882
|
+
url: routingToken,
|
|
1883
|
+
headers: {
|
|
1884
|
+
Authorization: authHeader
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
]
|
|
1888
|
+
},
|
|
1889
|
+
null,
|
|
1890
|
+
2
|
|
1891
|
+
);
|
|
1547
1892
|
} else {
|
|
1548
1893
|
const urlKey = platform === "windsurf" ? "serverUrl" : "url";
|
|
1549
1894
|
snippetText = JSON.stringify(
|
|
@@ -1568,52 +1913,33 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
|
|
|
1568
1913
|
} else if (platform === "claude-desktop") {
|
|
1569
1914
|
process.stderr.write("\n" + style.dim(`Add this to ${getClaudeDesktopConfigPath()}:`) + "\n\n");
|
|
1570
1915
|
} else if (platform === "windsurf") {
|
|
1571
|
-
process.stderr.write(
|
|
1572
|
-
"\n" + style.dim("Add this to ~/.codeium/windsurf/mcp_config.json:") + "\n\n"
|
|
1573
|
-
);
|
|
1916
|
+
process.stderr.write("\n" + style.dim(`Add this to ${getWindsurfMcpConfigPath()}:`) + "\n\n");
|
|
1574
1917
|
} else if (platform === "cline") {
|
|
1575
|
-
process.stderr.write("\n" + style.dim(
|
|
1576
|
-
process.stderr.write(
|
|
1577
|
-
style.dim(
|
|
1578
|
-
" macOS: ~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json"
|
|
1579
|
-
) + "\n"
|
|
1580
|
-
);
|
|
1581
|
-
process.stderr.write(
|
|
1582
|
-
style.dim(
|
|
1583
|
-
" Windows: %APPDATA%\\Code\\User\\globalStorage\\saoudrizwan.claude-dev\\settings\\cline_mcp_settings.json"
|
|
1584
|
-
) + "\n"
|
|
1585
|
-
);
|
|
1586
|
-
process.stderr.write(
|
|
1587
|
-
style.dim(
|
|
1588
|
-
" Linux: ~/.config/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json"
|
|
1589
|
-
) + "\n\n"
|
|
1590
|
-
);
|
|
1918
|
+
process.stderr.write("\n" + style.dim(`Add this to ${getClineMcpSettingsPath()}:`) + "\n\n");
|
|
1591
1919
|
} else if (platform === "gemini-cli") {
|
|
1592
1920
|
process.stderr.write(
|
|
1593
1921
|
"\n" + style.dim(
|
|
1594
|
-
|
|
1922
|
+
`Merge the snippet below into ${getGeminiCliSettingsPath()} (keep existing hooks and other keys). Restart Gemini CLI if it is already running.`
|
|
1595
1923
|
) + "\n\n"
|
|
1596
1924
|
);
|
|
1597
1925
|
} else if (platform === "kilo-code") {
|
|
1598
1926
|
process.stderr.write(
|
|
1599
|
-
"\n" + style.dim(
|
|
1927
|
+
"\n" + style.dim(`Add this to ${join(resolve(process.cwd()), ".kilocode", "mcp.json")}:`) + "\n\n"
|
|
1600
1928
|
);
|
|
1601
1929
|
} else if (platform === "github-copilot") {
|
|
1602
1930
|
process.stderr.write(
|
|
1603
1931
|
"\n" + style.dim(
|
|
1604
|
-
"
|
|
1932
|
+
"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."
|
|
1605
1933
|
) + "\n\n"
|
|
1606
1934
|
);
|
|
1607
1935
|
} else if (platform === "continue-dev") {
|
|
1608
|
-
process.stderr.write(
|
|
1609
|
-
"\n" + style.dim("Save this as .continue/mcpServers/shield.json in your workspace root.") + "\n\n"
|
|
1610
|
-
);
|
|
1936
|
+
process.stderr.write("\n" + style.dim(`Add this to ${getContinueConfigJsonPath()}:`) + "\n\n");
|
|
1611
1937
|
} else if (platform === "goose") {
|
|
1612
1938
|
process.stderr.write(
|
|
1613
1939
|
"\n" + style.dim("Add this to ~/.config/goose/config.yaml under the extensions key.") + "\n\n"
|
|
1614
1940
|
);
|
|
1615
1941
|
} else {
|
|
1616
|
-
process.stderr.write("\n" + style.dim(
|
|
1942
|
+
process.stderr.write("\n" + style.dim(`Add this to ${getCursorMcpJsonPath()}:`) + "\n\n");
|
|
1617
1943
|
}
|
|
1618
1944
|
process.stderr.write(style.cyan(snippetText) + "\n\n");
|
|
1619
1945
|
if (!usesInlineKey) {
|
|
@@ -1662,19 +1988,34 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
|
|
|
1662
1988
|
process.stderr.write(style.dim("Start a new Goose session after updating config.") + "\n");
|
|
1663
1989
|
}
|
|
1664
1990
|
}
|
|
1991
|
+
function dedupeAgentsByName(agents) {
|
|
1992
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1993
|
+
const out = [];
|
|
1994
|
+
for (const a of agents) {
|
|
1995
|
+
if (seen.has(a.name)) continue;
|
|
1996
|
+
seen.add(a.name);
|
|
1997
|
+
out.push(a);
|
|
1998
|
+
}
|
|
1999
|
+
return out;
|
|
2000
|
+
}
|
|
1665
2001
|
function mergeAgentsForPlatform(localAgents, remoteAgents, selectedPlatform) {
|
|
1666
|
-
const
|
|
1667
|
-
const
|
|
1668
|
-
|
|
2002
|
+
const byName = /* @__PURE__ */ new Map();
|
|
2003
|
+
for (const a of localAgents) {
|
|
2004
|
+
if (a.platform !== selectedPlatform) continue;
|
|
2005
|
+
if (!byName.has(a.name)) {
|
|
2006
|
+
byName.set(a.name, { ...a });
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
1669
2009
|
for (const r of remoteAgents) {
|
|
1670
2010
|
if (r.platform !== selectedPlatform) continue;
|
|
1671
|
-
if (
|
|
1672
|
-
|
|
1673
|
-
|
|
2011
|
+
if (!byName.has(r.name)) {
|
|
2012
|
+
byName.set(r.name, { name: r.name, platform: selectedPlatform });
|
|
2013
|
+
}
|
|
1674
2014
|
}
|
|
1675
|
-
return
|
|
2015
|
+
return [...byName.values()];
|
|
1676
2016
|
}
|
|
1677
|
-
async function runInit(explicitBaseUrl) {
|
|
2017
|
+
async function runInit(explicitBaseUrl, options) {
|
|
2018
|
+
const verbose = options?.verbose === true;
|
|
1678
2019
|
if (!process.stdin.isTTY) {
|
|
1679
2020
|
process.stderr.write(
|
|
1680
2021
|
style.red("Error: interactive terminal required. Cannot run init with piped input.") + "\n"
|
|
@@ -1724,6 +2065,16 @@ async function runInit(explicitBaseUrl) {
|
|
|
1724
2065
|
apiKey = existing.apiKey;
|
|
1725
2066
|
}
|
|
1726
2067
|
}
|
|
2068
|
+
if (apiKey.length === 0) {
|
|
2069
|
+
const signupDashboardUrl = deriveDashboardUrl(resolvedBaseUrl).replace(/\/+$/, "");
|
|
2070
|
+
console.log("");
|
|
2071
|
+
console.log(" Multicorn Shield controls what your AI agents can do.");
|
|
2072
|
+
console.log(" You need a free account to get an API key.");
|
|
2073
|
+
console.log("");
|
|
2074
|
+
console.log(` 1. Sign up or log in \u2192 ${signupDashboardUrl}`);
|
|
2075
|
+
console.log(" 2. Go to Settings \u2192 API Keys to create a key");
|
|
2076
|
+
console.log("");
|
|
2077
|
+
}
|
|
1727
2078
|
while (apiKey.length === 0) {
|
|
1728
2079
|
const input = await ask("API key (starts with mcs_): ");
|
|
1729
2080
|
const key = input.trim();
|
|
@@ -1779,7 +2130,7 @@ async function runInit(explicitBaseUrl) {
|
|
|
1779
2130
|
`
|
|
1780
2131
|
);
|
|
1781
2132
|
process.stderr.write(
|
|
1782
|
-
"\n" + style.bold("Try it:") + " " + style.cyan(
|
|
2133
|
+
"\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(
|
|
1783
2134
|
"npx multicorn-shield --wrap npx @modelcontextprotocol/server-filesystem /tmp"
|
|
1784
2135
|
) + "\n"
|
|
1785
2136
|
);
|
|
@@ -1800,10 +2151,8 @@ async function runInit(explicitBaseUrl) {
|
|
|
1800
2151
|
continue;
|
|
1801
2152
|
}
|
|
1802
2153
|
const remoteAccountAgents = await fetchRemoteAgentsSummaries(apiKey, resolvedBaseUrl);
|
|
1803
|
-
const agentsForPlatform =
|
|
1804
|
-
currentAgents,
|
|
1805
|
-
remoteAccountAgents,
|
|
1806
|
-
selectedPlatform
|
|
2154
|
+
const agentsForPlatform = dedupeAgentsByName(
|
|
2155
|
+
mergeAgentsForPlatform(currentAgents, remoteAccountAgents, selectedPlatform)
|
|
1807
2156
|
);
|
|
1808
2157
|
const localForPlatformCount = currentAgents.filter(
|
|
1809
2158
|
(a) => a.platform === selectedPlatform
|
|
@@ -1812,11 +2161,13 @@ async function runInit(explicitBaseUrl) {
|
|
|
1812
2161
|
(r) => r.platform === selectedPlatform
|
|
1813
2162
|
).length;
|
|
1814
2163
|
const savedSummary = currentAgents.length === 0 ? "none on disk" : currentAgents.map((a) => `${a.name} (${a.platform})`).join(", ");
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
2164
|
+
if (verbose) {
|
|
2165
|
+
process.stderr.write(
|
|
2166
|
+
style.dim(
|
|
2167
|
+
`[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}.`
|
|
2168
|
+
) + "\n"
|
|
2169
|
+
);
|
|
2170
|
+
}
|
|
1820
2171
|
if (agentsForPlatform.length > 0) {
|
|
1821
2172
|
process.stderr.write(
|
|
1822
2173
|
`
|
|
@@ -1856,6 +2207,14 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
1856
2207
|
}
|
|
1857
2208
|
}
|
|
1858
2209
|
}
|
|
2210
|
+
if (selectedPlatform === "cursor" || selectedPlatform === "github-copilot") {
|
|
2211
|
+
const where = selectedPlatform === "cursor" ? "Cursor" : "GitHub Copilot";
|
|
2212
|
+
process.stderr.write(
|
|
2213
|
+
"\n" + style.dim(
|
|
2214
|
+
`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.`
|
|
2215
|
+
) + "\n\n"
|
|
2216
|
+
);
|
|
2217
|
+
}
|
|
1859
2218
|
const prereqEntry = INIT_WIZARD_PLATFORM_REGISTRY.find((e) => e.slug === selectedPlatform);
|
|
1860
2219
|
if (prereqEntry?.prereqUrl !== void 0) {
|
|
1861
2220
|
const proceed = await promptHostedProxyInstallPrereq(
|
|
@@ -1871,7 +2230,7 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
1871
2230
|
continue;
|
|
1872
2231
|
}
|
|
1873
2232
|
}
|
|
1874
|
-
const agentName = await promptAgentName(ask, selectedPlatform);
|
|
2233
|
+
const agentName = await promptAgentName(ask, selectedPlatform, removeAgentNameBeforeSave);
|
|
1875
2234
|
let setupSucceeded = false;
|
|
1876
2235
|
if (selectedPlatform === "openclaw") {
|
|
1877
2236
|
let detection;
|
|
@@ -1997,7 +2356,9 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
1997
2356
|
) + style.cyan("~/.multicorn/windsurf-hooks") + style.dim(" if that is a concern.") + "\n\n"
|
|
1998
2357
|
);
|
|
1999
2358
|
process.stderr.write(
|
|
2000
|
-
style.dim(
|
|
2359
|
+
style.dim(
|
|
2360
|
+
"Try it: make a request in Windsurf - Shield will intercept the first tool call and ask for your consent."
|
|
2361
|
+
) + "\n"
|
|
2001
2362
|
);
|
|
2002
2363
|
configuredAgents.push({
|
|
2003
2364
|
selection,
|
|
@@ -2054,7 +2415,13 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
2054
2415
|
if (created && proxyUrl.length > 0) {
|
|
2055
2416
|
process.stderr.write("\n" + style.bold("Your Shield proxy URL:") + "\n");
|
|
2056
2417
|
process.stderr.write(" " + style.cyan(proxyUrl) + "\n");
|
|
2057
|
-
|
|
2418
|
+
await applyHostedProxyMcpConfig(
|
|
2419
|
+
selectedPlatform,
|
|
2420
|
+
proxyUrl,
|
|
2421
|
+
shortName,
|
|
2422
|
+
apiKey,
|
|
2423
|
+
initWorkspacePath
|
|
2424
|
+
);
|
|
2058
2425
|
configuredAgents.push({
|
|
2059
2426
|
selection,
|
|
2060
2427
|
platform: selectedPlatform,
|
|
@@ -2139,7 +2506,13 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
2139
2506
|
if (created && proxyUrl.length > 0) {
|
|
2140
2507
|
process.stderr.write("\n" + style.bold("Your Shield proxy URL:") + "\n");
|
|
2141
2508
|
process.stderr.write(" " + style.cyan(proxyUrl) + "\n");
|
|
2142
|
-
|
|
2509
|
+
await applyHostedProxyMcpConfig(
|
|
2510
|
+
selectedPlatform,
|
|
2511
|
+
proxyUrl,
|
|
2512
|
+
shortName,
|
|
2513
|
+
apiKey,
|
|
2514
|
+
initWorkspacePath
|
|
2515
|
+
);
|
|
2143
2516
|
configuredAgents.push({
|
|
2144
2517
|
selection,
|
|
2145
2518
|
platform: selectedPlatform,
|
|
@@ -2218,7 +2591,13 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
2218
2591
|
if (created && proxyUrl.length > 0) {
|
|
2219
2592
|
process.stderr.write("\n" + style.bold("Your Shield proxy URL:") + "\n");
|
|
2220
2593
|
process.stderr.write(" " + style.cyan(proxyUrl) + "\n");
|
|
2221
|
-
|
|
2594
|
+
await applyHostedProxyMcpConfig(
|
|
2595
|
+
selectedPlatform,
|
|
2596
|
+
proxyUrl,
|
|
2597
|
+
shortName,
|
|
2598
|
+
apiKey,
|
|
2599
|
+
initWorkspacePath
|
|
2600
|
+
);
|
|
2222
2601
|
configuredAgents.push({
|
|
2223
2602
|
selection,
|
|
2224
2603
|
platform: selectedPlatform,
|
|
@@ -2260,7 +2639,13 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
2260
2639
|
if (created && proxyUrl.length > 0) {
|
|
2261
2640
|
process.stderr.write("\n" + style.bold("Your Shield proxy URL:") + "\n");
|
|
2262
2641
|
process.stderr.write(" " + style.cyan(proxyUrl) + "\n");
|
|
2263
|
-
|
|
2642
|
+
await applyHostedProxyMcpConfig(
|
|
2643
|
+
selectedPlatform,
|
|
2644
|
+
proxyUrl,
|
|
2645
|
+
shortName,
|
|
2646
|
+
apiKey,
|
|
2647
|
+
initWorkspacePath
|
|
2648
|
+
);
|
|
2264
2649
|
configuredAgents.push({
|
|
2265
2650
|
selection,
|
|
2266
2651
|
platform: selectedPlatform,
|
|
@@ -2326,42 +2711,42 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
2326
2711
|
const blocks = [];
|
|
2327
2712
|
if (configuredPlatforms.has("openclaw")) {
|
|
2328
2713
|
blocks.push(
|
|
2329
|
-
"\n" + style.bold("OpenClaw") + "\n \u2192 Restart your gateway: " + style.cyan("openclaw gateway restart") + "\n \u2192 Start a session: " + style.cyan("openclaw tui") + "\n"
|
|
2714
|
+
"\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"
|
|
2330
2715
|
);
|
|
2331
2716
|
}
|
|
2332
2717
|
if (configuredPlatforms.has("claude-code")) {
|
|
2333
2718
|
blocks.push(
|
|
2334
|
-
"\n" + style.bold("Claude Code") + "\n \u2192 Start
|
|
2719
|
+
"\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"
|
|
2335
2720
|
);
|
|
2336
2721
|
}
|
|
2337
2722
|
if (configuredPlatforms.has("claude-desktop")) {
|
|
2338
2723
|
blocks.push(
|
|
2339
|
-
"\n" + style.bold("Claude Desktop") + "\n \u2192 Restart Claude Desktop to pick up config changes\n"
|
|
2724
|
+
"\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"
|
|
2340
2725
|
);
|
|
2341
2726
|
}
|
|
2342
2727
|
if (configuredPlatforms.has("cursor")) {
|
|
2343
2728
|
blocks.push(
|
|
2344
|
-
"\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
|
|
2729
|
+
"\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"
|
|
2345
2730
|
);
|
|
2346
2731
|
}
|
|
2347
2732
|
if (configuredPlatforms.has("kilo-code")) {
|
|
2348
2733
|
blocks.push(
|
|
2349
|
-
"\n" + style.bold("Kilo Code") + "\n \u2192 Restart the editor or reload the window if the MCP server does not appear\n \u2192
|
|
2734
|
+
"\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"
|
|
2350
2735
|
);
|
|
2351
2736
|
}
|
|
2352
2737
|
if (configuredPlatforms.has("github-copilot")) {
|
|
2353
2738
|
blocks.push(
|
|
2354
|
-
"\n" + style.bold("GitHub Copilot") + "\n \u2192 Reload the editor window if the MCP server does not appear\n \u2192
|
|
2739
|
+
"\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"
|
|
2355
2740
|
);
|
|
2356
2741
|
}
|
|
2357
2742
|
if (configuredPlatforms.has("continue-dev")) {
|
|
2358
2743
|
blocks.push(
|
|
2359
|
-
"\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"
|
|
2744
|
+
"\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"
|
|
2360
2745
|
);
|
|
2361
2746
|
}
|
|
2362
2747
|
if (configuredPlatforms.has("goose")) {
|
|
2363
2748
|
blocks.push(
|
|
2364
|
-
"\n" + style.bold("Goose") + "\n \u2192 Start a new Goose session after updating config\n"
|
|
2749
|
+
"\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"
|
|
2365
2750
|
);
|
|
2366
2751
|
}
|
|
2367
2752
|
const windsurfNativeConfigured = configuredAgents.some(
|
|
@@ -2372,12 +2757,12 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
2372
2757
|
);
|
|
2373
2758
|
if (windsurfNativeConfigured) {
|
|
2374
2759
|
blocks.push(
|
|
2375
|
-
"\n" + style.bold("Windsurf (native)") + "\n \u2192
|
|
2760
|
+
"\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"
|
|
2376
2761
|
);
|
|
2377
2762
|
}
|
|
2378
2763
|
if (windsurfHostedConfigured) {
|
|
2379
2764
|
blocks.push(
|
|
2380
|
-
"\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"
|
|
2765
|
+
"\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"
|
|
2381
2766
|
);
|
|
2382
2767
|
}
|
|
2383
2768
|
const clineNativeConfigured = configuredAgents.some(
|
|
@@ -2388,12 +2773,12 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
2388
2773
|
);
|
|
2389
2774
|
if (clineNativeConfigured) {
|
|
2390
2775
|
blocks.push(
|
|
2391
|
-
"\n" + style.bold("Cline (native)") + "\n \u2192
|
|
2776
|
+
"\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"
|
|
2392
2777
|
);
|
|
2393
2778
|
}
|
|
2394
2779
|
if (clineHostedConfigured) {
|
|
2395
2780
|
blocks.push(
|
|
2396
|
-
"\n" + style.bold("Cline (hosted)") + "\n \u2192 Restart Cline or reload the VS Code window\n"
|
|
2781
|
+
"\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"
|
|
2397
2782
|
);
|
|
2398
2783
|
}
|
|
2399
2784
|
const geminiCliNativeConfigured = configuredAgents.some(
|
|
@@ -2404,25 +2789,38 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
2404
2789
|
);
|
|
2405
2790
|
if (geminiCliNativeConfigured) {
|
|
2406
2791
|
blocks.push(
|
|
2407
|
-
"\n" + style.bold("Gemini CLI (native)") + "\n \u2192
|
|
2792
|
+
"\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"
|
|
2408
2793
|
);
|
|
2409
2794
|
}
|
|
2410
2795
|
if (geminiCliHostedConfigured) {
|
|
2411
2796
|
blocks.push(
|
|
2412
|
-
"\n" + style.bold("Gemini CLI (hosted)") + "\n \u2192
|
|
2797
|
+
"\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"
|
|
2798
|
+
);
|
|
2799
|
+
}
|
|
2800
|
+
if (configuredPlatforms.has("other-mcp")) {
|
|
2801
|
+
blocks.push(
|
|
2802
|
+
"\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"
|
|
2413
2803
|
);
|
|
2414
2804
|
}
|
|
2415
2805
|
if (blocks.length > 0) {
|
|
2416
2806
|
process.stderr.write("\n" + style.bold(style.violet("Next steps")) + "\n");
|
|
2417
2807
|
process.stderr.write(blocks.join("") + "\n");
|
|
2418
2808
|
}
|
|
2809
|
+
const dashboardUrl = deriveDashboardUrl(resolvedBaseUrl).replace(/\/+$/, "");
|
|
2810
|
+
console.log("");
|
|
2811
|
+
console.log(" Your dashboard");
|
|
2812
|
+
console.log(` \u2192 ${dashboardUrl}/agents`);
|
|
2813
|
+
console.log("");
|
|
2814
|
+
console.log(" Use any tool in your agent to see it appear.");
|
|
2815
|
+
console.log("");
|
|
2419
2816
|
}
|
|
2420
2817
|
return lastConfig;
|
|
2421
2818
|
}
|
|
2422
|
-
var style, BANNER, NativePluginPrerequisiteMissingError, CONFIG_DIR, CONFIG_PATH, OPENCLAW_CONFIG_PATH, ANSI_PATTERN, OPENCLAW_MIN_VERSION, INIT_WIZARD_PLATFORM_REGISTRY, INIT_WIZARD_MENU_SECTIONS, INIT_WIZARD_SELECTION_MAX, PLATFORM_BY_SELECTION, DEFAULT_SHIELD_API_BASE_URL;
|
|
2819
|
+
var SECRET_JSON_FILE_OPTIONS, style, BANNER, NativePluginPrerequisiteMissingError, CONFIG_DIR, CONFIG_PATH, OPENCLAW_CONFIG_PATH, ANSI_PATTERN, OPENCLAW_MIN_VERSION, INIT_WIZARD_PLATFORM_REGISTRY, INIT_WIZARD_MENU_SECTIONS, INIT_WIZARD_SELECTION_MAX, PLATFORM_BY_SELECTION, DEFAULT_SHIELD_API_BASE_URL;
|
|
2423
2820
|
var init_config = __esm({
|
|
2424
2821
|
"src/proxy/config.ts"() {
|
|
2425
2822
|
init_consent();
|
|
2823
|
+
SECRET_JSON_FILE_OPTIONS = { encoding: "utf8", mode: 384 };
|
|
2426
2824
|
style = {
|
|
2427
2825
|
violet: (s) => `\x1B[38;2;124;58;237m${s}\x1B[0m`,
|
|
2428
2826
|
violetLight: (s) => `\x1B[38;2;167;139;250m${s}\x1B[0m`,
|
|
@@ -3012,6 +3410,7 @@ function createProxyServer(config) {
|
|
|
3012
3410
|
const pendingLines = [];
|
|
3013
3411
|
let draining = false;
|
|
3014
3412
|
let stopped = false;
|
|
3413
|
+
let hasLoggedFirstAction = false;
|
|
3015
3414
|
async function refreshScopes() {
|
|
3016
3415
|
if (stopped) return;
|
|
3017
3416
|
if (agentId.length === 0) return;
|
|
@@ -3117,8 +3516,10 @@ function createProxyServer(config) {
|
|
|
3117
3516
|
);
|
|
3118
3517
|
}
|
|
3119
3518
|
}
|
|
3519
|
+
const rawCostCents = extractCostCents(toolParams.arguments);
|
|
3520
|
+
const costCents = Number.isFinite(rawCostCents) && rawCostCents > 0 ? Math.min(rawCostCents, 1e8) : 0;
|
|
3521
|
+
const costUsd = costCents > 0 ? costCents / 100 : void 0;
|
|
3120
3522
|
if (spendingChecker !== null) {
|
|
3121
|
-
const costCents = extractCostCents(toolParams.arguments);
|
|
3122
3523
|
if (costCents > 0) {
|
|
3123
3524
|
const spendResult = spendingChecker.checkSpend(costCents);
|
|
3124
3525
|
if (!spendResult.allowed) {
|
|
@@ -3137,7 +3538,8 @@ function createProxyServer(config) {
|
|
|
3137
3538
|
agent: config.agentName,
|
|
3138
3539
|
service,
|
|
3139
3540
|
actionType: action,
|
|
3140
|
-
status: "blocked"
|
|
3541
|
+
status: "blocked",
|
|
3542
|
+
...costUsd !== void 0 ? { cost: costUsd } : {}
|
|
3141
3543
|
});
|
|
3142
3544
|
config.logger.debug("Spending-blocked action logged.", { tool: toolParams.name });
|
|
3143
3545
|
}
|
|
@@ -3165,9 +3567,15 @@ function createProxyServer(config) {
|
|
|
3165
3567
|
agent: config.agentName,
|
|
3166
3568
|
service,
|
|
3167
3569
|
actionType: action,
|
|
3168
|
-
status: "approved"
|
|
3570
|
+
status: "approved",
|
|
3571
|
+
...costUsd !== void 0 ? { cost: costUsd } : {}
|
|
3169
3572
|
});
|
|
3170
3573
|
config.logger.debug("Approved action logged.", { tool: toolParams.name });
|
|
3574
|
+
if (!hasLoggedFirstAction) {
|
|
3575
|
+
hasLoggedFirstAction = true;
|
|
3576
|
+
const dashUrl = deriveDashboardUrl(config.baseUrl).replace(/\/+$/, "");
|
|
3577
|
+
config.logger.info(`First action recorded. View activity \u2192 ${dashUrl}/agents`);
|
|
3578
|
+
}
|
|
3171
3579
|
}
|
|
3172
3580
|
}
|
|
3173
3581
|
return null;
|
|
@@ -3421,7 +3829,10 @@ async function restoreClaudeDesktopMcpFromBackup() {
|
|
|
3421
3829
|
}
|
|
3422
3830
|
root["mcpServers"] = backup.mcpServers;
|
|
3423
3831
|
await mkdir(dirname(configPath), { recursive: true });
|
|
3424
|
-
await writeFile(configPath, JSON.stringify(root, null, 2) + "\n", {
|
|
3832
|
+
await writeFile(configPath, JSON.stringify(root, null, 2) + "\n", {
|
|
3833
|
+
encoding: "utf8",
|
|
3834
|
+
mode: 384
|
|
3835
|
+
});
|
|
3425
3836
|
}
|
|
3426
3837
|
var init_restore = __esm({
|
|
3427
3838
|
"src/extension/restore.ts"() {
|
|
@@ -3448,6 +3859,7 @@ function parseArgs(argv) {
|
|
|
3448
3859
|
let agentName = "";
|
|
3449
3860
|
let deleteAgentName = "";
|
|
3450
3861
|
let apiKey = void 0;
|
|
3862
|
+
let verbose = false;
|
|
3451
3863
|
for (let i = 0; i < args.length; i++) {
|
|
3452
3864
|
const arg = args[i];
|
|
3453
3865
|
if (arg === "init") {
|
|
@@ -3547,6 +3959,8 @@ function parseArgs(argv) {
|
|
|
3547
3959
|
apiKey = next;
|
|
3548
3960
|
i++;
|
|
3549
3961
|
}
|
|
3962
|
+
} else if (arg === "--verbose" || arg === "--debug") {
|
|
3963
|
+
verbose = true;
|
|
3550
3964
|
}
|
|
3551
3965
|
}
|
|
3552
3966
|
return {
|
|
@@ -3558,7 +3972,8 @@ function parseArgs(argv) {
|
|
|
3558
3972
|
dashboardUrl,
|
|
3559
3973
|
agentName,
|
|
3560
3974
|
deleteAgentName,
|
|
3561
|
-
apiKey
|
|
3975
|
+
apiKey,
|
|
3976
|
+
verbose
|
|
3562
3977
|
};
|
|
3563
3978
|
}
|
|
3564
3979
|
function printHelp() {
|
|
@@ -3584,6 +3999,7 @@ function printHelp() {
|
|
|
3584
3999
|
" Shield's permission layer.",
|
|
3585
4000
|
"",
|
|
3586
4001
|
"Options:",
|
|
4002
|
+
" --verbose, --debug Print extra diagnostics during init (menu selection, agent counts)",
|
|
3587
4003
|
" --api-key <key> Multicorn API key (overrides MULTICORN_API_KEY env var and config file)",
|
|
3588
4004
|
" --log-level <level> Log level: debug | info | warn | error (default: info)",
|
|
3589
4005
|
" --base-url <url> Multicorn API base URL (default: https://api.multicorn.ai)",
|
|
@@ -3614,7 +4030,7 @@ async function runCli() {
|
|
|
3614
4030
|
process.exit(0);
|
|
3615
4031
|
}
|
|
3616
4032
|
if (cli.subcommand === "init") {
|
|
3617
|
-
await runInit(cli.baseUrl);
|
|
4033
|
+
await runInit(cli.baseUrl, { verbose: cli.verbose });
|
|
3618
4034
|
return;
|
|
3619
4035
|
}
|
|
3620
4036
|
if (cli.subcommand === "agents") {
|