multicorn-shield 0.13.0 → 1.1.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 +53 -13
- package/README.md +5 -5
- package/dist/index.cjs +4 -0
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +4 -0
- package/dist/multicorn-proxy.js +550 -175
- package/dist/multicorn-shield.js +3297 -31
- package/dist/openclaw-plugin/multicorn-shield.js +2 -2
- package/dist/proxy.cjs +1 -1
- package/dist/proxy.js +1 -1
- package/dist/shield-extension.js +64 -1
- package/package.json +3 -3
- package/plugins/windsurf/README.md +2 -2
package/dist/multicorn-proxy.js
CHANGED
|
@@ -1,31 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { existsSync, statSync } from 'fs';
|
|
3
|
-
import { mkdir, writeFile,
|
|
4
|
-
import {
|
|
3
|
+
import { readFile, mkdir, writeFile, copyFile, chmod, unlink } from 'fs/promises';
|
|
4
|
+
import { dirname, join } from 'path';
|
|
5
5
|
import { homedir } from 'os';
|
|
6
6
|
import { fileURLToPath } from 'url';
|
|
7
7
|
import { createInterface } from 'readline';
|
|
8
|
-
import { spawn } from 'child_process';
|
|
9
8
|
import { createHash } from 'crypto';
|
|
9
|
+
import { spawn } from 'child_process';
|
|
10
10
|
import 'stream';
|
|
11
11
|
|
|
12
|
-
var
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
dim: (s) => `\x1B[2m${s}\x1B[0m`
|
|
12
|
+
var __defProp = Object.defineProperty;
|
|
13
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
14
|
+
var __esm = (fn, res) => function __init() {
|
|
15
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
16
|
+
};
|
|
17
|
+
var __export = (target, all) => {
|
|
18
|
+
for (var name in all)
|
|
19
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
21
20
|
};
|
|
22
|
-
var BANNER = [
|
|
23
|
-
" \u2588\u2588\u2588 \u2588 \u2588 \u2588 \u2588\u2588\u2588 \u2588 \u2588\u2588\u2584 ",
|
|
24
|
-
" \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588",
|
|
25
|
-
" \u2588\u2588\u2588 \u2588\u2588\u2588\u2588 \u2588 \u2588\u2588 \u2588 \u2588 \u2588",
|
|
26
|
-
" \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588",
|
|
27
|
-
" \u2588\u2588\u2588 \u2588 \u2588 \u2588 \u2588\u2588\u2588 \u2588\u2588\u2588 \u2588\u2588\u2580 "
|
|
28
|
-
].map((line) => style.violet(line)).join("\n");
|
|
29
21
|
function withSpinner(message) {
|
|
30
22
|
const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
31
23
|
let i = 0;
|
|
@@ -43,12 +35,6 @@ function withSpinner(message) {
|
|
|
43
35
|
}
|
|
44
36
|
};
|
|
45
37
|
}
|
|
46
|
-
var NativePluginPrerequisiteMissingError = class extends Error {
|
|
47
|
-
constructor() {
|
|
48
|
-
super("Native plugin prerequisites not met");
|
|
49
|
-
this.name = "NativePluginPrerequisiteMissingError";
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
38
|
function isExistingDirectory(path) {
|
|
53
39
|
try {
|
|
54
40
|
if (!existsSync(path)) return false;
|
|
@@ -60,10 +46,6 @@ function isExistingDirectory(path) {
|
|
|
60
46
|
function nativePluginSkippedSaveNote(wizardCommand, productName) {
|
|
61
47
|
return "\n" + style.dim("Your agent config has been saved. Run ") + style.cyan(wizardCommand) + style.dim(` again after installing ${productName} to complete hook setup.`) + "\n";
|
|
62
48
|
}
|
|
63
|
-
var CONFIG_DIR = join(homedir(), ".multicorn");
|
|
64
|
-
var CONFIG_PATH = join(CONFIG_DIR, "config.json");
|
|
65
|
-
var OPENCLAW_CONFIG_PATH = join(homedir(), ".openclaw", "openclaw.json");
|
|
66
|
-
var ANSI_PATTERN = new RegExp(String.fromCharCode(27) + "\\[[0-9;]*[a-zA-Z]", "g");
|
|
67
49
|
function stripAnsi(str) {
|
|
68
50
|
return str.replace(ANSI_PATTERN, "");
|
|
69
51
|
}
|
|
@@ -207,7 +189,6 @@ async function saveConfig(config) {
|
|
|
207
189
|
mode: 384
|
|
208
190
|
});
|
|
209
191
|
}
|
|
210
|
-
var OPENCLAW_MIN_VERSION = "2026.2.26";
|
|
211
192
|
async function detectOpenClaw() {
|
|
212
193
|
let raw;
|
|
213
194
|
try {
|
|
@@ -373,7 +354,8 @@ async function isCursorConnected() {
|
|
|
373
354
|
const url = rec["url"];
|
|
374
355
|
if (typeof url === "string" && url.includes("multicorn")) return true;
|
|
375
356
|
const args = rec["args"];
|
|
376
|
-
if (Array.isArray(args) && args.includes("multicorn-proxy"))
|
|
357
|
+
if (Array.isArray(args) && (args.includes("multicorn-shield") || args.includes("multicorn-proxy")))
|
|
358
|
+
return true;
|
|
377
359
|
}
|
|
378
360
|
return false;
|
|
379
361
|
} catch (err) {
|
|
@@ -452,7 +434,7 @@ async function installWindsurfNativeHooks() {
|
|
|
452
434
|
"Open Windsurf at least once so this folder exists, or install from:\n " + style.cyan("https://windsurf.com/download") + "\n\n"
|
|
453
435
|
);
|
|
454
436
|
process.stderr.write("Then run this wizard again:\n");
|
|
455
|
-
process.stderr.write(" " + style.cyan("npx multicorn-
|
|
437
|
+
process.stderr.write(" " + style.cyan("npx multicorn-shield init") + "\n");
|
|
456
438
|
throw new NativePluginPrerequisiteMissingError();
|
|
457
439
|
}
|
|
458
440
|
const installDir = getWindsurfHooksInstallDir();
|
|
@@ -770,60 +752,73 @@ function getClaudeDesktopConfigPath() {
|
|
|
770
752
|
);
|
|
771
753
|
}
|
|
772
754
|
}
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
"
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
}
|
|
755
|
+
function platformMenuLabelForSelection(sel) {
|
|
756
|
+
const slug = PLATFORM_BY_SELECTION[sel];
|
|
757
|
+
if (slug === void 0) return "Unknown";
|
|
758
|
+
const entry = INIT_WIZARD_PLATFORM_REGISTRY.find((e) => e.slug === slug);
|
|
759
|
+
return entry?.displayName ?? slug;
|
|
760
|
+
}
|
|
761
|
+
async function promptHostedProxyInstallPrereq(ask, platformLabel, prereqUrl) {
|
|
762
|
+
process.stderr.write("\n");
|
|
763
|
+
process.stderr.write(
|
|
764
|
+
style.bold("Before continuing, make sure you have ") + platformLabel + style.bold(" installed.") + "\n"
|
|
765
|
+
);
|
|
766
|
+
process.stderr.write(" \u2192 " + style.cyan(prereqUrl) + "\n\n");
|
|
767
|
+
const answer = await ask("Ready to continue? (Y/n) ");
|
|
768
|
+
return answer.trim().toLowerCase() !== "n";
|
|
769
|
+
}
|
|
770
|
+
function isPlatformDetectedForMenu(slug) {
|
|
771
|
+
switch (slug) {
|
|
772
|
+
case "openclaw":
|
|
773
|
+
return isOpenClawConnected();
|
|
774
|
+
case "claude-code":
|
|
775
|
+
return Promise.resolve(isClaudeCodeConnected());
|
|
776
|
+
case "cursor":
|
|
777
|
+
return isCursorConnected();
|
|
778
|
+
case "windsurf":
|
|
779
|
+
return isWindsurfConnected();
|
|
780
|
+
default:
|
|
781
|
+
return Promise.resolve(false);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
802
784
|
async function promptPlatformSelection(ask) {
|
|
803
785
|
process.stderr.write(
|
|
804
|
-
"\n" + style.bold(style.violet("Which platform are you connecting?")) + "\n"
|
|
786
|
+
"\n" + style.bold(style.violet("Which platform are you connecting?")) + "\n\n"
|
|
805
787
|
);
|
|
806
|
-
const
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
const
|
|
814
|
-
|
|
815
|
-
|
|
788
|
+
const detectionSlugs = INIT_WIZARD_PLATFORM_REGISTRY.filter((e) => e.detectable).map(
|
|
789
|
+
(e) => e.slug
|
|
790
|
+
);
|
|
791
|
+
const connectedFlags = await Promise.all(
|
|
792
|
+
detectionSlugs.map((slug) => isPlatformDetectedForMenu(slug))
|
|
793
|
+
);
|
|
794
|
+
function markerFor(platform) {
|
|
795
|
+
const idx = detectionSlugs.indexOf(platform);
|
|
796
|
+
if (idx === -1) return "";
|
|
797
|
+
if (!connectedFlags[idx]) return "";
|
|
798
|
+
return " " + style.dim("\u25CF detected locally");
|
|
799
|
+
}
|
|
800
|
+
let optionNum = 1;
|
|
801
|
+
for (const section of INIT_WIZARD_MENU_SECTIONS) {
|
|
802
|
+
process.stderr.write(" " + style.dim(section.title) + "\n");
|
|
803
|
+
for (const item of section.items) {
|
|
804
|
+
const indent = optionNum >= 10 ? " " : " ";
|
|
805
|
+
process.stderr.write(
|
|
806
|
+
`${indent}${style.violet(String(optionNum))}. ${item.label}${markerFor(item.platform)}
|
|
816
807
|
`
|
|
817
|
-
|
|
808
|
+
);
|
|
809
|
+
optionNum++;
|
|
810
|
+
}
|
|
818
811
|
}
|
|
819
812
|
process.stderr.write(
|
|
820
|
-
|
|
813
|
+
"\n" + style.dim(
|
|
814
|
+
` Pick ${String(INIT_WIZARD_SELECTION_MAX)} to wrap a local MCP server with multicorn-shield --wrap.`
|
|
815
|
+
) + "\n"
|
|
821
816
|
);
|
|
822
817
|
let selection = 0;
|
|
823
818
|
while (selection === 0) {
|
|
824
|
-
const input = await ask(
|
|
819
|
+
const input = await ask(`Select (1-${String(INIT_WIZARD_SELECTION_MAX)}): `);
|
|
825
820
|
const num = parseInt(input.trim(), 10);
|
|
826
|
-
if (num >= 1 && num <=
|
|
821
|
+
if (num >= 1 && num <= INIT_WIZARD_SELECTION_MAX) {
|
|
827
822
|
selection = num;
|
|
828
823
|
}
|
|
829
824
|
}
|
|
@@ -894,12 +889,7 @@ async function promptProxyConfig(ask, agentName) {
|
|
|
894
889
|
}
|
|
895
890
|
targetUrl = input.trim();
|
|
896
891
|
}
|
|
897
|
-
const
|
|
898
|
-
const shortNameInput = await ask(
|
|
899
|
-
`
|
|
900
|
-
Short name (a nickname for this connection, used in your proxy URL): ${style.dim(`(${defaultShortName})`)} `
|
|
901
|
-
);
|
|
902
|
-
const shortName = shortNameInput.trim().length > 0 ? normalizeAgentName(shortNameInput.trim()) || defaultShortName : defaultShortName;
|
|
892
|
+
const shortName = normalizeAgentName(agentName) || "shield-mcp";
|
|
903
893
|
return { targetUrl, shortName };
|
|
904
894
|
}
|
|
905
895
|
async function createProxyConfig(baseUrl, apiKey, agentName, targetUrl, serverName, platform) {
|
|
@@ -943,24 +933,84 @@ async function createProxyConfig(baseUrl, apiKey, agentName, targetUrl, serverNa
|
|
|
943
933
|
const data = envelope["data"];
|
|
944
934
|
return typeof data?.["proxy_url"] === "string" ? data["proxy_url"] : "";
|
|
945
935
|
}
|
|
936
|
+
function gooseHostedProxyYaml(shortName, proxyUrl, bearerHeader) {
|
|
937
|
+
return `extensions:
|
|
938
|
+
${shortName}:
|
|
939
|
+
type: streamable_http
|
|
940
|
+
url: ${proxyUrl}
|
|
941
|
+
headers:
|
|
942
|
+
Authorization: ${bearerHeader}
|
|
943
|
+
enabled: true
|
|
944
|
+
timeout: 300
|
|
945
|
+
`;
|
|
946
|
+
}
|
|
946
947
|
function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
|
|
947
|
-
const
|
|
948
|
+
const hostedInlinePlatforms = /* @__PURE__ */ new Set([
|
|
949
|
+
"cursor",
|
|
950
|
+
"claude-desktop",
|
|
951
|
+
"windsurf",
|
|
952
|
+
"cline",
|
|
953
|
+
"gemini-cli",
|
|
954
|
+
"kilo-code",
|
|
955
|
+
"github-copilot",
|
|
956
|
+
"continue-dev",
|
|
957
|
+
"goose"
|
|
958
|
+
]);
|
|
959
|
+
const usesInlineKey = hostedInlinePlatforms.has(platform);
|
|
948
960
|
const authHeader = usesInlineKey ? `Bearer ${apiKey}` : "Bearer YOUR_SHIELD_API_KEY";
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
961
|
+
let snippetText;
|
|
962
|
+
if (platform === "github-copilot") {
|
|
963
|
+
snippetText = JSON.stringify(
|
|
964
|
+
{
|
|
965
|
+
mcp: {
|
|
966
|
+
servers: {
|
|
967
|
+
[shortName]: {
|
|
968
|
+
type: "http",
|
|
969
|
+
url: routingToken,
|
|
970
|
+
headers: {
|
|
971
|
+
Authorization: authHeader
|
|
972
|
+
}
|
|
973
|
+
}
|
|
957
974
|
}
|
|
958
975
|
}
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
)
|
|
976
|
+
},
|
|
977
|
+
null,
|
|
978
|
+
2
|
|
979
|
+
);
|
|
980
|
+
} else if (platform === "goose") {
|
|
981
|
+
snippetText = gooseHostedProxyYaml(shortName, routingToken, authHeader);
|
|
982
|
+
} else if (platform === "gemini-cli") {
|
|
983
|
+
snippetText = JSON.stringify(
|
|
984
|
+
{
|
|
985
|
+
mcpServers: {
|
|
986
|
+
[shortName]: {
|
|
987
|
+
httpUrl: routingToken,
|
|
988
|
+
headers: {
|
|
989
|
+
Authorization: authHeader
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
},
|
|
994
|
+
null,
|
|
995
|
+
2
|
|
996
|
+
);
|
|
997
|
+
} else {
|
|
998
|
+
const urlKey = platform === "windsurf" ? "serverUrl" : "url";
|
|
999
|
+
snippetText = JSON.stringify(
|
|
1000
|
+
{
|
|
1001
|
+
mcpServers: {
|
|
1002
|
+
[shortName]: {
|
|
1003
|
+
[urlKey]: routingToken,
|
|
1004
|
+
headers: {
|
|
1005
|
+
Authorization: authHeader
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
},
|
|
1010
|
+
null,
|
|
1011
|
+
2
|
|
1012
|
+
);
|
|
1013
|
+
}
|
|
964
1014
|
if (platform === "openclaw") {
|
|
965
1015
|
process.stderr.write("\n" + style.dim("Add this to your OpenClaw agent config:") + "\n\n");
|
|
966
1016
|
} else if (platform === "claude-code") {
|
|
@@ -994,10 +1044,28 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
|
|
|
994
1044
|
"Add this to ~/.gemini/settings.json (create the file if it does not exist). For project-specific config, use .gemini/settings.json in your project root. Restart Gemini CLI after saving. Run /mcp to verify the server is connected."
|
|
995
1045
|
) + "\n\n"
|
|
996
1046
|
);
|
|
1047
|
+
} else if (platform === "kilo-code") {
|
|
1048
|
+
process.stderr.write(
|
|
1049
|
+
"\n" + style.dim("Add this to .kilocode/mcp.json in your project root.") + "\n\n"
|
|
1050
|
+
);
|
|
1051
|
+
} else if (platform === "github-copilot") {
|
|
1052
|
+
process.stderr.write(
|
|
1053
|
+
"\n" + style.dim(
|
|
1054
|
+
"Open VS Code Settings (JSON) and merge this under the mcp key. If you do not have an mcp section yet, add one. Copilot picks up MCP servers when you use Agent mode."
|
|
1055
|
+
) + "\n\n"
|
|
1056
|
+
);
|
|
1057
|
+
} else if (platform === "continue-dev") {
|
|
1058
|
+
process.stderr.write(
|
|
1059
|
+
"\n" + style.dim("Save this as .continue/mcpServers/shield.json in your workspace root.") + "\n\n"
|
|
1060
|
+
);
|
|
1061
|
+
} else if (platform === "goose") {
|
|
1062
|
+
process.stderr.write(
|
|
1063
|
+
"\n" + style.dim("Add this to ~/.config/goose/config.yaml under the extensions key.") + "\n\n"
|
|
1064
|
+
);
|
|
997
1065
|
} else {
|
|
998
1066
|
process.stderr.write("\n" + style.dim("Add this to ~/.cursor/mcp.json:") + "\n\n");
|
|
999
1067
|
}
|
|
1000
|
-
process.stderr.write(style.cyan(
|
|
1068
|
+
process.stderr.write(style.cyan(snippetText) + "\n\n");
|
|
1001
1069
|
if (!usesInlineKey) {
|
|
1002
1070
|
process.stderr.write(
|
|
1003
1071
|
style.dim(
|
|
@@ -1035,8 +1103,15 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
|
|
|
1035
1103
|
) + "\n"
|
|
1036
1104
|
);
|
|
1037
1105
|
}
|
|
1106
|
+
if (platform === "github-copilot" || platform === "continue-dev") {
|
|
1107
|
+
process.stderr.write(
|
|
1108
|
+
style.dim("Reload the editor window if the MCP server does not appear immediately.") + "\n"
|
|
1109
|
+
);
|
|
1110
|
+
}
|
|
1111
|
+
if (platform === "goose") {
|
|
1112
|
+
process.stderr.write(style.dim("Start a new Goose session after updating config.") + "\n");
|
|
1113
|
+
}
|
|
1038
1114
|
}
|
|
1039
|
-
var DEFAULT_SHIELD_API_BASE_URL = "https://api.multicorn.ai";
|
|
1040
1115
|
async function runInit(explicitBaseUrl) {
|
|
1041
1116
|
if (!process.stdin.isTTY) {
|
|
1042
1117
|
process.stderr.write(
|
|
@@ -1124,8 +1199,8 @@ async function runInit(explicitBaseUrl) {
|
|
|
1124
1199
|
let postSaveNativeSkipNote = null;
|
|
1125
1200
|
const selection = await promptPlatformSelection(ask);
|
|
1126
1201
|
const selectedPlatform = PLATFORM_BY_SELECTION[selection] ?? "cursor";
|
|
1127
|
-
const selectedLabel =
|
|
1128
|
-
if (
|
|
1202
|
+
const selectedLabel = platformMenuLabelForSelection(selection);
|
|
1203
|
+
if (selectedPlatform === "other-mcp") {
|
|
1129
1204
|
const raw = existing !== null ? { ...existing } : {};
|
|
1130
1205
|
raw["apiKey"] = apiKey;
|
|
1131
1206
|
raw["baseUrl"] = resolvedBaseUrl;
|
|
@@ -1140,7 +1215,7 @@ async function runInit(explicitBaseUrl) {
|
|
|
1140
1215
|
);
|
|
1141
1216
|
process.stderr.write(
|
|
1142
1217
|
"\n" + style.bold("Try it:") + " " + style.cyan(
|
|
1143
|
-
"npx multicorn-
|
|
1218
|
+
"npx multicorn-shield --wrap npx @modelcontextprotocol/server-filesystem /tmp"
|
|
1144
1219
|
) + "\n"
|
|
1145
1220
|
);
|
|
1146
1221
|
} catch (error) {
|
|
@@ -1175,9 +1250,24 @@ An agent for ${selectedLabel} already exists: ${style.cyan(existingForPlatform.n
|
|
|
1175
1250
|
continue;
|
|
1176
1251
|
}
|
|
1177
1252
|
}
|
|
1253
|
+
const prereqEntry = INIT_WIZARD_PLATFORM_REGISTRY.find((e) => e.slug === selectedPlatform);
|
|
1254
|
+
if (prereqEntry?.prereqUrl !== void 0) {
|
|
1255
|
+
const proceed = await promptHostedProxyInstallPrereq(
|
|
1256
|
+
ask,
|
|
1257
|
+
prereqEntry.displayName,
|
|
1258
|
+
prereqEntry.prereqUrl
|
|
1259
|
+
);
|
|
1260
|
+
if (!proceed) {
|
|
1261
|
+
const another2 = await ask("\nConnect another agent? (Y/n) ");
|
|
1262
|
+
if (another2.trim().toLowerCase() === "n") {
|
|
1263
|
+
configuring = false;
|
|
1264
|
+
}
|
|
1265
|
+
continue;
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1178
1268
|
const agentName = await promptAgentName(ask, selectedPlatform);
|
|
1179
1269
|
let setupSucceeded = false;
|
|
1180
|
-
if (
|
|
1270
|
+
if (selectedPlatform === "openclaw") {
|
|
1181
1271
|
let detection;
|
|
1182
1272
|
try {
|
|
1183
1273
|
detection = await detectOpenClaw();
|
|
@@ -1190,7 +1280,7 @@ An agent for ${selectedLabel} already exists: ${style.cyan(existingForPlatform.n
|
|
|
1190
1280
|
}
|
|
1191
1281
|
if (detection.status === "not-found") {
|
|
1192
1282
|
process.stderr.write(
|
|
1193
|
-
style.red("\u2717") + " OpenClaw is not installed. Install OpenClaw first, then run npx multicorn-
|
|
1283
|
+
style.red("\u2717") + " OpenClaw is not installed. Install OpenClaw first, then run npx multicorn-shield init again.\n"
|
|
1194
1284
|
);
|
|
1195
1285
|
rl.close();
|
|
1196
1286
|
return null;
|
|
@@ -1257,7 +1347,7 @@ An agent for ${selectedLabel} already exists: ${style.cyan(existingForPlatform.n
|
|
|
1257
1347
|
agentName
|
|
1258
1348
|
});
|
|
1259
1349
|
setupSucceeded = true;
|
|
1260
|
-
} else if (
|
|
1350
|
+
} else if (selectedPlatform === "claude-code") {
|
|
1261
1351
|
process.stderr.write("\nTo connect Claude Code to Shield:\n\n");
|
|
1262
1352
|
process.stderr.write(
|
|
1263
1353
|
" " + style.bold("Step 1") + " - Add the Multicorn marketplace:\n " + style.cyan("claude plugin marketplace add Multicorn-AI/multicorn-shield") + "\n\n"
|
|
@@ -1275,7 +1365,7 @@ An agent for ${selectedLabel} already exists: ${style.cyan(existingForPlatform.n
|
|
|
1275
1365
|
agentName
|
|
1276
1366
|
});
|
|
1277
1367
|
setupSucceeded = true;
|
|
1278
|
-
} else if (
|
|
1368
|
+
} else if (selectedPlatform === "windsurf") {
|
|
1279
1369
|
const windsurfMode = await promptWindsurfIntegrationMode(ask);
|
|
1280
1370
|
if (windsurfMode === "native") {
|
|
1281
1371
|
try {
|
|
@@ -1306,7 +1396,7 @@ An agent for ${selectedLabel} already exists: ${style.cyan(existingForPlatform.n
|
|
|
1306
1396
|
} catch (error) {
|
|
1307
1397
|
if (error instanceof NativePluginPrerequisiteMissingError) {
|
|
1308
1398
|
postSaveNativeSkipNote = nativePluginSkippedSaveNote(
|
|
1309
|
-
"npx multicorn-
|
|
1399
|
+
"npx multicorn-shield init",
|
|
1310
1400
|
"Windsurf"
|
|
1311
1401
|
);
|
|
1312
1402
|
configuredAgents.push({
|
|
@@ -1363,7 +1453,7 @@ An agent for ${selectedLabel} already exists: ${style.cyan(existingForPlatform.n
|
|
|
1363
1453
|
setupSucceeded = true;
|
|
1364
1454
|
}
|
|
1365
1455
|
}
|
|
1366
|
-
} else if (
|
|
1456
|
+
} else if (selectedPlatform === "gemini-cli") {
|
|
1367
1457
|
const geminiMode = await promptGeminiCliIntegrationMode(ask);
|
|
1368
1458
|
if (geminiMode === "native") {
|
|
1369
1459
|
try {
|
|
@@ -1448,7 +1538,7 @@ An agent for ${selectedLabel} already exists: ${style.cyan(existingForPlatform.n
|
|
|
1448
1538
|
setupSucceeded = true;
|
|
1449
1539
|
}
|
|
1450
1540
|
}
|
|
1451
|
-
} else if (
|
|
1541
|
+
} else if (selectedPlatform === "cline") {
|
|
1452
1542
|
const clineMode = await promptClineIntegrationMode(ask);
|
|
1453
1543
|
if (clineMode === "native") {
|
|
1454
1544
|
try {
|
|
@@ -1634,6 +1724,26 @@ An agent for ${selectedLabel} already exists: ${style.cyan(existingForPlatform.n
|
|
|
1634
1724
|
"\n" + style.bold("To complete your Cursor setup:") + "\n 1. If you don't have Cursor yet, download it from " + style.cyan("https://cursor.com/downloads") + "\n 2. Open " + style.cyan("~/.cursor/mcp.json") + " and paste the config snippet shown above\n 3. Restart Cursor (or launch it for the first time) to load the new MCP server\n"
|
|
1635
1725
|
);
|
|
1636
1726
|
}
|
|
1727
|
+
if (configuredPlatforms.has("kilo-code")) {
|
|
1728
|
+
blocks.push(
|
|
1729
|
+
"\n" + style.bold("To complete your Kilo Code setup:") + "\n 1. Save the snippet to " + style.cyan(".kilocode/mcp.json") + " in your project root, or under the mcp key in " + style.cyan("kilo.jsonc") + "\n 2. Run your next task in Kilo Code so it picks up the MCP server\n"
|
|
1730
|
+
);
|
|
1731
|
+
}
|
|
1732
|
+
if (configuredPlatforms.has("github-copilot")) {
|
|
1733
|
+
blocks.push(
|
|
1734
|
+
"\n" + style.bold("GitHub Copilot MCP:") + "\n 1. Open VS Code Command Palette: Preferences: Open User Settings (JSON)\n 2. Merge the snippet under the " + style.cyan("mcp") + " key and save\n 3. Use Copilot Agent mode and verify the MCP server connects\n"
|
|
1735
|
+
);
|
|
1736
|
+
}
|
|
1737
|
+
if (configuredPlatforms.has("continue-dev")) {
|
|
1738
|
+
blocks.push(
|
|
1739
|
+
"\n" + style.bold("Continue MCP:") + "\n 1. If you don't have Continue yet, install from " + style.cyan("https://docs.continue.dev/ide-extensions/install") + "\n 2. Save JSON as " + style.cyan(".continue/mcpServers/shield.json") + " in your workspace, or add to " + style.cyan("~/.continue/config.yaml") + "\n 3. Reload VS Code and open Continue agent mode\n"
|
|
1740
|
+
);
|
|
1741
|
+
}
|
|
1742
|
+
if (configuredPlatforms.has("goose")) {
|
|
1743
|
+
blocks.push(
|
|
1744
|
+
"\n" + style.bold("Goose MCP extension:") + "\n 1. Edit " + style.cyan("~/.config/goose/config.yaml") + " (or use goose configure)\n 2. Restart Goose CLI or Desktop\n"
|
|
1745
|
+
);
|
|
1746
|
+
}
|
|
1637
1747
|
const windsurfNativeConfigured = configuredAgents.some(
|
|
1638
1748
|
(a) => a.platform === "windsurf" && a.windsurfIntegration === "native"
|
|
1639
1749
|
);
|
|
@@ -1689,22 +1799,144 @@ An agent for ${selectedLabel} already exists: ${style.cyan(existingForPlatform.n
|
|
|
1689
1799
|
}
|
|
1690
1800
|
return lastConfig;
|
|
1691
1801
|
}
|
|
1802
|
+
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_AGENT_NAMES, DEFAULT_SHIELD_API_BASE_URL;
|
|
1803
|
+
var init_config = __esm({
|
|
1804
|
+
"src/proxy/config.ts"() {
|
|
1805
|
+
style = {
|
|
1806
|
+
violet: (s) => `\x1B[38;2;124;58;237m${s}\x1B[0m`,
|
|
1807
|
+
violetLight: (s) => `\x1B[38;2;167;139;250m${s}\x1B[0m`,
|
|
1808
|
+
green: (s) => `\x1B[38;2;34;197;94m${s}\x1B[0m`,
|
|
1809
|
+
yellow: (s) => `\x1B[38;2;245;158;11m${s}\x1B[0m`,
|
|
1810
|
+
red: (s) => `\x1B[38;2;239;68;68m${s}\x1B[0m`,
|
|
1811
|
+
cyan: (s) => `\x1B[38;2;6;182;212m${s}\x1B[0m`,
|
|
1812
|
+
bold: (s) => `\x1B[1m${s}\x1B[0m`,
|
|
1813
|
+
dim: (s) => `\x1B[2m${s}\x1B[0m`
|
|
1814
|
+
};
|
|
1815
|
+
BANNER = [
|
|
1816
|
+
" \u2588\u2588\u2588 \u2588 \u2588 \u2588 \u2588\u2588\u2588 \u2588 \u2588\u2588\u2584 ",
|
|
1817
|
+
" \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588",
|
|
1818
|
+
" \u2588\u2588\u2588 \u2588\u2588\u2588\u2588 \u2588 \u2588\u2588 \u2588 \u2588 \u2588",
|
|
1819
|
+
" \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588",
|
|
1820
|
+
" \u2588\u2588\u2588 \u2588 \u2588 \u2588 \u2588\u2588\u2588 \u2588\u2588\u2588 \u2588\u2588\u2580 "
|
|
1821
|
+
].map((line) => style.violet(line)).join("\n");
|
|
1822
|
+
NativePluginPrerequisiteMissingError = class extends Error {
|
|
1823
|
+
constructor() {
|
|
1824
|
+
super("Native plugin prerequisites not met");
|
|
1825
|
+
this.name = "NativePluginPrerequisiteMissingError";
|
|
1826
|
+
}
|
|
1827
|
+
};
|
|
1828
|
+
CONFIG_DIR = join(homedir(), ".multicorn");
|
|
1829
|
+
CONFIG_PATH = join(CONFIG_DIR, "config.json");
|
|
1830
|
+
OPENCLAW_CONFIG_PATH = join(homedir(), ".openclaw", "openclaw.json");
|
|
1831
|
+
ANSI_PATTERN = new RegExp(String.fromCharCode(27) + "\\[[0-9;]*[a-zA-Z]", "g");
|
|
1832
|
+
OPENCLAW_MIN_VERSION = "2026.2.26";
|
|
1833
|
+
INIT_WIZARD_PLATFORM_REGISTRY = [
|
|
1834
|
+
{ slug: "openclaw", displayName: "OpenClaw", section: "native", detectable: true },
|
|
1835
|
+
{ slug: "claude-code", displayName: "Claude Code", section: "native", detectable: true },
|
|
1836
|
+
{ slug: "windsurf", displayName: "Windsurf", section: "native", detectable: true },
|
|
1837
|
+
{ slug: "cline", displayName: "Cline", section: "native", detectable: false },
|
|
1838
|
+
{ slug: "gemini-cli", displayName: "Gemini CLI", section: "native", detectable: false },
|
|
1839
|
+
{
|
|
1840
|
+
slug: "cursor",
|
|
1841
|
+
displayName: "Cursor",
|
|
1842
|
+
section: "hosted",
|
|
1843
|
+
prereqUrl: "https://www.cursor.com/downloads",
|
|
1844
|
+
detectable: true
|
|
1845
|
+
},
|
|
1846
|
+
{
|
|
1847
|
+
slug: "claude-desktop",
|
|
1848
|
+
displayName: "Claude Desktop",
|
|
1849
|
+
section: "hosted",
|
|
1850
|
+
prereqUrl: "https://claude.ai/download",
|
|
1851
|
+
detectable: false
|
|
1852
|
+
},
|
|
1853
|
+
{
|
|
1854
|
+
slug: "github-copilot",
|
|
1855
|
+
displayName: "GitHub Copilot",
|
|
1856
|
+
section: "hosted",
|
|
1857
|
+
prereqUrl: "https://docs.github.com/en/copilot/get-started",
|
|
1858
|
+
detectable: false
|
|
1859
|
+
},
|
|
1860
|
+
{
|
|
1861
|
+
slug: "kilo-code",
|
|
1862
|
+
displayName: "Kilo Code",
|
|
1863
|
+
section: "hosted",
|
|
1864
|
+
prereqUrl: "https://kilocode.ai/docs/getting-started",
|
|
1865
|
+
detectable: false
|
|
1866
|
+
},
|
|
1867
|
+
{
|
|
1868
|
+
slug: "continue-dev",
|
|
1869
|
+
displayName: "Continue",
|
|
1870
|
+
section: "hosted",
|
|
1871
|
+
prereqUrl: "https://docs.continue.dev/ide-extensions/install",
|
|
1872
|
+
detectable: false
|
|
1873
|
+
},
|
|
1874
|
+
{
|
|
1875
|
+
slug: "goose",
|
|
1876
|
+
displayName: "Goose",
|
|
1877
|
+
section: "hosted",
|
|
1878
|
+
prereqUrl: "https://goose-docs.ai/docs/quickstart/",
|
|
1879
|
+
detectable: false
|
|
1880
|
+
},
|
|
1881
|
+
{ slug: "other-mcp", displayName: "Local MCP / Other", section: "hosted", detectable: false }
|
|
1882
|
+
];
|
|
1883
|
+
INIT_WIZARD_MENU_SECTIONS = (() => {
|
|
1884
|
+
const itemsFor = (section) => INIT_WIZARD_PLATFORM_REGISTRY.filter((e) => e.section === section).map((e) => ({
|
|
1885
|
+
platform: e.slug,
|
|
1886
|
+
label: e.displayName
|
|
1887
|
+
}));
|
|
1888
|
+
return [
|
|
1889
|
+
{ title: "Recommended (native plugin)", items: itemsFor("native") },
|
|
1890
|
+
{ title: "Hosted proxy (MCP only)", items: itemsFor("hosted") }
|
|
1891
|
+
];
|
|
1892
|
+
})();
|
|
1893
|
+
INIT_WIZARD_SELECTION_MAX = INIT_WIZARD_PLATFORM_REGISTRY.length;
|
|
1894
|
+
PLATFORM_BY_SELECTION = Object.fromEntries(
|
|
1895
|
+
INIT_WIZARD_PLATFORM_REGISTRY.map((e, i) => [i + 1, e.slug])
|
|
1896
|
+
);
|
|
1897
|
+
DEFAULT_AGENT_NAMES = {
|
|
1898
|
+
openclaw: "my-openclaw-agent",
|
|
1899
|
+
"claude-code": "my-claude-code-agent",
|
|
1900
|
+
cursor: "my-cursor-agent",
|
|
1901
|
+
windsurf: "my-windsurf-agent",
|
|
1902
|
+
cline: "my-cline-agent",
|
|
1903
|
+
"claude-desktop": "my-claude-desktop-agent",
|
|
1904
|
+
"gemini-cli": "my-gemini-cli-agent",
|
|
1905
|
+
"kilo-code": "my-kilo-code-agent",
|
|
1906
|
+
"github-copilot": "my-github-copilot-agent",
|
|
1907
|
+
"continue-dev": "my-continue-agent",
|
|
1908
|
+
goose: "my-goose-agent"
|
|
1909
|
+
};
|
|
1910
|
+
DEFAULT_SHIELD_API_BASE_URL = "https://api.multicorn.ai";
|
|
1911
|
+
}
|
|
1912
|
+
});
|
|
1692
1913
|
|
|
1693
1914
|
// src/types/index.ts
|
|
1694
|
-
var PERMISSION_LEVELS
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1915
|
+
var PERMISSION_LEVELS;
|
|
1916
|
+
var init_types = __esm({
|
|
1917
|
+
"src/types/index.ts"() {
|
|
1918
|
+
PERMISSION_LEVELS = {
|
|
1919
|
+
Read: "read",
|
|
1920
|
+
Write: "write",
|
|
1921
|
+
Execute: "execute",
|
|
1922
|
+
Publish: "publish",
|
|
1923
|
+
Create: "create"
|
|
1924
|
+
};
|
|
1925
|
+
}
|
|
1926
|
+
});
|
|
1701
1927
|
|
|
1702
1928
|
// src/scopes/scope-parser.ts
|
|
1703
|
-
var VALID_PERMISSION_LEVELS = new Set(Object.values(PERMISSION_LEVELS));
|
|
1704
|
-
[...VALID_PERMISSION_LEVELS].join(", ");
|
|
1705
1929
|
function formatScope(scope) {
|
|
1706
1930
|
return `${scope.permissionLevel}:${scope.service}`;
|
|
1707
1931
|
}
|
|
1932
|
+
var VALID_PERMISSION_LEVELS;
|
|
1933
|
+
var init_scope_parser = __esm({
|
|
1934
|
+
"src/scopes/scope-parser.ts"() {
|
|
1935
|
+
init_types();
|
|
1936
|
+
VALID_PERMISSION_LEVELS = new Set(Object.values(PERMISSION_LEVELS));
|
|
1937
|
+
[...VALID_PERMISSION_LEVELS].join(", ");
|
|
1938
|
+
}
|
|
1939
|
+
});
|
|
1708
1940
|
|
|
1709
1941
|
// src/scopes/scope-validator.ts
|
|
1710
1942
|
function validateScopeAccess(grantedScopes, requested) {
|
|
@@ -1732,6 +1964,11 @@ function hasScope(grantedScopes, requested) {
|
|
|
1732
1964
|
(granted) => granted.service === requested.service && granted.permissionLevel === requested.permissionLevel
|
|
1733
1965
|
);
|
|
1734
1966
|
}
|
|
1967
|
+
var init_scope_validator = __esm({
|
|
1968
|
+
"src/scopes/scope-validator.ts"() {
|
|
1969
|
+
init_scope_parser();
|
|
1970
|
+
}
|
|
1971
|
+
});
|
|
1735
1972
|
|
|
1736
1973
|
// src/logger/action-logger.ts
|
|
1737
1974
|
function createActionLogger(config) {
|
|
@@ -1905,6 +2142,10 @@ function createActionLogger(config) {
|
|
|
1905
2142
|
function sleep(ms) {
|
|
1906
2143
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1907
2144
|
}
|
|
2145
|
+
var init_action_logger = __esm({
|
|
2146
|
+
"src/logger/action-logger.ts"() {
|
|
2147
|
+
}
|
|
2148
|
+
});
|
|
1908
2149
|
|
|
1909
2150
|
// src/spending/spending-checker.ts
|
|
1910
2151
|
function createSpendingChecker(config) {
|
|
@@ -2037,13 +2278,12 @@ function validateLimits(limits) {
|
|
|
2037
2278
|
function dollarsToCents(dollars) {
|
|
2038
2279
|
return Math.round(dollars * 100);
|
|
2039
2280
|
}
|
|
2281
|
+
var init_spending_checker = __esm({
|
|
2282
|
+
"src/spending/spending-checker.ts"() {
|
|
2283
|
+
}
|
|
2284
|
+
});
|
|
2040
2285
|
|
|
2041
2286
|
// src/proxy/interceptor.ts
|
|
2042
|
-
var BLOCKED_ERROR_CODE = -32e3;
|
|
2043
|
-
var SPENDING_BLOCKED_ERROR_CODE = -32001;
|
|
2044
|
-
var INTERNAL_ERROR_CODE = -32002;
|
|
2045
|
-
var SERVICE_UNREACHABLE_ERROR_CODE = -32003;
|
|
2046
|
-
var AUTH_ERROR_CODE = -32004;
|
|
2047
2287
|
function parseJsonRpcLine(line) {
|
|
2048
2288
|
const trimmed = line.trim();
|
|
2049
2289
|
if (trimmed.length === 0) return null;
|
|
@@ -2111,7 +2351,7 @@ function buildServiceUnreachableResponse(id, dashboardUrl) {
|
|
|
2111
2351
|
};
|
|
2112
2352
|
}
|
|
2113
2353
|
function buildAuthErrorResponse(id) {
|
|
2114
|
-
const message = "Action blocked: Shield API key is invalid or has been revoked. Run npx multicorn-
|
|
2354
|
+
const message = "Action blocked: Shield API key is invalid or has been revoked. Run npx multicorn-shield init to reconfigure.";
|
|
2115
2355
|
return {
|
|
2116
2356
|
jsonrpc: "2.0",
|
|
2117
2357
|
id,
|
|
@@ -2143,9 +2383,16 @@ function capitalize(str) {
|
|
|
2143
2383
|
const first = str[0];
|
|
2144
2384
|
return first !== void 0 ? first.toUpperCase() + str.slice(1) : str;
|
|
2145
2385
|
}
|
|
2146
|
-
var
|
|
2147
|
-
var
|
|
2148
|
-
|
|
2386
|
+
var BLOCKED_ERROR_CODE, SPENDING_BLOCKED_ERROR_CODE, INTERNAL_ERROR_CODE, SERVICE_UNREACHABLE_ERROR_CODE, AUTH_ERROR_CODE;
|
|
2387
|
+
var init_interceptor = __esm({
|
|
2388
|
+
"src/proxy/interceptor.ts"() {
|
|
2389
|
+
BLOCKED_ERROR_CODE = -32e3;
|
|
2390
|
+
SPENDING_BLOCKED_ERROR_CODE = -32001;
|
|
2391
|
+
INTERNAL_ERROR_CODE = -32002;
|
|
2392
|
+
SERVICE_UNREACHABLE_ERROR_CODE = -32003;
|
|
2393
|
+
AUTH_ERROR_CODE = -32004;
|
|
2394
|
+
}
|
|
2395
|
+
});
|
|
2149
2396
|
function cacheKey(agentName, apiKey) {
|
|
2150
2397
|
return createHash("sha256").update(`${agentName}:${apiKey}`).digest("hex").slice(0, 16);
|
|
2151
2398
|
}
|
|
@@ -2216,10 +2463,14 @@ async function saveCachedScopes(agentName, agentId, scopes, apiKey) {
|
|
|
2216
2463
|
function isScopesCacheFile(value) {
|
|
2217
2464
|
return typeof value === "object" && value !== null;
|
|
2218
2465
|
}
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2466
|
+
var MULTICORN_DIR, SCOPES_PATH, CACHE_META_PATH;
|
|
2467
|
+
var init_scope_cache = __esm({
|
|
2468
|
+
"src/openclaw/scope-cache.ts"() {
|
|
2469
|
+
MULTICORN_DIR = join(homedir(), ".multicorn");
|
|
2470
|
+
SCOPES_PATH = join(MULTICORN_DIR, "scopes.json");
|
|
2471
|
+
CACHE_META_PATH = join(MULTICORN_DIR, "cache-meta.json");
|
|
2472
|
+
}
|
|
2473
|
+
});
|
|
2223
2474
|
function deriveDashboardUrl(baseUrl) {
|
|
2224
2475
|
try {
|
|
2225
2476
|
const url = new URL(baseUrl);
|
|
@@ -2244,13 +2495,6 @@ function deriveDashboardUrl(baseUrl) {
|
|
|
2244
2495
|
return "https://app.multicorn.ai";
|
|
2245
2496
|
}
|
|
2246
2497
|
}
|
|
2247
|
-
var ShieldAuthError = class _ShieldAuthError extends Error {
|
|
2248
|
-
constructor(message) {
|
|
2249
|
-
super(message);
|
|
2250
|
-
this.name = "ShieldAuthError";
|
|
2251
|
-
Object.setPrototypeOf(this, _ShieldAuthError.prototype);
|
|
2252
|
-
}
|
|
2253
|
-
};
|
|
2254
2498
|
async function findAgentByName(agentName, apiKey, baseUrl) {
|
|
2255
2499
|
let response;
|
|
2256
2500
|
try {
|
|
@@ -2443,13 +2687,25 @@ function isPermissionShape(value) {
|
|
|
2443
2687
|
const obj = value;
|
|
2444
2688
|
return typeof obj["service"] === "string" && typeof obj["read"] === "boolean" && typeof obj["write"] === "boolean" && typeof obj["execute"] === "boolean" && (obj["revoked_at"] === null || obj["revoked_at"] === void 0 || typeof obj["revoked_at"] === "string");
|
|
2445
2689
|
}
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2690
|
+
var CONSENT_POLL_INTERVAL_MS, CONSENT_POLL_TIMEOUT_MS, ShieldAuthError;
|
|
2691
|
+
var init_consent = __esm({
|
|
2692
|
+
"src/proxy/consent.ts"() {
|
|
2693
|
+
init_scope_cache();
|
|
2694
|
+
CONSENT_POLL_INTERVAL_MS = 3e3;
|
|
2695
|
+
CONSENT_POLL_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
2696
|
+
ShieldAuthError = class _ShieldAuthError extends Error {
|
|
2697
|
+
constructor(message) {
|
|
2698
|
+
super(message);
|
|
2699
|
+
this.name = "ShieldAuthError";
|
|
2700
|
+
Object.setPrototypeOf(this, _ShieldAuthError.prototype);
|
|
2701
|
+
}
|
|
2702
|
+
};
|
|
2703
|
+
}
|
|
2704
|
+
});
|
|
2449
2705
|
function createProxyServer(config) {
|
|
2450
2706
|
if (!config.baseUrl.startsWith("https://") && !config.baseUrl.startsWith("http://localhost") && !config.baseUrl.startsWith("http://127.0.0.1")) {
|
|
2451
2707
|
throw new Error(
|
|
2452
|
-
`[multicorn-
|
|
2708
|
+
`[multicorn-shield] Base URL must use HTTPS. Received: "${config.baseUrl}". Use https:// or http://localhost for local development.`
|
|
2453
2709
|
);
|
|
2454
2710
|
}
|
|
2455
2711
|
let child = null;
|
|
@@ -2546,7 +2802,7 @@ function createProxyServer(config) {
|
|
|
2546
2802
|
if (actionLogger !== null) {
|
|
2547
2803
|
if (!config.agentName || config.agentName.trim().length === 0) {
|
|
2548
2804
|
process.stderr.write(
|
|
2549
|
-
"[multicorn-
|
|
2805
|
+
"[multicorn-shield] Cannot log action: agent name not resolved\n"
|
|
2550
2806
|
);
|
|
2551
2807
|
} else {
|
|
2552
2808
|
config.logger.debug("Logging blocked action (post-consent).", {
|
|
@@ -2576,7 +2832,7 @@ function createProxyServer(config) {
|
|
|
2576
2832
|
if (actionLogger !== null) {
|
|
2577
2833
|
if (!config.agentName || config.agentName.trim().length === 0) {
|
|
2578
2834
|
process.stderr.write(
|
|
2579
|
-
"[multicorn-
|
|
2835
|
+
"[multicorn-shield] Cannot log action: agent name not resolved\n"
|
|
2580
2836
|
);
|
|
2581
2837
|
} else {
|
|
2582
2838
|
config.logger.debug("Logging blocked action (spending).", {
|
|
@@ -2605,7 +2861,7 @@ function createProxyServer(config) {
|
|
|
2605
2861
|
}
|
|
2606
2862
|
if (actionLogger !== null) {
|
|
2607
2863
|
if (!config.agentName || config.agentName.trim().length === 0) {
|
|
2608
|
-
process.stderr.write("[multicorn-
|
|
2864
|
+
process.stderr.write("[multicorn-shield] Cannot log action: agent name not resolved\n");
|
|
2609
2865
|
} else {
|
|
2610
2866
|
config.logger.debug("Logging approved action.", {
|
|
2611
2867
|
agent: config.agentName,
|
|
@@ -2687,7 +2943,7 @@ function createProxyServer(config) {
|
|
|
2687
2943
|
agent: config.agentName
|
|
2688
2944
|
});
|
|
2689
2945
|
process.stderr.write(
|
|
2690
|
-
"\nError: API key was rejected by the Multicorn service.\nCheck your key at https://app.multicorn.ai/settings#api-keys or run `npx multicorn-
|
|
2946
|
+
"\nError: API key was rejected by the Multicorn service.\nCheck your key at https://app.multicorn.ai/settings#api-keys or run `npx multicorn-shield init` to reconfigure.\n\n"
|
|
2691
2947
|
);
|
|
2692
2948
|
throw new Error("API key was rejected by the Multicorn service.");
|
|
2693
2949
|
}
|
|
@@ -2760,12 +3016,17 @@ function extractCostCents(args) {
|
|
|
2760
3016
|
if (typeof amount !== "number" || !Number.isFinite(amount) || amount <= 0) return 0;
|
|
2761
3017
|
return dollarsToCents(amount);
|
|
2762
3018
|
}
|
|
2763
|
-
var
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
3019
|
+
var DEFAULT_SCOPE_REFRESH_INTERVAL_MS;
|
|
3020
|
+
var init_proxy = __esm({
|
|
3021
|
+
"src/proxy/index.ts"() {
|
|
3022
|
+
init_scope_validator();
|
|
3023
|
+
init_action_logger();
|
|
3024
|
+
init_spending_checker();
|
|
3025
|
+
init_interceptor();
|
|
3026
|
+
init_consent();
|
|
3027
|
+
DEFAULT_SCOPE_REFRESH_INTERVAL_MS = 6e4;
|
|
3028
|
+
}
|
|
3029
|
+
});
|
|
2769
3030
|
function createLogger(level, output = process.stderr) {
|
|
2770
3031
|
const minLevel = LOG_LEVELS[level];
|
|
2771
3032
|
function write(logLevel, msg, data) {
|
|
@@ -2796,8 +3057,93 @@ function createLogger(level, output = process.stderr) {
|
|
|
2796
3057
|
function isValidLogLevel(value) {
|
|
2797
3058
|
return typeof value === "string" && Object.hasOwn(LOG_LEVELS, value);
|
|
2798
3059
|
}
|
|
3060
|
+
var LOG_LEVELS;
|
|
3061
|
+
var init_logger = __esm({
|
|
3062
|
+
"src/proxy/logger.ts"() {
|
|
3063
|
+
LOG_LEVELS = {
|
|
3064
|
+
debug: 0,
|
|
3065
|
+
info: 1,
|
|
3066
|
+
warn: 2,
|
|
3067
|
+
error: 3
|
|
3068
|
+
};
|
|
3069
|
+
}
|
|
3070
|
+
});
|
|
3071
|
+
function getExtensionBackupPath() {
|
|
3072
|
+
return join(homedir(), ".multicorn", EXTENSION_BACKUP_FILENAME);
|
|
3073
|
+
}
|
|
3074
|
+
function isRecord(value) {
|
|
3075
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3076
|
+
}
|
|
3077
|
+
async function readExtensionBackup() {
|
|
3078
|
+
try {
|
|
3079
|
+
const raw = await readFile(getExtensionBackupPath(), "utf8");
|
|
3080
|
+
const parsed = JSON.parse(raw);
|
|
3081
|
+
if (!isRecord(parsed)) return null;
|
|
3082
|
+
if (parsed["version"] !== 1) return null;
|
|
3083
|
+
if (typeof parsed["createdAt"] !== "string") return null;
|
|
3084
|
+
if (typeof parsed["claudeDesktopConfigPath"] !== "string") return null;
|
|
3085
|
+
const mcpServers = parsed["mcpServers"];
|
|
3086
|
+
if (!isRecord(mcpServers)) return null;
|
|
3087
|
+
return {
|
|
3088
|
+
version: 1,
|
|
3089
|
+
createdAt: parsed["createdAt"],
|
|
3090
|
+
claudeDesktopConfigPath: parsed["claudeDesktopConfigPath"],
|
|
3091
|
+
mcpServers
|
|
3092
|
+
};
|
|
3093
|
+
} catch {
|
|
3094
|
+
return null;
|
|
3095
|
+
}
|
|
3096
|
+
}
|
|
3097
|
+
var EXTENSION_BACKUP_FILENAME;
|
|
3098
|
+
var init_config_reader = __esm({
|
|
3099
|
+
"src/extension/config-reader.ts"() {
|
|
3100
|
+
EXTENSION_BACKUP_FILENAME = "extension-backup.json";
|
|
3101
|
+
}
|
|
3102
|
+
});
|
|
3103
|
+
function isErrnoException2(e) {
|
|
3104
|
+
return typeof e === "object" && e !== null && "code" in e;
|
|
3105
|
+
}
|
|
3106
|
+
function isRecord2(value) {
|
|
3107
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3108
|
+
}
|
|
3109
|
+
async function restoreClaudeDesktopMcpFromBackup() {
|
|
3110
|
+
const backup = await readExtensionBackup();
|
|
3111
|
+
if (backup === null) {
|
|
3112
|
+
throw new Error(
|
|
3113
|
+
"No Shield extension backup found. Expected ~/.multicorn/extension-backup.json from a previous Shield Desktop Extension session."
|
|
3114
|
+
);
|
|
3115
|
+
}
|
|
3116
|
+
const configPath = getClaudeDesktopConfigPath();
|
|
3117
|
+
let root = {};
|
|
3118
|
+
try {
|
|
3119
|
+
const raw = await readFile(configPath, "utf8");
|
|
3120
|
+
const parsed = JSON.parse(raw);
|
|
3121
|
+
if (isRecord2(parsed)) {
|
|
3122
|
+
root = parsed;
|
|
3123
|
+
}
|
|
3124
|
+
} catch (error) {
|
|
3125
|
+
if (!isErrnoException2(error) || error.code !== "ENOENT") {
|
|
3126
|
+
throw error;
|
|
3127
|
+
}
|
|
3128
|
+
}
|
|
3129
|
+
root["mcpServers"] = backup.mcpServers;
|
|
3130
|
+
await mkdir(dirname(configPath), { recursive: true });
|
|
3131
|
+
await writeFile(configPath, JSON.stringify(root, null, 2) + "\n", { encoding: "utf8" });
|
|
3132
|
+
}
|
|
3133
|
+
var init_restore = __esm({
|
|
3134
|
+
"src/extension/restore.ts"() {
|
|
3135
|
+
init_config();
|
|
3136
|
+
init_config_reader();
|
|
3137
|
+
}
|
|
3138
|
+
});
|
|
2799
3139
|
|
|
2800
|
-
// bin/multicorn-
|
|
3140
|
+
// bin/multicorn-shield.ts
|
|
3141
|
+
var multicorn_shield_exports = {};
|
|
3142
|
+
__export(multicorn_shield_exports, {
|
|
3143
|
+
parseArgs: () => parseArgs,
|
|
3144
|
+
resolveWrapConfig: () => resolveWrapConfig,
|
|
3145
|
+
runCli: () => runCli
|
|
3146
|
+
});
|
|
2801
3147
|
function parseArgs(argv) {
|
|
2802
3148
|
const args = argv.slice(2);
|
|
2803
3149
|
let subcommand = "help";
|
|
@@ -2820,7 +3166,7 @@ function parseArgs(argv) {
|
|
|
2820
3166
|
const name = args[i + 1];
|
|
2821
3167
|
if (name === void 0 || name.startsWith("-")) {
|
|
2822
3168
|
process.stderr.write("Error: delete-agent requires an agent name.\n");
|
|
2823
|
-
process.stderr.write("Example: npx multicorn-
|
|
3169
|
+
process.stderr.write("Example: npx multicorn-shield delete-agent my-agent\n");
|
|
2824
3170
|
process.exit(1);
|
|
2825
3171
|
}
|
|
2826
3172
|
deleteAgentName = name;
|
|
@@ -2872,7 +3218,7 @@ function parseArgs(argv) {
|
|
|
2872
3218
|
}
|
|
2873
3219
|
if (remaining.length === 0) {
|
|
2874
3220
|
process.stderr.write("Error: --wrap requires a command to run.\n");
|
|
2875
|
-
process.stderr.write("Example: npx multicorn-
|
|
3221
|
+
process.stderr.write("Example: npx multicorn-shield --wrap my-mcp-server\n");
|
|
2876
3222
|
process.exit(1);
|
|
2877
3223
|
}
|
|
2878
3224
|
wrapCommand = remaining[0] ?? "";
|
|
@@ -2925,19 +3271,22 @@ function parseArgs(argv) {
|
|
|
2925
3271
|
function printHelp() {
|
|
2926
3272
|
process.stderr.write(
|
|
2927
3273
|
[
|
|
2928
|
-
"multicorn-
|
|
3274
|
+
"multicorn-shield: MCP permission proxy and Shield setup",
|
|
2929
3275
|
"",
|
|
2930
3276
|
"Usage:",
|
|
2931
|
-
" npx multicorn-
|
|
3277
|
+
" npx multicorn-shield init",
|
|
2932
3278
|
" Interactive setup. Saves API key to ~/.multicorn/config.json.",
|
|
2933
3279
|
"",
|
|
2934
|
-
" npx multicorn-
|
|
3280
|
+
" npx multicorn-shield restore",
|
|
3281
|
+
" Restore MCP servers in claude_desktop_config.json from the Shield extension backup.",
|
|
3282
|
+
"",
|
|
3283
|
+
" npx multicorn-shield agents",
|
|
2935
3284
|
" List configured agents and show which is the default.",
|
|
2936
3285
|
"",
|
|
2937
|
-
" npx multicorn-
|
|
3286
|
+
" npx multicorn-shield delete-agent <name>",
|
|
2938
3287
|
" Remove a saved agent.",
|
|
2939
3288
|
"",
|
|
2940
|
-
" npx multicorn-
|
|
3289
|
+
" npx multicorn-shield --wrap <command> [args...]",
|
|
2941
3290
|
" Start <command> as an MCP server and proxy all tool calls through",
|
|
2942
3291
|
" Shield's permission layer.",
|
|
2943
3292
|
"",
|
|
@@ -2949,14 +3298,22 @@ function printHelp() {
|
|
|
2949
3298
|
" --agent-name <name> Override agent name derived from the wrapped command",
|
|
2950
3299
|
"",
|
|
2951
3300
|
"Examples:",
|
|
2952
|
-
" npx multicorn-
|
|
2953
|
-
" npx multicorn-
|
|
2954
|
-
" npx multicorn-
|
|
3301
|
+
" npx multicorn-shield init",
|
|
3302
|
+
" npx multicorn-shield --wrap npx @modelcontextprotocol/server-filesystem /tmp",
|
|
3303
|
+
" npx multicorn-shield --wrap my-mcp-server --log-level debug",
|
|
2955
3304
|
""
|
|
2956
3305
|
].join("\n")
|
|
2957
3306
|
);
|
|
2958
3307
|
}
|
|
2959
|
-
async function
|
|
3308
|
+
async function runCli() {
|
|
3309
|
+
const first = process.argv[2];
|
|
3310
|
+
if (first === "restore") {
|
|
3311
|
+
await restoreClaudeDesktopMcpFromBackup();
|
|
3312
|
+
process.stderr.write(
|
|
3313
|
+
"Restored MCP server entries from ~/.multicorn/extension-backup.json into Claude Desktop config.\nRestart Claude Desktop to apply changes.\n"
|
|
3314
|
+
);
|
|
3315
|
+
return;
|
|
3316
|
+
}
|
|
2960
3317
|
const cli = parseArgs(process.argv);
|
|
2961
3318
|
const logger = createLogger(cli.logLevel);
|
|
2962
3319
|
if (cli.subcommand === "help") {
|
|
@@ -2971,13 +3328,13 @@ async function main() {
|
|
|
2971
3328
|
const config2 = await loadConfig();
|
|
2972
3329
|
if (config2 === null) {
|
|
2973
3330
|
process.stderr.write(
|
|
2974
|
-
"No config found. Run `npx multicorn-
|
|
3331
|
+
"No config found. Run `npx multicorn-shield init` to set up your API key.\n"
|
|
2975
3332
|
);
|
|
2976
3333
|
process.exit(1);
|
|
2977
3334
|
}
|
|
2978
3335
|
const agents = collectAgentsFromConfig(config2);
|
|
2979
3336
|
if (agents.length === 0) {
|
|
2980
|
-
process.stderr.write("No agents configured. Run `npx multicorn-
|
|
3337
|
+
process.stderr.write("No agents configured. Run `npx multicorn-shield init` to add one.\n");
|
|
2981
3338
|
process.exit(0);
|
|
2982
3339
|
}
|
|
2983
3340
|
const def = config2.defaultAgent;
|
|
@@ -3067,7 +3424,7 @@ async function resolveWrapConfig(cli, logger) {
|
|
|
3067
3424
|
return config;
|
|
3068
3425
|
}
|
|
3069
3426
|
process.stderr.write(
|
|
3070
|
-
"No API key found. Provide one via the --api-key flag, the MULTICORN_API_KEY environment variable, or run `npx multicorn-
|
|
3427
|
+
"No API key found. Provide one via the --api-key flag, the MULTICORN_API_KEY environment variable, or run `npx multicorn-shield init` to set up a config file.\n"
|
|
3071
3428
|
);
|
|
3072
3429
|
process.exit(1);
|
|
3073
3430
|
}
|
|
@@ -3095,14 +3452,32 @@ function deriveAgentName(command) {
|
|
|
3095
3452
|
const base = command.split("/").pop() ?? command;
|
|
3096
3453
|
return base.replace(/\.[cm]?[jt]s$/, "");
|
|
3097
3454
|
}
|
|
3098
|
-
var isDirectRun
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3455
|
+
var isDirectRun;
|
|
3456
|
+
var init_multicorn_shield = __esm({
|
|
3457
|
+
"bin/multicorn-shield.ts"() {
|
|
3458
|
+
init_config();
|
|
3459
|
+
init_proxy();
|
|
3460
|
+
init_logger();
|
|
3461
|
+
init_consent();
|
|
3462
|
+
init_restore();
|
|
3463
|
+
isDirectRun = process.argv[1] !== void 0 && (import.meta.url.endsWith(process.argv[1]) || import.meta.url === `file://${process.argv[1]}` || import.meta.url.endsWith("/multicorn-shield.js") || import.meta.url.endsWith("/multicorn-shield.ts"));
|
|
3464
|
+
if (isDirectRun && process.env["VITEST"] === void 0) {
|
|
3465
|
+
runCli().catch((error) => {
|
|
3466
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3467
|
+
process.stderr.write(`Fatal error: ${message}
|
|
3103
3468
|
`);
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
}
|
|
3469
|
+
process.exit(1);
|
|
3470
|
+
});
|
|
3471
|
+
}
|
|
3472
|
+
}
|
|
3473
|
+
});
|
|
3107
3474
|
|
|
3108
|
-
|
|
3475
|
+
// bin/multicorn-proxy.ts
|
|
3476
|
+
process.stderr.write("Warning: multicorn-proxy is deprecated. Use multicorn-shield instead.\n");
|
|
3477
|
+
var { runCli: runCli2 } = await Promise.resolve().then(() => (init_multicorn_shield(), multicorn_shield_exports));
|
|
3478
|
+
void runCli2().catch((error) => {
|
|
3479
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3480
|
+
process.stderr.write(`Fatal error: ${message}
|
|
3481
|
+
`);
|
|
3482
|
+
process.exit(1);
|
|
3483
|
+
});
|