multicorn-shield 1.11.1 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/README.md +10 -2
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/multicorn-proxy.js +250 -201
- package/dist/multicorn-shield.js +246 -199
- package/dist/openclaw-hook/HOOK.md +5 -3
- package/dist/openclaw-hook/handler.js +1 -1
- package/dist/openclaw-plugin/multicorn-shield.js +28 -7
- package/dist/openclaw-plugin/openclaw.plugin.json +1 -1
- package/dist/shield-extension.js +3 -12
- package/package.json +4 -2
- package/plugins/windsurf/hooks/scripts/pre-action.cjs +126 -17
package/dist/multicorn-proxy.js
CHANGED
|
@@ -644,7 +644,34 @@ function isVersionAtLeast(version, minimum) {
|
|
|
644
644
|
}
|
|
645
645
|
return true;
|
|
646
646
|
}
|
|
647
|
-
|
|
647
|
+
function buildOpenClawShieldPluginConfig(params) {
|
|
648
|
+
return {
|
|
649
|
+
apiKey: OPENCLAW_SHIELD_API_KEY_ENV_REF,
|
|
650
|
+
baseUrl: params.baseUrl,
|
|
651
|
+
agentName: params.agentName,
|
|
652
|
+
failMode: "closed"
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
function detectStaleOpenClawHookIntegration(openclawConfig) {
|
|
656
|
+
const hooks = openclawConfig["hooks"];
|
|
657
|
+
if (typeof hooks !== "object" || hooks === null) return false;
|
|
658
|
+
const internal = hooks["internal"];
|
|
659
|
+
if (typeof internal !== "object" || internal === null) return false;
|
|
660
|
+
const entries = internal["entries"];
|
|
661
|
+
if (typeof entries !== "object" || entries === null) return false;
|
|
662
|
+
return Object.prototype.hasOwnProperty.call(entries, "multicorn-shield");
|
|
663
|
+
}
|
|
664
|
+
function staleOpenClawHookIntegrationMessage() {
|
|
665
|
+
return "Deprecated OpenClaw gateway hook config detected at hooks.internal.entries.multicorn-shield. Shield now uses the multicorn-shield plugin under plugins.entries. Disable the old hook manually (for example openclaw hooks disable multicorn-shield) and remove hooks.internal.entries.multicorn-shield when you are ready.";
|
|
666
|
+
}
|
|
667
|
+
function stripLegacyOpenClawShieldEntryFields(shield) {
|
|
668
|
+
delete shield["env"];
|
|
669
|
+
delete shield["apiKey"];
|
|
670
|
+
delete shield["baseUrl"];
|
|
671
|
+
delete shield["agentName"];
|
|
672
|
+
delete shield["failMode"];
|
|
673
|
+
}
|
|
674
|
+
async function updateOpenClawConfigIfPresent(_apiKey, baseUrl, agentName) {
|
|
648
675
|
let raw;
|
|
649
676
|
try {
|
|
650
677
|
raw = await readFile(OPENCLAW_CONFIG_PATH, "utf8");
|
|
@@ -660,49 +687,66 @@ async function updateOpenClawConfigIfPresent(apiKey, baseUrl, agentName) {
|
|
|
660
687
|
} catch {
|
|
661
688
|
return "parse-error";
|
|
662
689
|
}
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
690
|
+
if (detectStaleOpenClawHookIntegration(obj)) {
|
|
691
|
+
process.stderr.write(
|
|
692
|
+
style.yellow("\u26A0") + " " + staleOpenClawHookIntegrationMessage() + "\n"
|
|
693
|
+
);
|
|
667
694
|
}
|
|
668
|
-
let
|
|
669
|
-
if (
|
|
670
|
-
|
|
671
|
-
|
|
695
|
+
let plugins = obj["plugins"];
|
|
696
|
+
if (plugins === void 0 || typeof plugins !== "object") {
|
|
697
|
+
plugins = {};
|
|
698
|
+
obj["plugins"] = plugins;
|
|
672
699
|
}
|
|
673
|
-
let
|
|
674
|
-
if (
|
|
675
|
-
|
|
676
|
-
|
|
700
|
+
let pluginEntries = plugins["entries"];
|
|
701
|
+
if (pluginEntries === void 0 || typeof pluginEntries !== "object") {
|
|
702
|
+
pluginEntries = {};
|
|
703
|
+
plugins["entries"] = pluginEntries;
|
|
677
704
|
}
|
|
678
|
-
let shield =
|
|
705
|
+
let shield = pluginEntries["multicorn-shield"];
|
|
679
706
|
if (shield === void 0 || typeof shield !== "object") {
|
|
680
|
-
shield = { enabled: true
|
|
681
|
-
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
shield["
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
env["MULTICORN_AGENT_NAME"] = agentName;
|
|
707
|
+
shield = { enabled: true };
|
|
708
|
+
pluginEntries["multicorn-shield"] = shield;
|
|
709
|
+
}
|
|
710
|
+
stripLegacyOpenClawShieldEntryFields(shield);
|
|
711
|
+
const resolvedAgentName = agentName ?? (typeof shield["config"] === "object" && shield["config"] !== null && typeof shield["config"]["agentName"] === "string" ? String(shield["config"]["agentName"]) : void 0);
|
|
712
|
+
if (resolvedAgentName !== void 0) {
|
|
713
|
+
shield["enabled"] = true;
|
|
714
|
+
shield["config"] = buildOpenClawShieldPluginConfig({
|
|
715
|
+
baseUrl,
|
|
716
|
+
agentName: resolvedAgentName
|
|
717
|
+
});
|
|
692
718
|
const agentsList = obj["agents"];
|
|
693
719
|
const list = agentsList?.["list"];
|
|
694
720
|
if (Array.isArray(list) && list.length > 0) {
|
|
695
721
|
const first = list[0];
|
|
696
|
-
if (first["id"] !==
|
|
697
|
-
first["id"] =
|
|
698
|
-
first["name"] =
|
|
722
|
+
if (first["id"] !== resolvedAgentName) {
|
|
723
|
+
first["id"] = resolvedAgentName;
|
|
724
|
+
first["name"] = resolvedAgentName;
|
|
699
725
|
}
|
|
700
726
|
} else {
|
|
701
727
|
if (agentsList !== void 0 && typeof agentsList === "object") {
|
|
702
|
-
agentsList["list"] = [{ id:
|
|
728
|
+
agentsList["list"] = [{ id: resolvedAgentName, name: resolvedAgentName }];
|
|
703
729
|
} else {
|
|
704
|
-
obj["agents"] = { list: [{ id:
|
|
730
|
+
obj["agents"] = { list: [{ id: resolvedAgentName, name: resolvedAgentName }] };
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
} else {
|
|
734
|
+
shield["enabled"] = true;
|
|
735
|
+
const existingConfig = shield["config"];
|
|
736
|
+
if (typeof existingConfig === "object" && existingConfig !== null) {
|
|
737
|
+
const cfg = { ...existingConfig };
|
|
738
|
+
cfg["apiKey"] = OPENCLAW_SHIELD_API_KEY_ENV_REF;
|
|
739
|
+
cfg["baseUrl"] = baseUrl;
|
|
740
|
+
if (cfg["failMode"] !== "open" && cfg["failMode"] !== "closed") {
|
|
741
|
+
cfg["failMode"] = "closed";
|
|
705
742
|
}
|
|
743
|
+
shield["config"] = cfg;
|
|
744
|
+
} else {
|
|
745
|
+
shield["config"] = {
|
|
746
|
+
apiKey: OPENCLAW_SHIELD_API_KEY_ENV_REF,
|
|
747
|
+
baseUrl,
|
|
748
|
+
failMode: "closed"
|
|
749
|
+
};
|
|
706
750
|
}
|
|
707
751
|
}
|
|
708
752
|
await writeFile(
|
|
@@ -712,6 +756,10 @@ async function updateOpenClawConfigIfPresent(apiKey, baseUrl, agentName) {
|
|
|
712
756
|
);
|
|
713
757
|
return "updated";
|
|
714
758
|
}
|
|
759
|
+
function isPastedShieldApiKeyInput(input) {
|
|
760
|
+
const trimmed = input.trim();
|
|
761
|
+
return trimmed.startsWith("mcs_") && trimmed.length >= 8;
|
|
762
|
+
}
|
|
715
763
|
async function validateApiKey(apiKey, baseUrl) {
|
|
716
764
|
try {
|
|
717
765
|
const response = await fetch(`${baseUrl}/api/v1/agents`, {
|
|
@@ -1081,40 +1129,6 @@ async function installCodexCliNativeHooks() {
|
|
|
1081
1129
|
};
|
|
1082
1130
|
await writeFile(getCodexHooksJsonPath(), JSON.stringify(hooksConfig, null, 2) + "\n", "utf8");
|
|
1083
1131
|
}
|
|
1084
|
-
async function promptCodexCliIntegrationMode(ask) {
|
|
1085
|
-
process.stderr.write("\n" + style.bold("Codex CLI integration") + "\n");
|
|
1086
|
-
process.stderr.write(
|
|
1087
|
-
" " + style.violet("1") + ". Native plugin - Shield checks terminal (Bash) commands via Codex Hooks\n"
|
|
1088
|
-
);
|
|
1089
|
-
process.stderr.write(
|
|
1090
|
-
" " + style.violet("2") + ". Hosted proxy - govern MCP server traffic via config.toml\n"
|
|
1091
|
-
);
|
|
1092
|
-
let choice = 0;
|
|
1093
|
-
while (choice === 0) {
|
|
1094
|
-
const input = await ask("Choose integration (1-2): ");
|
|
1095
|
-
const num = parseInt(input.trim(), 10);
|
|
1096
|
-
if (num === 1) choice = 1;
|
|
1097
|
-
if (num === 2) choice = 2;
|
|
1098
|
-
}
|
|
1099
|
-
return choice === 1 ? "native" : "hosted";
|
|
1100
|
-
}
|
|
1101
|
-
async function promptClineIntegrationMode(ask) {
|
|
1102
|
-
process.stderr.write("\n" + style.bold("Cline integration") + "\n");
|
|
1103
|
-
process.stderr.write(
|
|
1104
|
-
" " + style.violet("1") + ". Native plugin (recommended) - Cline Hooks see every file, terminal, browser, and MCP action\n"
|
|
1105
|
-
);
|
|
1106
|
-
process.stderr.write(
|
|
1107
|
-
" " + style.violet("2") + ". Hosted proxy - govern MCP traffic only (paste proxy URL into Cline MCP settings)\n"
|
|
1108
|
-
);
|
|
1109
|
-
let choice = 0;
|
|
1110
|
-
while (choice === 0) {
|
|
1111
|
-
const input = await ask("Choose integration (1-2): ");
|
|
1112
|
-
const num = parseInt(input.trim(), 10);
|
|
1113
|
-
if (num === 1) choice = 1;
|
|
1114
|
-
if (num === 2) choice = 2;
|
|
1115
|
-
}
|
|
1116
|
-
return choice === 1 ? "native" : "hosted";
|
|
1117
|
-
}
|
|
1118
1132
|
function getGeminiCliHooksInstallDir() {
|
|
1119
1133
|
return join(homedir(), ".multicorn", "gemini-cli-hooks");
|
|
1120
1134
|
}
|
|
@@ -1440,40 +1454,6 @@ async function installGeminiCliNativeHooks(ask) {
|
|
|
1440
1454
|
await mkdir(dirname(settingsPath), { recursive: true });
|
|
1441
1455
|
await writeFile(settingsPath, JSON.stringify(existing, null, 2) + "\n", SECRET_JSON_FILE_OPTIONS);
|
|
1442
1456
|
}
|
|
1443
|
-
async function promptGeminiCliIntegrationMode(ask) {
|
|
1444
|
-
process.stderr.write("\n" + style.bold("Gemini CLI integration") + "\n");
|
|
1445
|
-
process.stderr.write(
|
|
1446
|
-
" " + style.violet("1") + ". Native plugin (recommended) - Gemini CLI Hooks see every file, terminal, web, and MCP action\n"
|
|
1447
|
-
);
|
|
1448
|
-
process.stderr.write(
|
|
1449
|
-
" " + style.violet("2") + ". Hosted proxy - govern MCP traffic only (paste proxy URL into Gemini CLI settings)\n"
|
|
1450
|
-
);
|
|
1451
|
-
let choice = 0;
|
|
1452
|
-
while (choice === 0) {
|
|
1453
|
-
const input = await ask("Choose integration (1-2): ");
|
|
1454
|
-
const num = parseInt(input.trim(), 10);
|
|
1455
|
-
if (num === 1) choice = 1;
|
|
1456
|
-
if (num === 2) choice = 2;
|
|
1457
|
-
}
|
|
1458
|
-
return choice === 1 ? "native" : "hosted";
|
|
1459
|
-
}
|
|
1460
|
-
async function promptOpencodeIntegrationMode(ask) {
|
|
1461
|
-
process.stderr.write("\n" + style.bold("OpenCode integration") + "\n");
|
|
1462
|
-
process.stderr.write(
|
|
1463
|
-
" " + style.violet("1") + ". Native plugin (recommended) - Shield checks primary-agent tool execution via OpenCode Hooks\n"
|
|
1464
|
-
);
|
|
1465
|
-
process.stderr.write(
|
|
1466
|
-
" " + style.violet("2") + ". Hosted proxy - govern MCP server traffic via opencode.json (full subagent coverage when tools use MCP through Shield)\n"
|
|
1467
|
-
);
|
|
1468
|
-
let choice = 0;
|
|
1469
|
-
while (choice === 0) {
|
|
1470
|
-
const input = await ask("Choose integration (1-2): ");
|
|
1471
|
-
const num = parseInt(input.trim(), 10);
|
|
1472
|
-
if (num === 1) choice = 1;
|
|
1473
|
-
if (num === 2) choice = 2;
|
|
1474
|
-
}
|
|
1475
|
-
return choice === 1 ? "native" : "hosted";
|
|
1476
|
-
}
|
|
1477
1457
|
function getClaudeDesktopConfigPath() {
|
|
1478
1458
|
switch (process.platform) {
|
|
1479
1459
|
case "win32":
|
|
@@ -1537,8 +1517,11 @@ function getClineMcpSettingsPath() {
|
|
|
1537
1517
|
);
|
|
1538
1518
|
}
|
|
1539
1519
|
}
|
|
1540
|
-
function
|
|
1541
|
-
|
|
1520
|
+
function resolveInitHybridIntegrationMode(explicit) {
|
|
1521
|
+
return explicit ?? "native";
|
|
1522
|
+
}
|
|
1523
|
+
function platformMenuLabelForPickerSelection(sel) {
|
|
1524
|
+
const slug = PICKER_SLUG_BY_SELECTION[sel];
|
|
1542
1525
|
if (slug === void 0) return "Unknown";
|
|
1543
1526
|
const entry = INIT_WIZARD_PLATFORM_REGISTRY.find((e) => e.slug === slug);
|
|
1544
1527
|
return entry?.displayName ?? slug;
|
|
@@ -1556,48 +1539,31 @@ async function promptPlatformSelection(ask) {
|
|
|
1556
1539
|
process.stderr.write(
|
|
1557
1540
|
"\n" + style.bold(style.violet("Which platform are you connecting?")) + "\n\n"
|
|
1558
1541
|
);
|
|
1542
|
+
process.stderr.write(" " + style.dim("Native plugin (files, terminal, browser)") + "\n");
|
|
1559
1543
|
let optionNum = 1;
|
|
1560
|
-
for (const
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1544
|
+
for (const slug of INIT_WIZARD_PICKER_NATIVE_SLUGS) {
|
|
1545
|
+
const entry = INIT_WIZARD_PLATFORM_REGISTRY.find((e) => e.slug === slug);
|
|
1546
|
+
const label = entry?.displayName ?? slug;
|
|
1547
|
+
const indent = optionNum >= 10 ? " " : " ";
|
|
1548
|
+
process.stderr.write(`${indent}${style.violet(String(optionNum))}. ${label}
|
|
1565
1549
|
`);
|
|
1566
|
-
|
|
1567
|
-
}
|
|
1550
|
+
optionNum++;
|
|
1568
1551
|
}
|
|
1569
|
-
process.stderr.write(
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1552
|
+
process.stderr.write("\n");
|
|
1553
|
+
process.stderr.write(" MCP agents (Cursor, Copilot, others) are set up\n");
|
|
1554
|
+
process.stderr.write(` in the dashboard: ${INIT_WIZARD_DASHBOARD_AGENTS_URL}
|
|
1555
|
+
|
|
1556
|
+
`);
|
|
1574
1557
|
let selection = 0;
|
|
1575
1558
|
while (selection === 0) {
|
|
1576
|
-
const input = await ask(`Select (1-${String(
|
|
1559
|
+
const input = await ask(`Select (1-${String(INIT_WIZARD_PICKER_SELECTION_MAX)}): `);
|
|
1577
1560
|
const num = parseInt(input.trim(), 10);
|
|
1578
|
-
if (num >= 1 && num <=
|
|
1561
|
+
if (num >= 1 && num <= INIT_WIZARD_PICKER_SELECTION_MAX) {
|
|
1579
1562
|
selection = num;
|
|
1580
1563
|
}
|
|
1581
1564
|
}
|
|
1582
1565
|
return selection;
|
|
1583
1566
|
}
|
|
1584
|
-
async function promptWindsurfIntegrationMode(ask) {
|
|
1585
|
-
process.stderr.write("\n" + style.bold("Windsurf integration") + "\n");
|
|
1586
|
-
process.stderr.write(
|
|
1587
|
-
" " + style.violet("1") + ". Native plugin (recommended) - Cascade Hooks see every file, terminal, and MCP action\n"
|
|
1588
|
-
);
|
|
1589
|
-
process.stderr.write(
|
|
1590
|
-
" " + style.violet("2") + ". Hosted proxy - govern MCP traffic only (paste proxy URL into mcp_config)\n"
|
|
1591
|
-
);
|
|
1592
|
-
let choice = 0;
|
|
1593
|
-
while (choice === 0) {
|
|
1594
|
-
const input = await ask("Choose integration (1-2): ");
|
|
1595
|
-
const num = parseInt(input.trim(), 10);
|
|
1596
|
-
if (num === 1) choice = 1;
|
|
1597
|
-
if (num === 2) choice = 2;
|
|
1598
|
-
}
|
|
1599
|
-
return choice === 1 ? "native" : "hosted";
|
|
1600
|
-
}
|
|
1601
1567
|
async function arrowSelect(options, ask, fallbackLabel) {
|
|
1602
1568
|
if (options.length === 1) {
|
|
1603
1569
|
const only = options[0] ?? "";
|
|
@@ -1801,6 +1767,14 @@ async function createProxyConfig(baseUrl, apiKey, agentName, targetUrl, serverNa
|
|
|
1801
1767
|
function shouldEmbedKeyInHostedProxyUrl(platform) {
|
|
1802
1768
|
return HOSTED_PROXY_PLATFORMS_WITH_URL_KEY.has(platform);
|
|
1803
1769
|
}
|
|
1770
|
+
function buildClineHostedMcpEntry(url) {
|
|
1771
|
+
return {
|
|
1772
|
+
type: "streamableHttp",
|
|
1773
|
+
url,
|
|
1774
|
+
timeout: 60,
|
|
1775
|
+
disabled: false
|
|
1776
|
+
};
|
|
1777
|
+
}
|
|
1804
1778
|
function hostedProxyUrlWithKeyParam(proxyUrl, apiKey) {
|
|
1805
1779
|
if (apiKey.length === 0) {
|
|
1806
1780
|
process.stderr.write(
|
|
@@ -2204,9 +2178,11 @@ async function applyHostedProxyMcpConfig(platform, proxyUrl, shortName, apiKey,
|
|
|
2204
2178
|
printHostedProxyJsonParseWarning(getWindsurfMcpConfigPath());
|
|
2205
2179
|
}
|
|
2206
2180
|
} else if (platform === "cline") {
|
|
2207
|
-
result = await mergeMcpServersObjectStyle(
|
|
2208
|
-
|
|
2209
|
-
|
|
2181
|
+
result = await mergeMcpServersObjectStyle(
|
|
2182
|
+
getClineMcpSettingsPath(),
|
|
2183
|
+
shortName,
|
|
2184
|
+
buildClineHostedMcpEntry(proxyUrlWithKeyWhenNeeded)
|
|
2185
|
+
);
|
|
2210
2186
|
if (result === "parse-error") {
|
|
2211
2187
|
printHostedProxyJsonParseWarning(getClineMcpSettingsPath());
|
|
2212
2188
|
}
|
|
@@ -2496,9 +2472,7 @@ Authorization = "Bearer ${bearerToken}"
|
|
|
2496
2472
|
snippetText = platform === "cline" ? JSON.stringify(
|
|
2497
2473
|
{
|
|
2498
2474
|
mcpServers: {
|
|
2499
|
-
[shortName]:
|
|
2500
|
-
[urlKey]: urlInSnippet
|
|
2501
|
-
}
|
|
2475
|
+
[shortName]: buildClineHostedMcpEntry(urlInSnippet)
|
|
2502
2476
|
}
|
|
2503
2477
|
},
|
|
2504
2478
|
null,
|
|
@@ -2661,6 +2635,7 @@ function mergeAgentsForPlatform(localAgents, remoteAgents, selectedPlatform) {
|
|
|
2661
2635
|
}
|
|
2662
2636
|
async function runInit(explicitBaseUrl, options) {
|
|
2663
2637
|
const verbose = options?.verbose === true;
|
|
2638
|
+
const hybridIntegration = resolveInitHybridIntegrationMode(options?.integration);
|
|
2664
2639
|
if (!process.stdin.isTTY) {
|
|
2665
2640
|
process.stderr.write(
|
|
2666
2641
|
style.red("Error: interactive terminal required. Cannot run init with piped input.") + "\n"
|
|
@@ -2705,9 +2680,31 @@ async function runInit(explicitBaseUrl, options) {
|
|
|
2705
2680
|
if (existing !== null && existing.apiKey.startsWith("mcs_") && existing.apiKey.length >= 8) {
|
|
2706
2681
|
const masked = "mcs_..." + existing.apiKey.slice(-4);
|
|
2707
2682
|
process.stderr.write("Found existing API key: " + style.cyan(masked) + "\n");
|
|
2708
|
-
const answer = await ask("Use this key? (Y/n) ");
|
|
2709
|
-
|
|
2710
|
-
|
|
2683
|
+
const answer = await ask("Use this key? (Y/n, or paste a new mcs_ key) ");
|
|
2684
|
+
const trimmed = answer.trim();
|
|
2685
|
+
if (trimmed.toLowerCase() !== "n") {
|
|
2686
|
+
const candidate = isPastedShieldApiKeyInput(trimmed) ? trimmed : existing.apiKey;
|
|
2687
|
+
const spinner = withSpinner("Validating key...");
|
|
2688
|
+
let reuseResult;
|
|
2689
|
+
try {
|
|
2690
|
+
reuseResult = await validateApiKey(candidate, resolvedBaseUrl);
|
|
2691
|
+
} catch (error) {
|
|
2692
|
+
spinner.stop(false, "Validation failed");
|
|
2693
|
+
throw error;
|
|
2694
|
+
}
|
|
2695
|
+
if (reuseResult.valid) {
|
|
2696
|
+
spinner.stop(true, "Key validated");
|
|
2697
|
+
apiKey = candidate;
|
|
2698
|
+
} else {
|
|
2699
|
+
spinner.stop(false, reuseResult.error ?? "Validation failed.");
|
|
2700
|
+
if (isPastedShieldApiKeyInput(trimmed)) {
|
|
2701
|
+
process.stderr.write(style.red("That key was rejected. Enter a new key below.") + "\n");
|
|
2702
|
+
} else {
|
|
2703
|
+
process.stderr.write(
|
|
2704
|
+
style.yellow("Existing key is no longer valid. Enter a new key below.") + "\n"
|
|
2705
|
+
);
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2711
2708
|
}
|
|
2712
2709
|
}
|
|
2713
2710
|
if (apiKey.length === 0) {
|
|
@@ -2759,8 +2756,8 @@ async function runInit(explicitBaseUrl, options) {
|
|
|
2759
2756
|
let removeAgentNameBeforeSave = void 0;
|
|
2760
2757
|
const initWorkspacePath = resolve(process.cwd());
|
|
2761
2758
|
const selection = await promptPlatformSelection(ask);
|
|
2762
|
-
const selectedPlatform =
|
|
2763
|
-
const selectedLabel =
|
|
2759
|
+
const selectedPlatform = PICKER_SLUG_BY_SELECTION[selection] ?? "openclaw";
|
|
2760
|
+
const selectedLabel = platformMenuLabelForPickerSelection(selection);
|
|
2764
2761
|
if (selectedPlatform === "other-mcp") {
|
|
2765
2762
|
const raw = existing !== null ? { ...existing } : {};
|
|
2766
2763
|
raw["apiKey"] = apiKey;
|
|
@@ -2894,7 +2891,7 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
2894
2891
|
}
|
|
2895
2892
|
if (detection.status === "parse-error") {
|
|
2896
2893
|
process.stderr.write(
|
|
2897
|
-
style.red("\u2717") + " Could not update OpenClaw config. Set
|
|
2894
|
+
style.red("\u2717") + " Could not update OpenClaw config. Set plugins.entries.multicorn-shield.config.apiKey in ~/.openclaw/openclaw.json, or run npx multicorn-shield init.\n"
|
|
2898
2895
|
);
|
|
2899
2896
|
}
|
|
2900
2897
|
if (detection.status === "detected") {
|
|
@@ -2934,7 +2931,7 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
2934
2931
|
if (result === "parse-error") {
|
|
2935
2932
|
spinner.stop(
|
|
2936
2933
|
false,
|
|
2937
|
-
"Could not update OpenClaw config. Set
|
|
2934
|
+
"Could not update OpenClaw config. Set plugins.entries.multicorn-shield.config.apiKey in ~/.openclaw/openclaw.json, or run npx multicorn-shield init."
|
|
2938
2935
|
);
|
|
2939
2936
|
} else {
|
|
2940
2937
|
spinner.stop(
|
|
@@ -2981,7 +2978,7 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
2981
2978
|
});
|
|
2982
2979
|
setupSucceeded = true;
|
|
2983
2980
|
} else if (selectedPlatform === "windsurf") {
|
|
2984
|
-
const windsurfMode =
|
|
2981
|
+
const windsurfMode = hybridIntegration;
|
|
2985
2982
|
if (windsurfMode === "native") {
|
|
2986
2983
|
try {
|
|
2987
2984
|
await installWindsurfNativeHooks();
|
|
@@ -3080,7 +3077,7 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
3080
3077
|
}
|
|
3081
3078
|
}
|
|
3082
3079
|
} else if (selectedPlatform === "gemini-cli") {
|
|
3083
|
-
const geminiMode =
|
|
3080
|
+
const geminiMode = hybridIntegration;
|
|
3084
3081
|
if (geminiMode === "native") {
|
|
3085
3082
|
try {
|
|
3086
3083
|
await installGeminiCliNativeHooks(ask);
|
|
@@ -3174,7 +3171,7 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
3174
3171
|
}
|
|
3175
3172
|
}
|
|
3176
3173
|
} else if (selectedPlatform === "opencode") {
|
|
3177
|
-
const opencodeMode =
|
|
3174
|
+
const opencodeMode = hybridIntegration;
|
|
3178
3175
|
if (opencodeMode === "native") {
|
|
3179
3176
|
try {
|
|
3180
3177
|
await installOpenCodeNativePlugin();
|
|
@@ -3263,7 +3260,7 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
3263
3260
|
}
|
|
3264
3261
|
}
|
|
3265
3262
|
} else if (selectedPlatform === "codex-cli") {
|
|
3266
|
-
const codexMode =
|
|
3263
|
+
const codexMode = hybridIntegration;
|
|
3267
3264
|
if (codexMode === "native") {
|
|
3268
3265
|
try {
|
|
3269
3266
|
await installCodexCliNativeHooks();
|
|
@@ -3352,7 +3349,7 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
3352
3349
|
}
|
|
3353
3350
|
}
|
|
3354
3351
|
} else if (selectedPlatform === "cline") {
|
|
3355
|
-
const clineMode =
|
|
3352
|
+
const clineMode = hybridIntegration;
|
|
3356
3353
|
if (clineMode === "native") {
|
|
3357
3354
|
try {
|
|
3358
3355
|
await installClineNativeHooks();
|
|
@@ -3783,9 +3780,7 @@ async function writeLocalMcpEntry(client, agentName, localProxyUrl, apiKey, work
|
|
|
3783
3780
|
return await mergeMcpServersObjectStyle(
|
|
3784
3781
|
filePath,
|
|
3785
3782
|
entryKey,
|
|
3786
|
-
|
|
3787
|
-
url
|
|
3788
|
-
},
|
|
3783
|
+
buildClineHostedMcpEntry(url),
|
|
3789
3784
|
legacyKeys
|
|
3790
3785
|
) === "ok" ? filePath : null;
|
|
3791
3786
|
case "windsurf":
|
|
@@ -3946,7 +3941,7 @@ Authorization = "${authHeader}"
|
|
|
3946
3941
|
}
|
|
3947
3942
|
}
|
|
3948
3943
|
}
|
|
3949
|
-
var SECRET_JSON_FILE_OPTIONS, style, BANNER, NativePluginPrerequisiteMissingError, CONFIG_DIR, CONFIG_PATH, OPENCLAW_CONFIG_PATH, ANSI_PATTERN, UPSTREAM_AUTH_KNOWN_SCHEME_WITH_PAYLOAD, OPENCLAW_MIN_VERSION, INIT_WIZARD_PLATFORM_REGISTRY,
|
|
3944
|
+
var SECRET_JSON_FILE_OPTIONS, style, BANNER, NativePluginPrerequisiteMissingError, CONFIG_DIR, CONFIG_PATH, OPENCLAW_CONFIG_PATH, ANSI_PATTERN, UPSTREAM_AUTH_KNOWN_SCHEME_WITH_PAYLOAD, OPENCLAW_MIN_VERSION, OPENCLAW_SHIELD_API_KEY_ENV_REF, INIT_WIZARD_PLATFORM_REGISTRY, INIT_WIZARD_PICKER_NATIVE_SLUGS, INIT_WIZARD_DASHBOARD_AGENTS_URL, INIT_WIZARD_PICKER_SELECTION_MAX, PICKER_SLUG_BY_SELECTION, INIT_EXISTING_AGENTS_PLATFORM_ACTIONS, HOSTED_PROXY_PLATFORMS_WITH_URL_KEY, OPENCODE_CONFIG_SCHEMA_URL, DEFAULT_SHIELD_API_BASE_URL, CODING_CLIENTS, CLIENT_DISPLAY_NAMES, CLIENT_RELOAD_INSTRUCTIONS, CODING_CLIENT_TO_PLATFORM;
|
|
3950
3945
|
var init_config = __esm({
|
|
3951
3946
|
"src/proxy/config.ts"() {
|
|
3952
3947
|
init_consent();
|
|
@@ -3980,6 +3975,7 @@ var init_config = __esm({
|
|
|
3980
3975
|
ANSI_PATTERN = new RegExp(String.fromCharCode(27) + "\\[[0-9;]*[a-zA-Z]", "g");
|
|
3981
3976
|
UPSTREAM_AUTH_KNOWN_SCHEME_WITH_PAYLOAD = /^(Bearer|Basic|Token|ApiKey)(\s+)(.+)$/is;
|
|
3982
3977
|
OPENCLAW_MIN_VERSION = "2026.2.26";
|
|
3978
|
+
OPENCLAW_SHIELD_API_KEY_ENV_REF = "${MULTICORN_API_KEY}";
|
|
3983
3979
|
INIT_WIZARD_PLATFORM_REGISTRY = [
|
|
3984
3980
|
{ slug: "openclaw", displayName: "OpenClaw", section: "native" },
|
|
3985
3981
|
{ slug: "claude-code", displayName: "Claude Code", section: "native" },
|
|
@@ -4036,25 +4032,17 @@ var init_config = __esm({
|
|
|
4036
4032
|
},
|
|
4037
4033
|
{ slug: "other-mcp", displayName: "Local MCP / Other", section: "hosted" }
|
|
4038
4034
|
];
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
{ title: "Recommended (native plugin)", items: itemsFor("native") },
|
|
4046
|
-
{ title: "Hosted proxy (MCP only)", items: itemsFor("hosted") }
|
|
4047
|
-
];
|
|
4048
|
-
})();
|
|
4049
|
-
INIT_WIZARD_SELECTION_MAX = INIT_WIZARD_PLATFORM_REGISTRY.length;
|
|
4035
|
+
INIT_WIZARD_PICKER_NATIVE_SLUGS = INIT_WIZARD_PLATFORM_REGISTRY.filter((e) => e.section === "native").map((e) => e.slug);
|
|
4036
|
+
INIT_WIZARD_DASHBOARD_AGENTS_URL = "https://app.multicorn.ai/agents/new";
|
|
4037
|
+
INIT_WIZARD_PICKER_SELECTION_MAX = INIT_WIZARD_PICKER_NATIVE_SLUGS.length;
|
|
4038
|
+
PICKER_SLUG_BY_SELECTION = Object.fromEntries(
|
|
4039
|
+
INIT_WIZARD_PICKER_NATIVE_SLUGS.map((slug, i) => [i + 1, slug])
|
|
4040
|
+
);
|
|
4050
4041
|
INIT_EXISTING_AGENTS_PLATFORM_ACTIONS = [
|
|
4051
4042
|
"Add a new agent alongside these",
|
|
4052
4043
|
"Replace an existing agent",
|
|
4053
4044
|
"Skip - choose a different platform"
|
|
4054
4045
|
];
|
|
4055
|
-
PLATFORM_BY_SELECTION = Object.fromEntries(
|
|
4056
|
-
INIT_WIZARD_PLATFORM_REGISTRY.map((e, i) => [i + 1, e.slug])
|
|
4057
|
-
);
|
|
4058
4046
|
HOSTED_PROXY_PLATFORMS_WITH_URL_KEY = /* @__PURE__ */ new Set([
|
|
4059
4047
|
"cursor",
|
|
4060
4048
|
"claude-desktop",
|
|
@@ -5062,7 +5050,7 @@ var init_package = __esm({
|
|
|
5062
5050
|
"package.json"() {
|
|
5063
5051
|
package_default = {
|
|
5064
5052
|
name: "multicorn-shield",
|
|
5065
|
-
version: "1.
|
|
5053
|
+
version: "1.12.0",
|
|
5066
5054
|
description: "The control layer for AI agents: permissions, consent, spending limits, and audit logging.",
|
|
5067
5055
|
license: "MIT",
|
|
5068
5056
|
author: "Multicorn AI Pty Ltd",
|
|
@@ -5141,7 +5129,8 @@ var init_package = __esm({
|
|
|
5141
5129
|
prepare: "husky || true",
|
|
5142
5130
|
"release:patch": "npm version patch && pnpm publish",
|
|
5143
5131
|
"release:minor": "npm version minor && pnpm publish",
|
|
5144
|
-
"release:major": "npm version major && pnpm publish"
|
|
5132
|
+
"release:major": "npm version major && pnpm publish",
|
|
5133
|
+
local: "pnpm build &&node dist/multicorn-shield.js init --base-url http://localhost:8080"
|
|
5145
5134
|
},
|
|
5146
5135
|
"lint-staged": {
|
|
5147
5136
|
"*.ts": [
|
|
@@ -5169,6 +5158,7 @@ var init_package = __esm({
|
|
|
5169
5158
|
"@size-limit/file": "^11.1.6",
|
|
5170
5159
|
"@types/node": "^22.0.0",
|
|
5171
5160
|
"@vitest/coverage-v8": "^3.0.5",
|
|
5161
|
+
ajv: "^8.18.0",
|
|
5172
5162
|
eslint: "^9.19.0",
|
|
5173
5163
|
"eslint-config-prettier": "^10.0.1",
|
|
5174
5164
|
"eslint-plugin-unicorn": "^57.0.0",
|
|
@@ -5358,6 +5348,16 @@ function readPidfile(agent) {
|
|
|
5358
5348
|
return null;
|
|
5359
5349
|
}
|
|
5360
5350
|
}
|
|
5351
|
+
function supervisorPidFromSession(data) {
|
|
5352
|
+
if (typeof data.supervisorPid === "number") return data.supervisorPid;
|
|
5353
|
+
if (typeof data.pid === "number") return data.pid;
|
|
5354
|
+
return void 0;
|
|
5355
|
+
}
|
|
5356
|
+
function isAgentSessionRunning(data) {
|
|
5357
|
+
if (data === null) return false;
|
|
5358
|
+
const pid = supervisorPidFromSession(data);
|
|
5359
|
+
return typeof pid === "number" && isProcessAlive(pid);
|
|
5360
|
+
}
|
|
5361
5361
|
function removePidfile(agent) {
|
|
5362
5362
|
const p = pidfilePath(agent);
|
|
5363
5363
|
try {
|
|
@@ -5398,7 +5398,7 @@ function runStatus() {
|
|
|
5398
5398
|
const fsReg = readFsRegistry();
|
|
5399
5399
|
process.stderr.write("Active sessions:\n\n");
|
|
5400
5400
|
for (const s of sessions) {
|
|
5401
|
-
const supervisorAlive =
|
|
5401
|
+
const supervisorAlive = isAgentSessionRunning(s);
|
|
5402
5402
|
const fsEntry = fsReg[s.dir];
|
|
5403
5403
|
const fsAlive = fsEntry !== void 0 && isProcessAlive(fsEntry.pid);
|
|
5404
5404
|
const proxyAlive = proxyReg !== null && proxyReg.port === s.proxyPort && isProcessAlive(proxyReg.pid);
|
|
@@ -5545,9 +5545,7 @@ async function withResourceLock(fn) {
|
|
|
5545
5545
|
}
|
|
5546
5546
|
}
|
|
5547
5547
|
function liveAgents() {
|
|
5548
|
-
return listAllPidfiles().filter(
|
|
5549
|
-
(p) => typeof p.supervisorPid === "number" && isProcessAlive(p.supervisorPid)
|
|
5550
|
-
);
|
|
5548
|
+
return listAllPidfiles().filter((p) => isAgentSessionRunning(p));
|
|
5551
5549
|
}
|
|
5552
5550
|
function agentsReferencingProxy(proxyPort, agents, excludeAgent) {
|
|
5553
5551
|
return agents.filter((a) => a.agent !== excludeAgent && a.proxyPort === proxyPort);
|
|
@@ -5565,27 +5563,37 @@ async function nextFreePort(start, claimed, isBusy, range = FS_PORT_SCAN_RANGE)
|
|
|
5565
5563
|
`No free port for the filesystem server in range ${String(start)}-${String(start + range)}.`
|
|
5566
5564
|
);
|
|
5567
5565
|
}
|
|
5566
|
+
async function resolveBaseUrl(explicitBaseUrl) {
|
|
5567
|
+
if (explicitBaseUrl !== void 0 && explicitBaseUrl.trim().length > 0) {
|
|
5568
|
+
return explicitBaseUrl.trim();
|
|
5569
|
+
}
|
|
5570
|
+
const envBaseUrl = process.env["MULTICORN_BASE_URL"];
|
|
5571
|
+
if (typeof envBaseUrl === "string" && envBaseUrl.trim().length > 0) {
|
|
5572
|
+
return envBaseUrl.trim();
|
|
5573
|
+
}
|
|
5574
|
+
const config = await loadConfig();
|
|
5575
|
+
if (config !== null && config.baseUrl.length > 0) {
|
|
5576
|
+
return config.baseUrl;
|
|
5577
|
+
}
|
|
5578
|
+
const fromFile = await readBaseUrlFromConfig();
|
|
5579
|
+
if (fromFile !== void 0 && fromFile.length > 0) {
|
|
5580
|
+
return fromFile;
|
|
5581
|
+
}
|
|
5582
|
+
return DEFAULT_SHIELD_API_BASE_URL;
|
|
5583
|
+
}
|
|
5568
5584
|
async function resolveConfig(opts) {
|
|
5585
|
+
const baseUrl = await resolveBaseUrl(opts.baseUrl);
|
|
5569
5586
|
const fromFlag = opts.apiKey;
|
|
5570
5587
|
if (fromFlag && fromFlag.length > 0) {
|
|
5571
|
-
return {
|
|
5572
|
-
apiKey: fromFlag,
|
|
5573
|
-
baseUrl: opts.baseUrl ?? DEFAULT_SHIELD_API_BASE_URL
|
|
5574
|
-
};
|
|
5588
|
+
return { apiKey: fromFlag, baseUrl };
|
|
5575
5589
|
}
|
|
5576
5590
|
const envKey = process.env["MULTICORN_API_KEY"];
|
|
5577
5591
|
if (typeof envKey === "string" && envKey.length > 0) {
|
|
5578
|
-
return {
|
|
5579
|
-
apiKey: envKey,
|
|
5580
|
-
baseUrl: opts.baseUrl ?? DEFAULT_SHIELD_API_BASE_URL
|
|
5581
|
-
};
|
|
5592
|
+
return { apiKey: envKey, baseUrl };
|
|
5582
5593
|
}
|
|
5583
5594
|
const config = await loadConfig();
|
|
5584
5595
|
if (config !== null) {
|
|
5585
|
-
return {
|
|
5586
|
-
apiKey: config.apiKey,
|
|
5587
|
-
baseUrl: opts.baseUrl ?? config.baseUrl
|
|
5588
|
-
};
|
|
5596
|
+
return { apiKey: config.apiKey, baseUrl };
|
|
5589
5597
|
}
|
|
5590
5598
|
process.stderr.write(
|
|
5591
5599
|
"No API key found. Pass --api-key <key> or add it to ~/.multicorn/config.json.\n"
|
|
@@ -5641,8 +5649,24 @@ function startLocalProxyDetached(port, apiBaseUrl) {
|
|
|
5641
5649
|
child.unref();
|
|
5642
5650
|
return child;
|
|
5643
5651
|
}
|
|
5644
|
-
async function ensureProxy(proxyPort, apiBaseUrl) {
|
|
5645
|
-
|
|
5652
|
+
async function ensureProxy(proxyPort, apiBaseUrl, options) {
|
|
5653
|
+
const forceRespawn = options?.forceRespawn === true;
|
|
5654
|
+
if (forceRespawn) {
|
|
5655
|
+
const reg2 = readProxyRegistry();
|
|
5656
|
+
if (reg2 !== null && reg2.port === proxyPort && isProcessAlive(reg2.pid)) {
|
|
5657
|
+
killWithEscalation(reg2.pid, true);
|
|
5658
|
+
}
|
|
5659
|
+
if (reg2 !== null && reg2.port === proxyPort) {
|
|
5660
|
+
try {
|
|
5661
|
+
unlinkSync(PROXY_REGISTRY);
|
|
5662
|
+
} catch {
|
|
5663
|
+
}
|
|
5664
|
+
}
|
|
5665
|
+
for (let i = 0; i < 20; i++) {
|
|
5666
|
+
if (!await isPortListening(proxyPort)) break;
|
|
5667
|
+
await sleep3(100);
|
|
5668
|
+
}
|
|
5669
|
+
} else if (await probeProxyHealth(proxyPort)) {
|
|
5646
5670
|
const reg2 = readProxyRegistry();
|
|
5647
5671
|
const managed = reg2 !== null && reg2.port === proxyPort && isProcessAlive(reg2.pid);
|
|
5648
5672
|
return { reused: true, managed };
|
|
@@ -5878,8 +5902,9 @@ async function runStop(agent) {
|
|
|
5878
5902
|
process.exit(1);
|
|
5879
5903
|
}
|
|
5880
5904
|
await withResourceLock(() => {
|
|
5881
|
-
|
|
5882
|
-
|
|
5905
|
+
const supervisorPid = supervisorPidFromSession(data);
|
|
5906
|
+
if (typeof supervisorPid === "number" && isProcessAlive(supervisorPid)) {
|
|
5907
|
+
killWithEscalation(supervisorPid);
|
|
5883
5908
|
}
|
|
5884
5909
|
removePidfile(agent);
|
|
5885
5910
|
releaseFsServerIfUnused(data.dir, agent);
|
|
@@ -5909,7 +5934,8 @@ Run it once with the folder: npx multicorn-shield files restart <dir> --agent ${
|
|
|
5909
5934
|
await runDetached({
|
|
5910
5935
|
...opts,
|
|
5911
5936
|
dir,
|
|
5912
|
-
proxyPort: opts.proxyPort ?? existing?.proxyPort
|
|
5937
|
+
proxyPort: opts.proxyPort ?? existing?.proxyPort,
|
|
5938
|
+
respawnProxy: true});
|
|
5913
5939
|
}
|
|
5914
5940
|
async function runDetached(opts) {
|
|
5915
5941
|
const absDir = resolve(opts.dir);
|
|
@@ -5920,8 +5946,7 @@ async function runDetached(opts) {
|
|
|
5920
5946
|
}
|
|
5921
5947
|
const existing = readPidfile(opts.agent);
|
|
5922
5948
|
if (existing !== null) {
|
|
5923
|
-
|
|
5924
|
-
if (alive) {
|
|
5949
|
+
if (isAgentSessionRunning(existing)) {
|
|
5925
5950
|
process.stderr.write(
|
|
5926
5951
|
`Already running for agent "${opts.agent}" (fs :${String(existing.fsPort)}, proxy :${String(existing.proxyPort)}).
|
|
5927
5952
|
Stop with: npx multicorn-shield files stop --agent ${opts.agent}
|
|
@@ -5936,6 +5961,7 @@ Stop with: npx multicorn-shield files stop --agent ${opts.agent}
|
|
|
5936
5961
|
if (opts.proxyPort !== void 0) args.push("--proxy-port", String(opts.proxyPort));
|
|
5937
5962
|
if (opts.apiKey !== void 0) args.push("--api-key", opts.apiKey);
|
|
5938
5963
|
if (opts.baseUrl !== void 0) args.push("--base-url", opts.baseUrl);
|
|
5964
|
+
if (opts.respawnProxy) args.push("--respawn-proxy");
|
|
5939
5965
|
if (opts.client !== void 0) args.push("--client", opts.client);
|
|
5940
5966
|
const scriptPath = process.argv[1] ?? resolve("dist/multicorn-shield.js");
|
|
5941
5967
|
const logFile = join(PIDFILE_DIR, `files-${opts.agent}.log`);
|
|
@@ -6019,8 +6045,7 @@ async function runFilesCommand(opts) {
|
|
|
6019
6045
|
const proxyPort = opts.proxyPort ?? DEFAULT_PROXY_PORT;
|
|
6020
6046
|
const existingPidfile = readPidfile(opts.agent);
|
|
6021
6047
|
if (existingPidfile !== null) {
|
|
6022
|
-
|
|
6023
|
-
if (supervisorAlive) {
|
|
6048
|
+
if (isAgentSessionRunning(existingPidfile)) {
|
|
6024
6049
|
process.stderr.write(
|
|
6025
6050
|
`A session for agent "${opts.agent}" is already running. Run 'files stop --agent ${opts.agent}' first, or use a different --agent name.
|
|
6026
6051
|
`
|
|
@@ -6034,7 +6059,9 @@ async function runFilesCommand(opts) {
|
|
|
6034
6059
|
let fsReused;
|
|
6035
6060
|
try {
|
|
6036
6061
|
const ensured = await withResourceLock(async () => {
|
|
6037
|
-
const proxyRes = await ensureProxy(proxyPort, config.baseUrl
|
|
6062
|
+
const proxyRes = await ensureProxy(proxyPort, config.baseUrl, {
|
|
6063
|
+
forceRespawn: opts.respawnProxy
|
|
6064
|
+
});
|
|
6038
6065
|
const fsRes = await ensureFsServer(realDir, opts.port);
|
|
6039
6066
|
writePidfile({
|
|
6040
6067
|
agent: opts.agent,
|
|
@@ -6330,6 +6357,8 @@ function parseArgs(argv) {
|
|
|
6330
6357
|
let filesForeground = false;
|
|
6331
6358
|
let filesStatus = false;
|
|
6332
6359
|
let filesRestart = false;
|
|
6360
|
+
let filesRespawnProxy = false;
|
|
6361
|
+
let initIntegration = void 0;
|
|
6333
6362
|
for (let i = 0; i < args.length; i++) {
|
|
6334
6363
|
const arg = args[i];
|
|
6335
6364
|
if (arg === "init") {
|
|
@@ -6380,6 +6409,8 @@ function parseArgs(argv) {
|
|
|
6380
6409
|
filesStop = true;
|
|
6381
6410
|
} else if (token === "--foreground") {
|
|
6382
6411
|
filesForeground = true;
|
|
6412
|
+
} else if (token === "--respawn-proxy") {
|
|
6413
|
+
filesRespawnProxy = true;
|
|
6383
6414
|
} else if (token === "--detach") ; else if (token === "stop") {
|
|
6384
6415
|
filesStop = true;
|
|
6385
6416
|
} else if (token === "status") {
|
|
@@ -6488,6 +6519,12 @@ function parseArgs(argv) {
|
|
|
6488
6519
|
}
|
|
6489
6520
|
} else if (arg === "--verbose" || arg === "--debug") {
|
|
6490
6521
|
verbose = true;
|
|
6522
|
+
} else if (arg === "--integration") {
|
|
6523
|
+
const next = args[i + 1];
|
|
6524
|
+
if (next === "native" || next === "hosted") {
|
|
6525
|
+
initIntegration = next;
|
|
6526
|
+
i++;
|
|
6527
|
+
}
|
|
6491
6528
|
}
|
|
6492
6529
|
}
|
|
6493
6530
|
return {
|
|
@@ -6501,6 +6538,7 @@ function parseArgs(argv) {
|
|
|
6501
6538
|
deleteAgentName,
|
|
6502
6539
|
apiKey,
|
|
6503
6540
|
verbose,
|
|
6541
|
+
initIntegration,
|
|
6504
6542
|
filesDir,
|
|
6505
6543
|
filesPort,
|
|
6506
6544
|
filesProxyPort,
|
|
@@ -6508,7 +6546,8 @@ function parseArgs(argv) {
|
|
|
6508
6546
|
filesClient,
|
|
6509
6547
|
filesForeground,
|
|
6510
6548
|
filesStatus,
|
|
6511
|
-
filesRestart
|
|
6549
|
+
filesRestart,
|
|
6550
|
+
filesRespawnProxy
|
|
6512
6551
|
};
|
|
6513
6552
|
}
|
|
6514
6553
|
function printHelp() {
|
|
@@ -6518,7 +6557,12 @@ function printHelp() {
|
|
|
6518
6557
|
"",
|
|
6519
6558
|
"Usage:",
|
|
6520
6559
|
" npx multicorn-shield init",
|
|
6521
|
-
" Interactive setup
|
|
6560
|
+
" Interactive setup for native plugins (files, terminal, browser).",
|
|
6561
|
+
" Native: OpenClaw, Claude Code, Windsurf, Cline, Gemini CLI, OpenCode, Codex CLI.",
|
|
6562
|
+
" MCP-only clients (Cursor, Copilot, etc.): https://app.multicorn.ai/agents/new",
|
|
6563
|
+
" Saves API key to ~/.multicorn/config.json.",
|
|
6564
|
+
" --integration hosted Use hosted MCP proxy for Windsurf, Cline, Gemini CLI,",
|
|
6565
|
+
" OpenCode, or Codex CLI (default: native).",
|
|
6522
6566
|
"",
|
|
6523
6567
|
" npx multicorn-shield files <dir> --agent <name> [--client <client>]",
|
|
6524
6568
|
" Share a local folder with a coding agent. Starts a filesystem MCP server",
|
|
@@ -6599,7 +6643,10 @@ async function runCli() {
|
|
|
6599
6643
|
process.exit(0);
|
|
6600
6644
|
}
|
|
6601
6645
|
if (cli.subcommand === "init") {
|
|
6602
|
-
await runInit(cli.baseUrl, {
|
|
6646
|
+
await runInit(cli.baseUrl, {
|
|
6647
|
+
verbose: cli.verbose,
|
|
6648
|
+
...cli.initIntegration !== void 0 ? { integration: cli.initIntegration } : {}
|
|
6649
|
+
});
|
|
6603
6650
|
return;
|
|
6604
6651
|
}
|
|
6605
6652
|
if (cli.subcommand === "files") {
|
|
@@ -6615,7 +6662,8 @@ async function runCli() {
|
|
|
6615
6662
|
client: void 0,
|
|
6616
6663
|
foreground: true,
|
|
6617
6664
|
status: true,
|
|
6618
|
-
restart: false
|
|
6665
|
+
restart: false,
|
|
6666
|
+
respawnProxy: false
|
|
6619
6667
|
});
|
|
6620
6668
|
return;
|
|
6621
6669
|
}
|
|
@@ -6640,7 +6688,8 @@ async function runCli() {
|
|
|
6640
6688
|
client: cli.filesClient,
|
|
6641
6689
|
foreground: cli.filesForeground,
|
|
6642
6690
|
status: false,
|
|
6643
|
-
restart: cli.filesRestart
|
|
6691
|
+
restart: cli.filesRestart,
|
|
6692
|
+
respawnProxy: cli.filesRespawnProxy
|
|
6644
6693
|
});
|
|
6645
6694
|
return;
|
|
6646
6695
|
}
|