multicorn-shield 1.11.0 → 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 +31 -0
- package/README.md +10 -2
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/multicorn-proxy.js +349 -212
- package/dist/multicorn-shield.js +340 -209
- 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/server.js +3499 -0
- package/dist/shield-extension.js +3 -12
- package/package.json +5 -3
- package/plugins/windsurf/hooks/scripts/pre-action.cjs +126 -17
package/dist/multicorn-shield.js
CHANGED
|
@@ -658,7 +658,35 @@ function isVersionAtLeast(version, minimum) {
|
|
|
658
658
|
}
|
|
659
659
|
return true;
|
|
660
660
|
}
|
|
661
|
-
|
|
661
|
+
var OPENCLAW_SHIELD_API_KEY_ENV_REF = "${MULTICORN_API_KEY}";
|
|
662
|
+
function buildOpenClawShieldPluginConfig(params) {
|
|
663
|
+
return {
|
|
664
|
+
apiKey: OPENCLAW_SHIELD_API_KEY_ENV_REF,
|
|
665
|
+
baseUrl: params.baseUrl,
|
|
666
|
+
agentName: params.agentName,
|
|
667
|
+
failMode: "closed"
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
function detectStaleOpenClawHookIntegration(openclawConfig) {
|
|
671
|
+
const hooks = openclawConfig["hooks"];
|
|
672
|
+
if (typeof hooks !== "object" || hooks === null) return false;
|
|
673
|
+
const internal = hooks["internal"];
|
|
674
|
+
if (typeof internal !== "object" || internal === null) return false;
|
|
675
|
+
const entries = internal["entries"];
|
|
676
|
+
if (typeof entries !== "object" || entries === null) return false;
|
|
677
|
+
return Object.prototype.hasOwnProperty.call(entries, "multicorn-shield");
|
|
678
|
+
}
|
|
679
|
+
function staleOpenClawHookIntegrationMessage() {
|
|
680
|
+
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.";
|
|
681
|
+
}
|
|
682
|
+
function stripLegacyOpenClawShieldEntryFields(shield) {
|
|
683
|
+
delete shield["env"];
|
|
684
|
+
delete shield["apiKey"];
|
|
685
|
+
delete shield["baseUrl"];
|
|
686
|
+
delete shield["agentName"];
|
|
687
|
+
delete shield["failMode"];
|
|
688
|
+
}
|
|
689
|
+
async function updateOpenClawConfigIfPresent(_apiKey, baseUrl, agentName) {
|
|
662
690
|
let raw;
|
|
663
691
|
try {
|
|
664
692
|
raw = await readFile(OPENCLAW_CONFIG_PATH, "utf8");
|
|
@@ -674,50 +702,67 @@ async function updateOpenClawConfigIfPresent(apiKey, baseUrl, agentName) {
|
|
|
674
702
|
} catch {
|
|
675
703
|
return "parse-error";
|
|
676
704
|
}
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
705
|
+
if (detectStaleOpenClawHookIntegration(obj)) {
|
|
706
|
+
process.stderr.write(
|
|
707
|
+
style.yellow("\u26A0") + " " + staleOpenClawHookIntegrationMessage() + "\n"
|
|
708
|
+
);
|
|
681
709
|
}
|
|
682
|
-
let
|
|
683
|
-
if (
|
|
684
|
-
|
|
685
|
-
|
|
710
|
+
let plugins = obj["plugins"];
|
|
711
|
+
if (plugins === void 0 || typeof plugins !== "object") {
|
|
712
|
+
plugins = {};
|
|
713
|
+
obj["plugins"] = plugins;
|
|
686
714
|
}
|
|
687
|
-
let
|
|
688
|
-
if (
|
|
689
|
-
|
|
690
|
-
|
|
715
|
+
let pluginEntries = plugins["entries"];
|
|
716
|
+
if (pluginEntries === void 0 || typeof pluginEntries !== "object") {
|
|
717
|
+
pluginEntries = {};
|
|
718
|
+
plugins["entries"] = pluginEntries;
|
|
691
719
|
}
|
|
692
|
-
let shield =
|
|
720
|
+
let shield = pluginEntries["multicorn-shield"];
|
|
693
721
|
if (shield === void 0 || typeof shield !== "object") {
|
|
694
|
-
shield = { enabled: true
|
|
695
|
-
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
shield["
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
env["MULTICORN_AGENT_NAME"] = agentName;
|
|
722
|
+
shield = { enabled: true };
|
|
723
|
+
pluginEntries["multicorn-shield"] = shield;
|
|
724
|
+
}
|
|
725
|
+
stripLegacyOpenClawShieldEntryFields(shield);
|
|
726
|
+
const resolvedAgentName = agentName ?? (typeof shield["config"] === "object" && shield["config"] !== null && typeof shield["config"]["agentName"] === "string" ? String(shield["config"]["agentName"]) : void 0);
|
|
727
|
+
if (resolvedAgentName !== void 0) {
|
|
728
|
+
shield["enabled"] = true;
|
|
729
|
+
shield["config"] = buildOpenClawShieldPluginConfig({
|
|
730
|
+
baseUrl,
|
|
731
|
+
agentName: resolvedAgentName
|
|
732
|
+
});
|
|
706
733
|
const agentsList = obj["agents"];
|
|
707
734
|
const list = agentsList?.["list"];
|
|
708
735
|
if (Array.isArray(list) && list.length > 0) {
|
|
709
736
|
const first = list[0];
|
|
710
|
-
if (first["id"] !==
|
|
711
|
-
first["id"] =
|
|
712
|
-
first["name"] =
|
|
737
|
+
if (first["id"] !== resolvedAgentName) {
|
|
738
|
+
first["id"] = resolvedAgentName;
|
|
739
|
+
first["name"] = resolvedAgentName;
|
|
713
740
|
}
|
|
714
741
|
} else {
|
|
715
742
|
if (agentsList !== void 0 && typeof agentsList === "object") {
|
|
716
|
-
agentsList["list"] = [{ id:
|
|
743
|
+
agentsList["list"] = [{ id: resolvedAgentName, name: resolvedAgentName }];
|
|
717
744
|
} else {
|
|
718
|
-
obj["agents"] = { list: [{ id:
|
|
745
|
+
obj["agents"] = { list: [{ id: resolvedAgentName, name: resolvedAgentName }] };
|
|
719
746
|
}
|
|
720
747
|
}
|
|
748
|
+
} else {
|
|
749
|
+
shield["enabled"] = true;
|
|
750
|
+
const existingConfig = shield["config"];
|
|
751
|
+
if (typeof existingConfig === "object" && existingConfig !== null) {
|
|
752
|
+
const cfg = { ...existingConfig };
|
|
753
|
+
cfg["apiKey"] = OPENCLAW_SHIELD_API_KEY_ENV_REF;
|
|
754
|
+
cfg["baseUrl"] = baseUrl;
|
|
755
|
+
if (cfg["failMode"] !== "open" && cfg["failMode"] !== "closed") {
|
|
756
|
+
cfg["failMode"] = "closed";
|
|
757
|
+
}
|
|
758
|
+
shield["config"] = cfg;
|
|
759
|
+
} else {
|
|
760
|
+
shield["config"] = {
|
|
761
|
+
apiKey: OPENCLAW_SHIELD_API_KEY_ENV_REF,
|
|
762
|
+
baseUrl,
|
|
763
|
+
failMode: "closed"
|
|
764
|
+
};
|
|
765
|
+
}
|
|
721
766
|
}
|
|
722
767
|
await writeFile(
|
|
723
768
|
OPENCLAW_CONFIG_PATH,
|
|
@@ -726,6 +771,10 @@ async function updateOpenClawConfigIfPresent(apiKey, baseUrl, agentName) {
|
|
|
726
771
|
);
|
|
727
772
|
return "updated";
|
|
728
773
|
}
|
|
774
|
+
function isPastedShieldApiKeyInput(input) {
|
|
775
|
+
const trimmed = input.trim();
|
|
776
|
+
return trimmed.startsWith("mcs_") && trimmed.length >= 8;
|
|
777
|
+
}
|
|
729
778
|
async function validateApiKey(apiKey, baseUrl) {
|
|
730
779
|
try {
|
|
731
780
|
const response = await fetch(`${baseUrl}/api/v1/agents`, {
|
|
@@ -1095,40 +1144,6 @@ async function installCodexCliNativeHooks() {
|
|
|
1095
1144
|
};
|
|
1096
1145
|
await writeFile(getCodexHooksJsonPath(), JSON.stringify(hooksConfig, null, 2) + "\n", "utf8");
|
|
1097
1146
|
}
|
|
1098
|
-
async function promptCodexCliIntegrationMode(ask) {
|
|
1099
|
-
process.stderr.write("\n" + style.bold("Codex CLI integration") + "\n");
|
|
1100
|
-
process.stderr.write(
|
|
1101
|
-
" " + style.violet("1") + ". Native plugin - Shield checks terminal (Bash) commands via Codex Hooks\n"
|
|
1102
|
-
);
|
|
1103
|
-
process.stderr.write(
|
|
1104
|
-
" " + style.violet("2") + ". Hosted proxy - govern MCP server traffic via config.toml\n"
|
|
1105
|
-
);
|
|
1106
|
-
let choice = 0;
|
|
1107
|
-
while (choice === 0) {
|
|
1108
|
-
const input = await ask("Choose integration (1-2): ");
|
|
1109
|
-
const num = parseInt(input.trim(), 10);
|
|
1110
|
-
if (num === 1) choice = 1;
|
|
1111
|
-
if (num === 2) choice = 2;
|
|
1112
|
-
}
|
|
1113
|
-
return choice === 1 ? "native" : "hosted";
|
|
1114
|
-
}
|
|
1115
|
-
async function promptClineIntegrationMode(ask) {
|
|
1116
|
-
process.stderr.write("\n" + style.bold("Cline integration") + "\n");
|
|
1117
|
-
process.stderr.write(
|
|
1118
|
-
" " + style.violet("1") + ". Native plugin (recommended) - Cline Hooks see every file, terminal, browser, and MCP action\n"
|
|
1119
|
-
);
|
|
1120
|
-
process.stderr.write(
|
|
1121
|
-
" " + style.violet("2") + ". Hosted proxy - govern MCP traffic only (paste proxy URL into Cline MCP settings)\n"
|
|
1122
|
-
);
|
|
1123
|
-
let choice = 0;
|
|
1124
|
-
while (choice === 0) {
|
|
1125
|
-
const input = await ask("Choose integration (1-2): ");
|
|
1126
|
-
const num = parseInt(input.trim(), 10);
|
|
1127
|
-
if (num === 1) choice = 1;
|
|
1128
|
-
if (num === 2) choice = 2;
|
|
1129
|
-
}
|
|
1130
|
-
return choice === 1 ? "native" : "hosted";
|
|
1131
|
-
}
|
|
1132
1147
|
function getGeminiCliHooksInstallDir() {
|
|
1133
1148
|
return join(homedir(), ".multicorn", "gemini-cli-hooks");
|
|
1134
1149
|
}
|
|
@@ -1454,40 +1469,6 @@ async function installGeminiCliNativeHooks(ask) {
|
|
|
1454
1469
|
await mkdir(dirname(settingsPath), { recursive: true });
|
|
1455
1470
|
await writeFile(settingsPath, JSON.stringify(existing, null, 2) + "\n", SECRET_JSON_FILE_OPTIONS);
|
|
1456
1471
|
}
|
|
1457
|
-
async function promptGeminiCliIntegrationMode(ask) {
|
|
1458
|
-
process.stderr.write("\n" + style.bold("Gemini CLI integration") + "\n");
|
|
1459
|
-
process.stderr.write(
|
|
1460
|
-
" " + style.violet("1") + ". Native plugin (recommended) - Gemini CLI Hooks see every file, terminal, web, and MCP action\n"
|
|
1461
|
-
);
|
|
1462
|
-
process.stderr.write(
|
|
1463
|
-
" " + style.violet("2") + ". Hosted proxy - govern MCP traffic only (paste proxy URL into Gemini CLI settings)\n"
|
|
1464
|
-
);
|
|
1465
|
-
let choice = 0;
|
|
1466
|
-
while (choice === 0) {
|
|
1467
|
-
const input = await ask("Choose integration (1-2): ");
|
|
1468
|
-
const num = parseInt(input.trim(), 10);
|
|
1469
|
-
if (num === 1) choice = 1;
|
|
1470
|
-
if (num === 2) choice = 2;
|
|
1471
|
-
}
|
|
1472
|
-
return choice === 1 ? "native" : "hosted";
|
|
1473
|
-
}
|
|
1474
|
-
async function promptOpencodeIntegrationMode(ask) {
|
|
1475
|
-
process.stderr.write("\n" + style.bold("OpenCode integration") + "\n");
|
|
1476
|
-
process.stderr.write(
|
|
1477
|
-
" " + style.violet("1") + ". Native plugin (recommended) - Shield checks primary-agent tool execution via OpenCode Hooks\n"
|
|
1478
|
-
);
|
|
1479
|
-
process.stderr.write(
|
|
1480
|
-
" " + style.violet("2") + ". Hosted proxy - govern MCP server traffic via opencode.json (full subagent coverage when tools use MCP through Shield)\n"
|
|
1481
|
-
);
|
|
1482
|
-
let choice = 0;
|
|
1483
|
-
while (choice === 0) {
|
|
1484
|
-
const input = await ask("Choose integration (1-2): ");
|
|
1485
|
-
const num = parseInt(input.trim(), 10);
|
|
1486
|
-
if (num === 1) choice = 1;
|
|
1487
|
-
if (num === 2) choice = 2;
|
|
1488
|
-
}
|
|
1489
|
-
return choice === 1 ? "native" : "hosted";
|
|
1490
|
-
}
|
|
1491
1472
|
function getClaudeDesktopConfigPath() {
|
|
1492
1473
|
switch (process.platform) {
|
|
1493
1474
|
case "win32":
|
|
@@ -1607,27 +1588,22 @@ var INIT_WIZARD_PLATFORM_REGISTRY = [
|
|
|
1607
1588
|
},
|
|
1608
1589
|
{ slug: "other-mcp", displayName: "Local MCP / Other", section: "hosted" }
|
|
1609
1590
|
];
|
|
1610
|
-
var
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
{ title: "Recommended (native plugin)", items: itemsFor("native") },
|
|
1617
|
-
{ title: "Hosted proxy (MCP only)", items: itemsFor("hosted") }
|
|
1618
|
-
];
|
|
1619
|
-
})();
|
|
1620
|
-
var INIT_WIZARD_SELECTION_MAX = INIT_WIZARD_PLATFORM_REGISTRY.length;
|
|
1591
|
+
var INIT_WIZARD_PICKER_NATIVE_SLUGS = INIT_WIZARD_PLATFORM_REGISTRY.filter((e) => e.section === "native").map((e) => e.slug);
|
|
1592
|
+
var INIT_WIZARD_DASHBOARD_AGENTS_URL = "https://app.multicorn.ai/agents/new";
|
|
1593
|
+
var INIT_WIZARD_PICKER_SELECTION_MAX = INIT_WIZARD_PICKER_NATIVE_SLUGS.length;
|
|
1594
|
+
var PICKER_SLUG_BY_SELECTION = Object.fromEntries(
|
|
1595
|
+
INIT_WIZARD_PICKER_NATIVE_SLUGS.map((slug, i) => [i + 1, slug])
|
|
1596
|
+
);
|
|
1621
1597
|
var INIT_EXISTING_AGENTS_PLATFORM_ACTIONS = [
|
|
1622
1598
|
"Add a new agent alongside these",
|
|
1623
1599
|
"Replace an existing agent",
|
|
1624
1600
|
"Skip - choose a different platform"
|
|
1625
1601
|
];
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
function
|
|
1630
|
-
const slug =
|
|
1602
|
+
function resolveInitHybridIntegrationMode(explicit) {
|
|
1603
|
+
return explicit ?? "native";
|
|
1604
|
+
}
|
|
1605
|
+
function platformMenuLabelForPickerSelection(sel) {
|
|
1606
|
+
const slug = PICKER_SLUG_BY_SELECTION[sel];
|
|
1631
1607
|
if (slug === void 0) return "Unknown";
|
|
1632
1608
|
const entry = INIT_WIZARD_PLATFORM_REGISTRY.find((e) => e.slug === slug);
|
|
1633
1609
|
return entry?.displayName ?? slug;
|
|
@@ -1645,48 +1621,31 @@ async function promptPlatformSelection(ask) {
|
|
|
1645
1621
|
process.stderr.write(
|
|
1646
1622
|
"\n" + style.bold(style.violet("Which platform are you connecting?")) + "\n\n"
|
|
1647
1623
|
);
|
|
1624
|
+
process.stderr.write(" " + style.dim("Native plugin (files, terminal, browser)") + "\n");
|
|
1648
1625
|
let optionNum = 1;
|
|
1649
|
-
for (const
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1626
|
+
for (const slug of INIT_WIZARD_PICKER_NATIVE_SLUGS) {
|
|
1627
|
+
const entry = INIT_WIZARD_PLATFORM_REGISTRY.find((e) => e.slug === slug);
|
|
1628
|
+
const label = entry?.displayName ?? slug;
|
|
1629
|
+
const indent = optionNum >= 10 ? " " : " ";
|
|
1630
|
+
process.stderr.write(`${indent}${style.violet(String(optionNum))}. ${label}
|
|
1654
1631
|
`);
|
|
1655
|
-
|
|
1656
|
-
}
|
|
1632
|
+
optionNum++;
|
|
1657
1633
|
}
|
|
1658
|
-
process.stderr.write(
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1634
|
+
process.stderr.write("\n");
|
|
1635
|
+
process.stderr.write(" MCP agents (Cursor, Copilot, others) are set up\n");
|
|
1636
|
+
process.stderr.write(` in the dashboard: ${INIT_WIZARD_DASHBOARD_AGENTS_URL}
|
|
1637
|
+
|
|
1638
|
+
`);
|
|
1663
1639
|
let selection = 0;
|
|
1664
1640
|
while (selection === 0) {
|
|
1665
|
-
const input = await ask(`Select (1-${String(
|
|
1641
|
+
const input = await ask(`Select (1-${String(INIT_WIZARD_PICKER_SELECTION_MAX)}): `);
|
|
1666
1642
|
const num = parseInt(input.trim(), 10);
|
|
1667
|
-
if (num >= 1 && num <=
|
|
1643
|
+
if (num >= 1 && num <= INIT_WIZARD_PICKER_SELECTION_MAX) {
|
|
1668
1644
|
selection = num;
|
|
1669
1645
|
}
|
|
1670
1646
|
}
|
|
1671
1647
|
return selection;
|
|
1672
1648
|
}
|
|
1673
|
-
async function promptWindsurfIntegrationMode(ask) {
|
|
1674
|
-
process.stderr.write("\n" + style.bold("Windsurf integration") + "\n");
|
|
1675
|
-
process.stderr.write(
|
|
1676
|
-
" " + style.violet("1") + ". Native plugin (recommended) - Cascade Hooks see every file, terminal, and MCP action\n"
|
|
1677
|
-
);
|
|
1678
|
-
process.stderr.write(
|
|
1679
|
-
" " + style.violet("2") + ". Hosted proxy - govern MCP traffic only (paste proxy URL into mcp_config)\n"
|
|
1680
|
-
);
|
|
1681
|
-
let choice = 0;
|
|
1682
|
-
while (choice === 0) {
|
|
1683
|
-
const input = await ask("Choose integration (1-2): ");
|
|
1684
|
-
const num = parseInt(input.trim(), 10);
|
|
1685
|
-
if (num === 1) choice = 1;
|
|
1686
|
-
if (num === 2) choice = 2;
|
|
1687
|
-
}
|
|
1688
|
-
return choice === 1 ? "native" : "hosted";
|
|
1689
|
-
}
|
|
1690
1649
|
async function arrowSelect(options, ask, fallbackLabel) {
|
|
1691
1650
|
if (options.length === 1) {
|
|
1692
1651
|
const only = options[0] ?? "";
|
|
@@ -1899,6 +1858,14 @@ var HOSTED_PROXY_PLATFORMS_WITH_URL_KEY = /* @__PURE__ */ new Set([
|
|
|
1899
1858
|
function shouldEmbedKeyInHostedProxyUrl(platform) {
|
|
1900
1859
|
return HOSTED_PROXY_PLATFORMS_WITH_URL_KEY.has(platform);
|
|
1901
1860
|
}
|
|
1861
|
+
function buildClineHostedMcpEntry(url) {
|
|
1862
|
+
return {
|
|
1863
|
+
type: "streamableHttp",
|
|
1864
|
+
url,
|
|
1865
|
+
timeout: 60,
|
|
1866
|
+
disabled: false
|
|
1867
|
+
};
|
|
1868
|
+
}
|
|
1902
1869
|
function hostedProxyUrlWithKeyParam(proxyUrl, apiKey) {
|
|
1903
1870
|
if (apiKey.length === 0) {
|
|
1904
1871
|
process.stderr.write(
|
|
@@ -2303,9 +2270,11 @@ async function applyHostedProxyMcpConfig(platform, proxyUrl, shortName, apiKey,
|
|
|
2303
2270
|
printHostedProxyJsonParseWarning(getWindsurfMcpConfigPath());
|
|
2304
2271
|
}
|
|
2305
2272
|
} else if (platform === "cline") {
|
|
2306
|
-
result = await mergeMcpServersObjectStyle(
|
|
2307
|
-
|
|
2308
|
-
|
|
2273
|
+
result = await mergeMcpServersObjectStyle(
|
|
2274
|
+
getClineMcpSettingsPath(),
|
|
2275
|
+
shortName,
|
|
2276
|
+
buildClineHostedMcpEntry(proxyUrlWithKeyWhenNeeded)
|
|
2277
|
+
);
|
|
2309
2278
|
if (result === "parse-error") {
|
|
2310
2279
|
printHostedProxyJsonParseWarning(getClineMcpSettingsPath());
|
|
2311
2280
|
}
|
|
@@ -2595,9 +2564,7 @@ Authorization = "Bearer ${bearerToken}"
|
|
|
2595
2564
|
snippetText = platform === "cline" ? JSON.stringify(
|
|
2596
2565
|
{
|
|
2597
2566
|
mcpServers: {
|
|
2598
|
-
[shortName]:
|
|
2599
|
-
[urlKey]: urlInSnippet
|
|
2600
|
-
}
|
|
2567
|
+
[shortName]: buildClineHostedMcpEntry(urlInSnippet)
|
|
2601
2568
|
}
|
|
2602
2569
|
},
|
|
2603
2570
|
null,
|
|
@@ -2761,6 +2728,7 @@ function mergeAgentsForPlatform(localAgents, remoteAgents, selectedPlatform) {
|
|
|
2761
2728
|
var DEFAULT_SHIELD_API_BASE_URL = "https://api.multicorn.ai";
|
|
2762
2729
|
async function runInit(explicitBaseUrl, options) {
|
|
2763
2730
|
const verbose = options?.verbose === true;
|
|
2731
|
+
const hybridIntegration = resolveInitHybridIntegrationMode(options?.integration);
|
|
2764
2732
|
if (!process.stdin.isTTY) {
|
|
2765
2733
|
process.stderr.write(
|
|
2766
2734
|
style.red("Error: interactive terminal required. Cannot run init with piped input.") + "\n"
|
|
@@ -2805,9 +2773,31 @@ async function runInit(explicitBaseUrl, options) {
|
|
|
2805
2773
|
if (existing !== null && existing.apiKey.startsWith("mcs_") && existing.apiKey.length >= 8) {
|
|
2806
2774
|
const masked = "mcs_..." + existing.apiKey.slice(-4);
|
|
2807
2775
|
process.stderr.write("Found existing API key: " + style.cyan(masked) + "\n");
|
|
2808
|
-
const answer = await ask("Use this key? (Y/n) ");
|
|
2809
|
-
|
|
2810
|
-
|
|
2776
|
+
const answer = await ask("Use this key? (Y/n, or paste a new mcs_ key) ");
|
|
2777
|
+
const trimmed = answer.trim();
|
|
2778
|
+
if (trimmed.toLowerCase() !== "n") {
|
|
2779
|
+
const candidate = isPastedShieldApiKeyInput(trimmed) ? trimmed : existing.apiKey;
|
|
2780
|
+
const spinner = withSpinner("Validating key...");
|
|
2781
|
+
let reuseResult;
|
|
2782
|
+
try {
|
|
2783
|
+
reuseResult = await validateApiKey(candidate, resolvedBaseUrl);
|
|
2784
|
+
} catch (error) {
|
|
2785
|
+
spinner.stop(false, "Validation failed");
|
|
2786
|
+
throw error;
|
|
2787
|
+
}
|
|
2788
|
+
if (reuseResult.valid) {
|
|
2789
|
+
spinner.stop(true, "Key validated");
|
|
2790
|
+
apiKey = candidate;
|
|
2791
|
+
} else {
|
|
2792
|
+
spinner.stop(false, reuseResult.error ?? "Validation failed.");
|
|
2793
|
+
if (isPastedShieldApiKeyInput(trimmed)) {
|
|
2794
|
+
process.stderr.write(style.red("That key was rejected. Enter a new key below.") + "\n");
|
|
2795
|
+
} else {
|
|
2796
|
+
process.stderr.write(
|
|
2797
|
+
style.yellow("Existing key is no longer valid. Enter a new key below.") + "\n"
|
|
2798
|
+
);
|
|
2799
|
+
}
|
|
2800
|
+
}
|
|
2811
2801
|
}
|
|
2812
2802
|
}
|
|
2813
2803
|
if (apiKey.length === 0) {
|
|
@@ -2859,8 +2849,8 @@ async function runInit(explicitBaseUrl, options) {
|
|
|
2859
2849
|
let removeAgentNameBeforeSave = void 0;
|
|
2860
2850
|
const initWorkspacePath = resolve(process.cwd());
|
|
2861
2851
|
const selection = await promptPlatformSelection(ask);
|
|
2862
|
-
const selectedPlatform =
|
|
2863
|
-
const selectedLabel =
|
|
2852
|
+
const selectedPlatform = PICKER_SLUG_BY_SELECTION[selection] ?? "openclaw";
|
|
2853
|
+
const selectedLabel = platformMenuLabelForPickerSelection(selection);
|
|
2864
2854
|
if (selectedPlatform === "other-mcp") {
|
|
2865
2855
|
const raw = existing !== null ? { ...existing } : {};
|
|
2866
2856
|
raw["apiKey"] = apiKey;
|
|
@@ -2994,7 +2984,7 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
2994
2984
|
}
|
|
2995
2985
|
if (detection.status === "parse-error") {
|
|
2996
2986
|
process.stderr.write(
|
|
2997
|
-
style.red("\u2717") + " Could not update OpenClaw config. Set
|
|
2987
|
+
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"
|
|
2998
2988
|
);
|
|
2999
2989
|
}
|
|
3000
2990
|
if (detection.status === "detected") {
|
|
@@ -3034,7 +3024,7 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
3034
3024
|
if (result === "parse-error") {
|
|
3035
3025
|
spinner.stop(
|
|
3036
3026
|
false,
|
|
3037
|
-
"Could not update OpenClaw config. Set
|
|
3027
|
+
"Could not update OpenClaw config. Set plugins.entries.multicorn-shield.config.apiKey in ~/.openclaw/openclaw.json, or run npx multicorn-shield init."
|
|
3038
3028
|
);
|
|
3039
3029
|
} else {
|
|
3040
3030
|
spinner.stop(
|
|
@@ -3081,7 +3071,7 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
3081
3071
|
});
|
|
3082
3072
|
setupSucceeded = true;
|
|
3083
3073
|
} else if (selectedPlatform === "windsurf") {
|
|
3084
|
-
const windsurfMode =
|
|
3074
|
+
const windsurfMode = hybridIntegration;
|
|
3085
3075
|
if (windsurfMode === "native") {
|
|
3086
3076
|
try {
|
|
3087
3077
|
await installWindsurfNativeHooks();
|
|
@@ -3180,7 +3170,7 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
3180
3170
|
}
|
|
3181
3171
|
}
|
|
3182
3172
|
} else if (selectedPlatform === "gemini-cli") {
|
|
3183
|
-
const geminiMode =
|
|
3173
|
+
const geminiMode = hybridIntegration;
|
|
3184
3174
|
if (geminiMode === "native") {
|
|
3185
3175
|
try {
|
|
3186
3176
|
await installGeminiCliNativeHooks(ask);
|
|
@@ -3274,7 +3264,7 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
3274
3264
|
}
|
|
3275
3265
|
}
|
|
3276
3266
|
} else if (selectedPlatform === "opencode") {
|
|
3277
|
-
const opencodeMode =
|
|
3267
|
+
const opencodeMode = hybridIntegration;
|
|
3278
3268
|
if (opencodeMode === "native") {
|
|
3279
3269
|
try {
|
|
3280
3270
|
await installOpenCodeNativePlugin();
|
|
@@ -3363,7 +3353,7 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
3363
3353
|
}
|
|
3364
3354
|
}
|
|
3365
3355
|
} else if (selectedPlatform === "codex-cli") {
|
|
3366
|
-
const codexMode =
|
|
3356
|
+
const codexMode = hybridIntegration;
|
|
3367
3357
|
if (codexMode === "native") {
|
|
3368
3358
|
try {
|
|
3369
3359
|
await installCodexCliNativeHooks();
|
|
@@ -3452,7 +3442,7 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
3452
3442
|
}
|
|
3453
3443
|
}
|
|
3454
3444
|
} else if (selectedPlatform === "cline") {
|
|
3455
|
-
const clineMode =
|
|
3445
|
+
const clineMode = hybridIntegration;
|
|
3456
3446
|
if (clineMode === "native") {
|
|
3457
3447
|
try {
|
|
3458
3448
|
await installClineNativeHooks();
|
|
@@ -3935,9 +3925,7 @@ async function writeLocalMcpEntry(client, agentName, localProxyUrl, apiKey, work
|
|
|
3935
3925
|
return await mergeMcpServersObjectStyle(
|
|
3936
3926
|
filePath,
|
|
3937
3927
|
entryKey,
|
|
3938
|
-
|
|
3939
|
-
url
|
|
3940
|
-
},
|
|
3928
|
+
buildClineHostedMcpEntry(url),
|
|
3941
3929
|
legacyKeys
|
|
3942
3930
|
) === "ok" ? filePath : null;
|
|
3943
3931
|
case "windsurf":
|
|
@@ -4985,10 +4973,95 @@ async function restoreClaudeDesktopMcpFromBackup() {
|
|
|
4985
4973
|
|
|
4986
4974
|
// package.json
|
|
4987
4975
|
var package_default = {
|
|
4988
|
-
version: "1.
|
|
4976
|
+
version: "1.12.0"};
|
|
4989
4977
|
|
|
4990
4978
|
// src/package-meta.ts
|
|
4991
4979
|
var PACKAGE_VERSION = package_default.version;
|
|
4980
|
+
var LOCAL_PROXY_READY_POLL_MS = 500;
|
|
4981
|
+
var LOCAL_PROXY_READY_MAX_POLLS = 30;
|
|
4982
|
+
var NOT_FOUND_MESSAGE = "Local proxy server entry (dist/server.js) not found. Reinstall multicorn-shield or run pnpm build.";
|
|
4983
|
+
function findMulticornShieldPackageRoot(moduleDir = dirname(fileURLToPath(import.meta.url))) {
|
|
4984
|
+
let current = moduleDir;
|
|
4985
|
+
for (; ; ) {
|
|
4986
|
+
const pkgPath = join(current, "package.json");
|
|
4987
|
+
if (existsSync(pkgPath)) {
|
|
4988
|
+
try {
|
|
4989
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
4990
|
+
if (pkg.name === "multicorn-shield") {
|
|
4991
|
+
return current;
|
|
4992
|
+
}
|
|
4993
|
+
} catch {
|
|
4994
|
+
}
|
|
4995
|
+
}
|
|
4996
|
+
const parent = dirname(current);
|
|
4997
|
+
if (parent === current) {
|
|
4998
|
+
break;
|
|
4999
|
+
}
|
|
5000
|
+
current = parent;
|
|
5001
|
+
}
|
|
5002
|
+
try {
|
|
5003
|
+
const req = createRequire(import.meta.url);
|
|
5004
|
+
return dirname(req.resolve("multicorn-shield/package.json"));
|
|
5005
|
+
} catch {
|
|
5006
|
+
throw new Error(NOT_FOUND_MESSAGE);
|
|
5007
|
+
}
|
|
5008
|
+
}
|
|
5009
|
+
function resolveLocalProxyServerEntry(options) {
|
|
5010
|
+
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
5011
|
+
const candidates = [
|
|
5012
|
+
join(findMulticornShieldPackageRoot(moduleDir), "dist", "server.js")
|
|
5013
|
+
];
|
|
5014
|
+
try {
|
|
5015
|
+
const req = createRequire(import.meta.url);
|
|
5016
|
+
const proxyRoot = dirname(req.resolve("multicorn-proxy/package.json"));
|
|
5017
|
+
candidates.unshift(join(proxyRoot, "dist", "server.js"));
|
|
5018
|
+
} catch {
|
|
5019
|
+
}
|
|
5020
|
+
for (const path of candidates) {
|
|
5021
|
+
if (existsSync(path)) return path;
|
|
5022
|
+
}
|
|
5023
|
+
throw new Error(NOT_FOUND_MESSAGE);
|
|
5024
|
+
}
|
|
5025
|
+
function buildLocalProxySpawnEnv(port, apiBaseUrl) {
|
|
5026
|
+
return {
|
|
5027
|
+
PORT: String(port),
|
|
5028
|
+
HOST: "127.0.0.1",
|
|
5029
|
+
SHIELD_API_BASE_URL: apiBaseUrl,
|
|
5030
|
+
ALLOW_PRIVATE_TARGETS: "true"
|
|
5031
|
+
};
|
|
5032
|
+
}
|
|
5033
|
+
function buildLocalProxySpawnCommand(port, apiBaseUrl, execPath = process.execPath, serverEntryPath = resolveLocalProxyServerEntry()) {
|
|
5034
|
+
const env = buildLocalProxySpawnEnv(port, apiBaseUrl);
|
|
5035
|
+
return {
|
|
5036
|
+
executable: execPath,
|
|
5037
|
+
args: [serverEntryPath],
|
|
5038
|
+
env,
|
|
5039
|
+
serverEntryPath
|
|
5040
|
+
};
|
|
5041
|
+
}
|
|
5042
|
+
function readProxyLogTail(logPath, maxBytes = 8e3) {
|
|
5043
|
+
try {
|
|
5044
|
+
const content = readFileSync(logPath, "utf8");
|
|
5045
|
+
if (content.length <= maxBytes) return content;
|
|
5046
|
+
return content.slice(-maxBytes);
|
|
5047
|
+
} catch {
|
|
5048
|
+
return "";
|
|
5049
|
+
}
|
|
5050
|
+
}
|
|
5051
|
+
function formatLocalProxyStartError(port, logPath, childExited, exitCode) {
|
|
5052
|
+
const logTail = readProxyLogTail(logPath);
|
|
5053
|
+
const reason = childExited ? `Proxy process exited early${exitCode === null ? "" : ` (code ${String(exitCode)})`}.` : `Proxy did not become ready on port ${String(port)} within the timeout.`;
|
|
5054
|
+
let message = `Could not start the local proxy on port ${String(port)}. ${reason}`;
|
|
5055
|
+
message += logTail.length > 0 ? `
|
|
5056
|
+
|
|
5057
|
+
Output from ${logPath}:
|
|
5058
|
+
${logTail.trimEnd()}` : `
|
|
5059
|
+
|
|
5060
|
+
See ${logPath} for details.`;
|
|
5061
|
+
return message;
|
|
5062
|
+
}
|
|
5063
|
+
|
|
5064
|
+
// src/commands/files.ts
|
|
4992
5065
|
var DEFAULT_FS_PORT = 3005;
|
|
4993
5066
|
var DEFAULT_PROXY_PORT = 3001;
|
|
4994
5067
|
var PIDFILE_DIR = process.env["MULTICORN_HOME"] ?? join(homedir(), ".multicorn");
|
|
@@ -5023,6 +5096,16 @@ function readPidfile(agent) {
|
|
|
5023
5096
|
return null;
|
|
5024
5097
|
}
|
|
5025
5098
|
}
|
|
5099
|
+
function supervisorPidFromSession(data) {
|
|
5100
|
+
if (typeof data.supervisorPid === "number") return data.supervisorPid;
|
|
5101
|
+
if (typeof data.pid === "number") return data.pid;
|
|
5102
|
+
return void 0;
|
|
5103
|
+
}
|
|
5104
|
+
function isAgentSessionRunning(data) {
|
|
5105
|
+
if (data === null) return false;
|
|
5106
|
+
const pid = supervisorPidFromSession(data);
|
|
5107
|
+
return typeof pid === "number" && isProcessAlive(pid);
|
|
5108
|
+
}
|
|
5026
5109
|
function removePidfile(agent) {
|
|
5027
5110
|
const p = pidfilePath(agent);
|
|
5028
5111
|
try {
|
|
@@ -5063,7 +5146,7 @@ function runStatus() {
|
|
|
5063
5146
|
const fsReg = readFsRegistry();
|
|
5064
5147
|
process.stderr.write("Active sessions:\n\n");
|
|
5065
5148
|
for (const s of sessions) {
|
|
5066
|
-
const supervisorAlive =
|
|
5149
|
+
const supervisorAlive = isAgentSessionRunning(s);
|
|
5067
5150
|
const fsEntry = fsReg[s.dir];
|
|
5068
5151
|
const fsAlive = fsEntry !== void 0 && isProcessAlive(fsEntry.pid);
|
|
5069
5152
|
const proxyAlive = proxyReg !== null && proxyReg.port === s.proxyPort && isProcessAlive(proxyReg.pid);
|
|
@@ -5210,9 +5293,7 @@ async function withResourceLock(fn) {
|
|
|
5210
5293
|
}
|
|
5211
5294
|
}
|
|
5212
5295
|
function liveAgents() {
|
|
5213
|
-
return listAllPidfiles().filter(
|
|
5214
|
-
(p) => typeof p.supervisorPid === "number" && isProcessAlive(p.supervisorPid)
|
|
5215
|
-
);
|
|
5296
|
+
return listAllPidfiles().filter((p) => isAgentSessionRunning(p));
|
|
5216
5297
|
}
|
|
5217
5298
|
function agentsReferencingProxy(proxyPort, agents, excludeAgent) {
|
|
5218
5299
|
return agents.filter((a) => a.agent !== excludeAgent && a.proxyPort === proxyPort);
|
|
@@ -5230,27 +5311,37 @@ async function nextFreePort(start, claimed, isBusy, range = FS_PORT_SCAN_RANGE)
|
|
|
5230
5311
|
`No free port for the filesystem server in range ${String(start)}-${String(start + range)}.`
|
|
5231
5312
|
);
|
|
5232
5313
|
}
|
|
5314
|
+
async function resolveBaseUrl(explicitBaseUrl) {
|
|
5315
|
+
if (explicitBaseUrl !== void 0 && explicitBaseUrl.trim().length > 0) {
|
|
5316
|
+
return explicitBaseUrl.trim();
|
|
5317
|
+
}
|
|
5318
|
+
const envBaseUrl = process.env["MULTICORN_BASE_URL"];
|
|
5319
|
+
if (typeof envBaseUrl === "string" && envBaseUrl.trim().length > 0) {
|
|
5320
|
+
return envBaseUrl.trim();
|
|
5321
|
+
}
|
|
5322
|
+
const config = await loadConfig();
|
|
5323
|
+
if (config !== null && config.baseUrl.length > 0) {
|
|
5324
|
+
return config.baseUrl;
|
|
5325
|
+
}
|
|
5326
|
+
const fromFile = await readBaseUrlFromConfig();
|
|
5327
|
+
if (fromFile !== void 0 && fromFile.length > 0) {
|
|
5328
|
+
return fromFile;
|
|
5329
|
+
}
|
|
5330
|
+
return DEFAULT_SHIELD_API_BASE_URL;
|
|
5331
|
+
}
|
|
5233
5332
|
async function resolveConfig(opts) {
|
|
5333
|
+
const baseUrl = await resolveBaseUrl(opts.baseUrl);
|
|
5234
5334
|
const fromFlag = opts.apiKey;
|
|
5235
5335
|
if (fromFlag && fromFlag.length > 0) {
|
|
5236
|
-
return {
|
|
5237
|
-
apiKey: fromFlag,
|
|
5238
|
-
baseUrl: opts.baseUrl ?? DEFAULT_SHIELD_API_BASE_URL
|
|
5239
|
-
};
|
|
5336
|
+
return { apiKey: fromFlag, baseUrl };
|
|
5240
5337
|
}
|
|
5241
5338
|
const envKey = process.env["MULTICORN_API_KEY"];
|
|
5242
5339
|
if (typeof envKey === "string" && envKey.length > 0) {
|
|
5243
|
-
return {
|
|
5244
|
-
apiKey: envKey,
|
|
5245
|
-
baseUrl: opts.baseUrl ?? DEFAULT_SHIELD_API_BASE_URL
|
|
5246
|
-
};
|
|
5340
|
+
return { apiKey: envKey, baseUrl };
|
|
5247
5341
|
}
|
|
5248
5342
|
const config = await loadConfig();
|
|
5249
5343
|
if (config !== null) {
|
|
5250
|
-
return {
|
|
5251
|
-
apiKey: config.apiKey,
|
|
5252
|
-
baseUrl: opts.baseUrl ?? config.baseUrl
|
|
5253
|
-
};
|
|
5344
|
+
return { apiKey: config.apiKey, baseUrl };
|
|
5254
5345
|
}
|
|
5255
5346
|
process.stderr.write(
|
|
5256
5347
|
"No API key found. Pass --api-key <key> or add it to ~/.multicorn/config.json.\n"
|
|
@@ -5290,26 +5381,40 @@ function startLocalProxyDetached(port, apiBaseUrl) {
|
|
|
5290
5381
|
const logFile = join(PIDFILE_DIR, "proxy.log");
|
|
5291
5382
|
const out = openSync(logFile, "a");
|
|
5292
5383
|
const err = openSync(logFile, "a");
|
|
5293
|
-
const
|
|
5384
|
+
const { executable, args, env: proxyEnv } = buildLocalProxySpawnCommand(port, apiBaseUrl);
|
|
5385
|
+
const child = spawn(executable, [...args], {
|
|
5294
5386
|
stdio: ["ignore", out, err],
|
|
5295
5387
|
detached: true,
|
|
5296
5388
|
env: {
|
|
5297
5389
|
...process.env,
|
|
5298
|
-
|
|
5299
|
-
HOST: "127.0.0.1",
|
|
5300
|
-
SHIELD_API_BASE_URL: apiBaseUrl,
|
|
5390
|
+
...proxyEnv
|
|
5301
5391
|
// SAFETY: blanket-allow for private targets is acceptable ONLY because
|
|
5302
5392
|
// this is a local single-user proxy. The only registered target is the
|
|
5303
5393
|
// filesystem server on the same machine. Do NOT copy this pattern to a
|
|
5304
5394
|
// multi-tenant or hosted proxy deployment.
|
|
5305
|
-
ALLOW_PRIVATE_TARGETS: "true"
|
|
5306
5395
|
}
|
|
5307
5396
|
});
|
|
5308
5397
|
child.unref();
|
|
5309
5398
|
return child;
|
|
5310
5399
|
}
|
|
5311
|
-
async function ensureProxy(proxyPort, apiBaseUrl) {
|
|
5312
|
-
|
|
5400
|
+
async function ensureProxy(proxyPort, apiBaseUrl, options) {
|
|
5401
|
+
const forceRespawn = options?.forceRespawn === true;
|
|
5402
|
+
if (forceRespawn) {
|
|
5403
|
+
const reg2 = readProxyRegistry();
|
|
5404
|
+
if (reg2 !== null && reg2.port === proxyPort && isProcessAlive(reg2.pid)) {
|
|
5405
|
+
killWithEscalation(reg2.pid, true);
|
|
5406
|
+
}
|
|
5407
|
+
if (reg2 !== null && reg2.port === proxyPort) {
|
|
5408
|
+
try {
|
|
5409
|
+
unlinkSync(PROXY_REGISTRY);
|
|
5410
|
+
} catch {
|
|
5411
|
+
}
|
|
5412
|
+
}
|
|
5413
|
+
for (let i = 0; i < 20; i++) {
|
|
5414
|
+
if (!await isPortListening(proxyPort)) break;
|
|
5415
|
+
await sleep3(100);
|
|
5416
|
+
}
|
|
5417
|
+
} else if (await probeProxyHealth(proxyPort)) {
|
|
5313
5418
|
const reg2 = readProxyRegistry();
|
|
5314
5419
|
const managed = reg2 !== null && reg2.port === proxyPort && isProcessAlive(reg2.pid);
|
|
5315
5420
|
return { reused: true, managed };
|
|
@@ -5326,9 +5431,11 @@ async function ensureProxy(proxyPort, apiBaseUrl) {
|
|
|
5326
5431
|
`A server is already running on port ${String(proxyPort)} but it's not a healthy Shield proxy. Stop it or re-run with --proxy-port <n>.`
|
|
5327
5432
|
);
|
|
5328
5433
|
}
|
|
5434
|
+
const logFile = join(PIDFILE_DIR, "proxy.log");
|
|
5329
5435
|
const child = startLocalProxyDetached(proxyPort, apiBaseUrl);
|
|
5330
|
-
for (let i = 0; i <
|
|
5331
|
-
await sleep3(
|
|
5436
|
+
for (let i = 0; i < LOCAL_PROXY_READY_MAX_POLLS; i++) {
|
|
5437
|
+
await sleep3(LOCAL_PROXY_READY_POLL_MS);
|
|
5438
|
+
if (child.exitCode !== null || child.signalCode !== null) break;
|
|
5332
5439
|
if (await probeProxyHealth(proxyPort)) {
|
|
5333
5440
|
if (child.pid !== void 0) {
|
|
5334
5441
|
writeJsonFile(PROXY_REGISTRY, { pid: child.pid, port: proxyPort });
|
|
@@ -5340,9 +5447,8 @@ async function ensureProxy(proxyPort, apiBaseUrl) {
|
|
|
5340
5447
|
if (child.pid !== void 0) killWithEscalation(child.pid, true);
|
|
5341
5448
|
} catch {
|
|
5342
5449
|
}
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
);
|
|
5450
|
+
const childExited = child.exitCode !== null || child.signalCode !== null;
|
|
5451
|
+
throw new Error(formatLocalProxyStartError(proxyPort, logFile, childExited, child.exitCode));
|
|
5346
5452
|
}
|
|
5347
5453
|
async function ensureFsServer(realDir, requestedPort) {
|
|
5348
5454
|
const reg = readFsRegistry();
|
|
@@ -5545,8 +5651,9 @@ async function runStop(agent) {
|
|
|
5545
5651
|
process.exit(1);
|
|
5546
5652
|
}
|
|
5547
5653
|
await withResourceLock(() => {
|
|
5548
|
-
|
|
5549
|
-
|
|
5654
|
+
const supervisorPid = supervisorPidFromSession(data);
|
|
5655
|
+
if (typeof supervisorPid === "number" && isProcessAlive(supervisorPid)) {
|
|
5656
|
+
killWithEscalation(supervisorPid);
|
|
5550
5657
|
}
|
|
5551
5658
|
removePidfile(agent);
|
|
5552
5659
|
releaseFsServerIfUnused(data.dir, agent);
|
|
@@ -5576,7 +5683,8 @@ Run it once with the folder: npx multicorn-shield files restart <dir> --agent ${
|
|
|
5576
5683
|
await runDetached({
|
|
5577
5684
|
...opts,
|
|
5578
5685
|
dir,
|
|
5579
|
-
proxyPort: opts.proxyPort ?? existing?.proxyPort
|
|
5686
|
+
proxyPort: opts.proxyPort ?? existing?.proxyPort,
|
|
5687
|
+
respawnProxy: true});
|
|
5580
5688
|
}
|
|
5581
5689
|
async function runDetached(opts) {
|
|
5582
5690
|
const absDir = resolve(opts.dir);
|
|
@@ -5587,8 +5695,7 @@ async function runDetached(opts) {
|
|
|
5587
5695
|
}
|
|
5588
5696
|
const existing = readPidfile(opts.agent);
|
|
5589
5697
|
if (existing !== null) {
|
|
5590
|
-
|
|
5591
|
-
if (alive) {
|
|
5698
|
+
if (isAgentSessionRunning(existing)) {
|
|
5592
5699
|
process.stderr.write(
|
|
5593
5700
|
`Already running for agent "${opts.agent}" (fs :${String(existing.fsPort)}, proxy :${String(existing.proxyPort)}).
|
|
5594
5701
|
Stop with: npx multicorn-shield files stop --agent ${opts.agent}
|
|
@@ -5603,6 +5710,7 @@ Stop with: npx multicorn-shield files stop --agent ${opts.agent}
|
|
|
5603
5710
|
if (opts.proxyPort !== void 0) args.push("--proxy-port", String(opts.proxyPort));
|
|
5604
5711
|
if (opts.apiKey !== void 0) args.push("--api-key", opts.apiKey);
|
|
5605
5712
|
if (opts.baseUrl !== void 0) args.push("--base-url", opts.baseUrl);
|
|
5713
|
+
if (opts.respawnProxy) args.push("--respawn-proxy");
|
|
5606
5714
|
if (opts.client !== void 0) args.push("--client", opts.client);
|
|
5607
5715
|
const scriptPath = process.argv[1] ?? resolve("dist/multicorn-shield.js");
|
|
5608
5716
|
const logFile = join(PIDFILE_DIR, `files-${opts.agent}.log`);
|
|
@@ -5686,8 +5794,7 @@ async function runFilesCommand(opts) {
|
|
|
5686
5794
|
const proxyPort = opts.proxyPort ?? DEFAULT_PROXY_PORT;
|
|
5687
5795
|
const existingPidfile = readPidfile(opts.agent);
|
|
5688
5796
|
if (existingPidfile !== null) {
|
|
5689
|
-
|
|
5690
|
-
if (supervisorAlive) {
|
|
5797
|
+
if (isAgentSessionRunning(existingPidfile)) {
|
|
5691
5798
|
process.stderr.write(
|
|
5692
5799
|
`A session for agent "${opts.agent}" is already running. Run 'files stop --agent ${opts.agent}' first, or use a different --agent name.
|
|
5693
5800
|
`
|
|
@@ -5701,7 +5808,9 @@ async function runFilesCommand(opts) {
|
|
|
5701
5808
|
let fsReused;
|
|
5702
5809
|
try {
|
|
5703
5810
|
const ensured = await withResourceLock(async () => {
|
|
5704
|
-
const proxyRes = await ensureProxy(proxyPort, config.baseUrl
|
|
5811
|
+
const proxyRes = await ensureProxy(proxyPort, config.baseUrl, {
|
|
5812
|
+
forceRespawn: opts.respawnProxy
|
|
5813
|
+
});
|
|
5705
5814
|
const fsRes = await ensureFsServer(realDir, opts.port);
|
|
5706
5815
|
writePidfile({
|
|
5707
5816
|
agent: opts.agent,
|
|
@@ -5968,6 +6077,8 @@ function parseArgs(argv) {
|
|
|
5968
6077
|
let filesForeground = false;
|
|
5969
6078
|
let filesStatus = false;
|
|
5970
6079
|
let filesRestart = false;
|
|
6080
|
+
let filesRespawnProxy = false;
|
|
6081
|
+
let initIntegration = void 0;
|
|
5971
6082
|
for (let i = 0; i < args.length; i++) {
|
|
5972
6083
|
const arg = args[i];
|
|
5973
6084
|
if (arg === "init") {
|
|
@@ -6018,6 +6129,8 @@ function parseArgs(argv) {
|
|
|
6018
6129
|
filesStop = true;
|
|
6019
6130
|
} else if (token === "--foreground") {
|
|
6020
6131
|
filesForeground = true;
|
|
6132
|
+
} else if (token === "--respawn-proxy") {
|
|
6133
|
+
filesRespawnProxy = true;
|
|
6021
6134
|
} else if (token === "--detach") ; else if (token === "stop") {
|
|
6022
6135
|
filesStop = true;
|
|
6023
6136
|
} else if (token === "status") {
|
|
@@ -6126,6 +6239,12 @@ function parseArgs(argv) {
|
|
|
6126
6239
|
}
|
|
6127
6240
|
} else if (arg === "--verbose" || arg === "--debug") {
|
|
6128
6241
|
verbose = true;
|
|
6242
|
+
} else if (arg === "--integration") {
|
|
6243
|
+
const next = args[i + 1];
|
|
6244
|
+
if (next === "native" || next === "hosted") {
|
|
6245
|
+
initIntegration = next;
|
|
6246
|
+
i++;
|
|
6247
|
+
}
|
|
6129
6248
|
}
|
|
6130
6249
|
}
|
|
6131
6250
|
return {
|
|
@@ -6139,6 +6258,7 @@ function parseArgs(argv) {
|
|
|
6139
6258
|
deleteAgentName,
|
|
6140
6259
|
apiKey,
|
|
6141
6260
|
verbose,
|
|
6261
|
+
initIntegration,
|
|
6142
6262
|
filesDir,
|
|
6143
6263
|
filesPort,
|
|
6144
6264
|
filesProxyPort,
|
|
@@ -6146,7 +6266,8 @@ function parseArgs(argv) {
|
|
|
6146
6266
|
filesClient,
|
|
6147
6267
|
filesForeground,
|
|
6148
6268
|
filesStatus,
|
|
6149
|
-
filesRestart
|
|
6269
|
+
filesRestart,
|
|
6270
|
+
filesRespawnProxy
|
|
6150
6271
|
};
|
|
6151
6272
|
}
|
|
6152
6273
|
function printHelp() {
|
|
@@ -6156,7 +6277,12 @@ function printHelp() {
|
|
|
6156
6277
|
"",
|
|
6157
6278
|
"Usage:",
|
|
6158
6279
|
" npx multicorn-shield init",
|
|
6159
|
-
" Interactive setup
|
|
6280
|
+
" Interactive setup for native plugins (files, terminal, browser).",
|
|
6281
|
+
" Native: OpenClaw, Claude Code, Windsurf, Cline, Gemini CLI, OpenCode, Codex CLI.",
|
|
6282
|
+
" MCP-only clients (Cursor, Copilot, etc.): https://app.multicorn.ai/agents/new",
|
|
6283
|
+
" Saves API key to ~/.multicorn/config.json.",
|
|
6284
|
+
" --integration hosted Use hosted MCP proxy for Windsurf, Cline, Gemini CLI,",
|
|
6285
|
+
" OpenCode, or Codex CLI (default: native).",
|
|
6160
6286
|
"",
|
|
6161
6287
|
" npx multicorn-shield files <dir> --agent <name> [--client <client>]",
|
|
6162
6288
|
" Share a local folder with a coding agent. Starts a filesystem MCP server",
|
|
@@ -6237,7 +6363,10 @@ async function runCli() {
|
|
|
6237
6363
|
process.exit(0);
|
|
6238
6364
|
}
|
|
6239
6365
|
if (cli.subcommand === "init") {
|
|
6240
|
-
await runInit(cli.baseUrl, {
|
|
6366
|
+
await runInit(cli.baseUrl, {
|
|
6367
|
+
verbose: cli.verbose,
|
|
6368
|
+
...cli.initIntegration !== void 0 ? { integration: cli.initIntegration } : {}
|
|
6369
|
+
});
|
|
6241
6370
|
return;
|
|
6242
6371
|
}
|
|
6243
6372
|
if (cli.subcommand === "files") {
|
|
@@ -6253,7 +6382,8 @@ async function runCli() {
|
|
|
6253
6382
|
client: void 0,
|
|
6254
6383
|
foreground: true,
|
|
6255
6384
|
status: true,
|
|
6256
|
-
restart: false
|
|
6385
|
+
restart: false,
|
|
6386
|
+
respawnProxy: false
|
|
6257
6387
|
});
|
|
6258
6388
|
return;
|
|
6259
6389
|
}
|
|
@@ -6278,7 +6408,8 @@ async function runCli() {
|
|
|
6278
6408
|
client: cli.filesClient,
|
|
6279
6409
|
foreground: cli.filesForeground,
|
|
6280
6410
|
status: false,
|
|
6281
|
-
restart: cli.filesRestart
|
|
6411
|
+
restart: cli.filesRestart,
|
|
6412
|
+
respawnProxy: cli.filesRespawnProxy
|
|
6282
6413
|
});
|
|
6283
6414
|
return;
|
|
6284
6415
|
}
|