multicorn-shield 1.2.0 → 1.3.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 +15 -1
- package/dist/multicorn-proxy.js +263 -179
- package/dist/multicorn-shield.js +263 -179
- package/dist/openclaw-hook/handler.js +3 -3
- package/dist/openclaw-plugin/multicorn-shield.js +3 -3
- package/dist/shield-extension.js +14 -19
- package/package.json +2 -1
- package/plugins/multicorn-shield/.claude-plugin/plugin.json +8 -0
- package/plugins/multicorn-shield/hooks/hooks.json +26 -0
- package/plugins/multicorn-shield/hooks/scripts/claude-code-tool-map.cjs +138 -0
- package/plugins/multicorn-shield/hooks/scripts/post-tool-use.cjs +253 -0
- package/plugins/multicorn-shield/hooks/scripts/pre-tool-use.cjs +642 -0
- package/plugins/multicorn-shield/skills/shield-governance/SKILL.md +24 -0
package/dist/multicorn-proxy.js
CHANGED
|
@@ -4,8 +4,9 @@ import { readFile, mkdir, writeFile, copyFile, chmod, unlink } from 'fs/promises
|
|
|
4
4
|
import { dirname, resolve, join, basename, sep } from 'path';
|
|
5
5
|
import { homedir } from 'os';
|
|
6
6
|
import { spawn } from 'child_process';
|
|
7
|
-
import { existsSync, statSync } from 'fs';
|
|
7
|
+
import { readFileSync, existsSync, statSync } from 'fs';
|
|
8
8
|
import { fileURLToPath } from 'url';
|
|
9
|
+
import { createRequire } from 'module';
|
|
9
10
|
import { createInterface } from 'readline';
|
|
10
11
|
import 'stream';
|
|
11
12
|
|
|
@@ -384,6 +385,32 @@ function isExistingDirectory(path) {
|
|
|
384
385
|
return false;
|
|
385
386
|
}
|
|
386
387
|
}
|
|
388
|
+
function jsonValueMentionsMulticornShield(value) {
|
|
389
|
+
if (typeof value === "string") {
|
|
390
|
+
return value.includes("multicorn-shield");
|
|
391
|
+
}
|
|
392
|
+
if (Array.isArray(value)) {
|
|
393
|
+
return value.some(jsonValueMentionsMulticornShield);
|
|
394
|
+
}
|
|
395
|
+
if (value !== null && typeof value === "object") {
|
|
396
|
+
for (const [k, v] of Object.entries(value)) {
|
|
397
|
+
if (k.includes("multicorn-shield")) return true;
|
|
398
|
+
if (jsonValueMentionsMulticornShield(v)) return true;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
return false;
|
|
402
|
+
}
|
|
403
|
+
function claudeInstalledPluginsListsMulticornShield() {
|
|
404
|
+
const path = join(homedir(), ".claude", "plugins", "installed_plugins.json");
|
|
405
|
+
if (!existsSync(path)) return false;
|
|
406
|
+
try {
|
|
407
|
+
const raw = readFileSync(path, "utf8");
|
|
408
|
+
const parsed = JSON.parse(raw);
|
|
409
|
+
return jsonValueMentionsMulticornShield(parsed);
|
|
410
|
+
} catch {
|
|
411
|
+
return false;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
387
414
|
function nativePluginSkippedSaveNote(wizardCommand, productName) {
|
|
388
415
|
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";
|
|
389
416
|
}
|
|
@@ -693,78 +720,48 @@ async function validateApiKey(apiKey, baseUrl) {
|
|
|
693
720
|
};
|
|
694
721
|
}
|
|
695
722
|
}
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
const raw = await readFile(OPENCLAW_CONFIG_PATH, "utf8");
|
|
699
|
-
const obj = JSON.parse(raw);
|
|
700
|
-
const hooks = obj["hooks"];
|
|
701
|
-
const internal = hooks?.["internal"];
|
|
702
|
-
const entries = internal?.["entries"];
|
|
703
|
-
const shield = entries?.["multicorn-shield"];
|
|
704
|
-
const env = shield?.["env"];
|
|
705
|
-
const key = env?.["MULTICORN_API_KEY"];
|
|
706
|
-
return typeof key === "string" && key.length > 0;
|
|
707
|
-
} catch {
|
|
708
|
-
return false;
|
|
709
|
-
}
|
|
723
|
+
function multicornShieldPackageRoot() {
|
|
724
|
+
return join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
710
725
|
}
|
|
711
|
-
function
|
|
726
|
+
function multicornShieldInstallRoot() {
|
|
712
727
|
try {
|
|
713
|
-
|
|
728
|
+
const req = createRequire(import.meta.url);
|
|
729
|
+
return dirname(req.resolve("multicorn-shield/package.json"));
|
|
714
730
|
} catch {
|
|
715
|
-
return
|
|
731
|
+
return multicornShieldPackageRoot();
|
|
716
732
|
}
|
|
717
733
|
}
|
|
718
|
-
function
|
|
719
|
-
return
|
|
734
|
+
function shieldInstalledVersionOlderThan(latest, installed) {
|
|
735
|
+
return latest.localeCompare(installed, void 0, { numeric: true }) > 0;
|
|
720
736
|
}
|
|
721
|
-
async function
|
|
737
|
+
async function warnIfInstalledShieldIsOutdated() {
|
|
722
738
|
try {
|
|
723
|
-
const
|
|
724
|
-
|
|
725
|
-
const
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
const
|
|
731
|
-
|
|
732
|
-
const
|
|
733
|
-
|
|
734
|
-
|
|
739
|
+
const res = await fetch("https://registry.npmjs.org/multicorn-shield/latest");
|
|
740
|
+
if (!res.ok) return;
|
|
741
|
+
const data = await res.json();
|
|
742
|
+
const latest = typeof data.version === "string" ? data.version : "";
|
|
743
|
+
if (latest.length === 0) return;
|
|
744
|
+
let installed = "";
|
|
745
|
+
try {
|
|
746
|
+
const req = createRequire(import.meta.url);
|
|
747
|
+
const pkgPath = req.resolve("multicorn-shield/package.json");
|
|
748
|
+
const raw = readFileSync(pkgPath, "utf8");
|
|
749
|
+
const pkg = JSON.parse(raw);
|
|
750
|
+
installed = typeof pkg.version === "string" ? pkg.version : "";
|
|
751
|
+
} catch {
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
if (installed.length === 0 || !shieldInstalledVersionOlderThan(latest, installed)) {
|
|
755
|
+
return;
|
|
735
756
|
}
|
|
736
|
-
return false;
|
|
737
|
-
} catch (err) {
|
|
738
757
|
process.stderr.write(
|
|
739
|
-
`
|
|
758
|
+
style.yellow("\u26A0") + ` multicorn-shield v${installed} is installed but v${latest} is available. Run npm update multicorn-shield to update.
|
|
759
|
+
|
|
740
760
|
`
|
|
741
761
|
);
|
|
742
|
-
return false;
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
function getWindsurfConfigPath() {
|
|
746
|
-
return join(homedir(), ".codeium", "windsurf", "mcp_config.json");
|
|
747
|
-
}
|
|
748
|
-
async function isWindsurfConnected() {
|
|
749
|
-
try {
|
|
750
|
-
const raw = await readFile(getWindsurfConfigPath(), "utf8");
|
|
751
|
-
const obj = JSON.parse(raw);
|
|
752
|
-
const mcpServers = obj["mcpServers"];
|
|
753
|
-
if (mcpServers === void 0 || typeof mcpServers !== "object") return false;
|
|
754
|
-
for (const entry of Object.values(mcpServers)) {
|
|
755
|
-
if (typeof entry !== "object" || entry === null) continue;
|
|
756
|
-
const rec = entry;
|
|
757
|
-
const url = rec["serverUrl"];
|
|
758
|
-
if (typeof url === "string" && url.includes("multicorn")) return true;
|
|
759
|
-
}
|
|
760
|
-
return false;
|
|
761
762
|
} catch {
|
|
762
|
-
return false;
|
|
763
763
|
}
|
|
764
764
|
}
|
|
765
|
-
function multicornShieldPackageRoot() {
|
|
766
|
-
return join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
767
|
-
}
|
|
768
765
|
function getWindsurfHooksInstallDir() {
|
|
769
766
|
return join(homedir(), ".multicorn", "windsurf-hooks");
|
|
770
767
|
}
|
|
@@ -994,6 +991,140 @@ function geminiStripMulticornHookEntries(hooks) {
|
|
|
994
991
|
out["AfterTool"] = geminiStripMatcherGroups(out["AfterTool"]);
|
|
995
992
|
return out;
|
|
996
993
|
}
|
|
994
|
+
function getClaudeCodeUserSettingsPath() {
|
|
995
|
+
return join(homedir(), ".claude", "settings.json");
|
|
996
|
+
}
|
|
997
|
+
function commandLooksLikeMulticornClaudePre(cmd) {
|
|
998
|
+
return typeof cmd === "string" && cmd.includes("pre-tool-use.cjs") && cmd.includes("multicorn-shield");
|
|
999
|
+
}
|
|
1000
|
+
function commandLooksLikeMulticornClaudePost(cmd) {
|
|
1001
|
+
return typeof cmd === "string" && cmd.includes("post-tool-use.cjs") && cmd.includes("multicorn-shield");
|
|
1002
|
+
}
|
|
1003
|
+
function claudeSettingsMatcherGroupReferencesShield(group, kind) {
|
|
1004
|
+
const inner = group["hooks"];
|
|
1005
|
+
if (!Array.isArray(inner)) return false;
|
|
1006
|
+
const pred = kind === "pre" ? commandLooksLikeMulticornClaudePre : commandLooksLikeMulticornClaudePost;
|
|
1007
|
+
for (const h of inner) {
|
|
1008
|
+
if (typeof h !== "object" || h === null) continue;
|
|
1009
|
+
const rec = h;
|
|
1010
|
+
if (pred(rec["command"])) return true;
|
|
1011
|
+
}
|
|
1012
|
+
return false;
|
|
1013
|
+
}
|
|
1014
|
+
function claudeHooksHaveShieldEntries(hooks) {
|
|
1015
|
+
for (const key of ["PreToolUse", "PostToolUse"]) {
|
|
1016
|
+
const arr = hooks[key];
|
|
1017
|
+
if (!Array.isArray(arr)) continue;
|
|
1018
|
+
const kind = key === "PreToolUse" ? "pre" : "post";
|
|
1019
|
+
for (const g of arr) {
|
|
1020
|
+
if (typeof g === "object" && g !== null) {
|
|
1021
|
+
if (claudeSettingsMatcherGroupReferencesShield(g, kind)) {
|
|
1022
|
+
return true;
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
return false;
|
|
1028
|
+
}
|
|
1029
|
+
function stripClaudeShieldHookGroups(arr, kind) {
|
|
1030
|
+
return arr.filter((g) => {
|
|
1031
|
+
if (typeof g !== "object" || g === null) return true;
|
|
1032
|
+
return !claudeSettingsMatcherGroupReferencesShield(g, kind);
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
async function installClaudeCodeUserSettingsHooks(ask) {
|
|
1036
|
+
const root = multicornShieldInstallRoot();
|
|
1037
|
+
const prePath = join(root, "plugins", "multicorn-shield", "hooks", "scripts", "pre-tool-use.cjs");
|
|
1038
|
+
const postPath = join(
|
|
1039
|
+
root,
|
|
1040
|
+
"plugins",
|
|
1041
|
+
"multicorn-shield",
|
|
1042
|
+
"hooks",
|
|
1043
|
+
"scripts",
|
|
1044
|
+
"post-tool-use.cjs"
|
|
1045
|
+
);
|
|
1046
|
+
if (!existsSync(prePath) || !existsSync(postPath)) {
|
|
1047
|
+
process.stderr.write(
|
|
1048
|
+
style.red(
|
|
1049
|
+
"Could not find Shield Claude Code hook scripts next to the multicorn-shield package.\n"
|
|
1050
|
+
)
|
|
1051
|
+
);
|
|
1052
|
+
process.stderr.write(style.dim(` Expected: ${prePath}`) + "\n");
|
|
1053
|
+
return false;
|
|
1054
|
+
}
|
|
1055
|
+
const settingsPath = getClaudeCodeUserSettingsPath();
|
|
1056
|
+
await mkdir(dirname(settingsPath), { recursive: true });
|
|
1057
|
+
let existing = {};
|
|
1058
|
+
try {
|
|
1059
|
+
const rawText = await readFile(settingsPath, "utf8");
|
|
1060
|
+
const parsed = JSON.parse(rawText);
|
|
1061
|
+
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
1062
|
+
existing = parsed;
|
|
1063
|
+
}
|
|
1064
|
+
} catch (err) {
|
|
1065
|
+
if (isErrnoException(err) && err.code === "ENOENT") {
|
|
1066
|
+
existing = {};
|
|
1067
|
+
} else {
|
|
1068
|
+
process.stderr.write(
|
|
1069
|
+
style.yellow("\u26A0") + ` Could not parse ${settingsPath}. Fix or remove the file, then run init again.
|
|
1070
|
+
`
|
|
1071
|
+
);
|
|
1072
|
+
return false;
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
const hooksRaw = existing["hooks"];
|
|
1076
|
+
const hooksObj = typeof hooksRaw === "object" && hooksRaw !== null && !Array.isArray(hooksRaw) ? { ...hooksRaw } : {};
|
|
1077
|
+
if (claudeHooksHaveShieldEntries(hooksObj)) {
|
|
1078
|
+
const answer = await ask(
|
|
1079
|
+
"Existing Multicorn Shield hooks were found in ~/.claude/settings.json. Overwrite? (Y/n) "
|
|
1080
|
+
);
|
|
1081
|
+
if (answer.trim().toLowerCase() === "n") {
|
|
1082
|
+
return false;
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
const preCmd = `node ${JSON.stringify(prePath)}`;
|
|
1086
|
+
const postCmd = `node ${JSON.stringify(postPath)}`;
|
|
1087
|
+
const preArr = stripClaudeShieldHookGroups(
|
|
1088
|
+
Array.isArray(hooksObj["PreToolUse"]) ? [...hooksObj["PreToolUse"]] : [],
|
|
1089
|
+
"pre"
|
|
1090
|
+
);
|
|
1091
|
+
const postArr = stripClaudeShieldHookGroups(
|
|
1092
|
+
Array.isArray(hooksObj["PostToolUse"]) ? [...hooksObj["PostToolUse"]] : [],
|
|
1093
|
+
"post"
|
|
1094
|
+
);
|
|
1095
|
+
preArr.push({
|
|
1096
|
+
matcher: "*",
|
|
1097
|
+
hooks: [
|
|
1098
|
+
{
|
|
1099
|
+
type: "command",
|
|
1100
|
+
name: "multicorn-shield-pre",
|
|
1101
|
+
command: preCmd,
|
|
1102
|
+
timeout: 600
|
|
1103
|
+
}
|
|
1104
|
+
]
|
|
1105
|
+
});
|
|
1106
|
+
postArr.push({
|
|
1107
|
+
matcher: "*",
|
|
1108
|
+
hooks: [
|
|
1109
|
+
{
|
|
1110
|
+
type: "command",
|
|
1111
|
+
name: "multicorn-shield-post",
|
|
1112
|
+
command: postCmd,
|
|
1113
|
+
timeout: 120
|
|
1114
|
+
}
|
|
1115
|
+
]
|
|
1116
|
+
});
|
|
1117
|
+
hooksObj["PreToolUse"] = preArr;
|
|
1118
|
+
hooksObj["PostToolUse"] = postArr;
|
|
1119
|
+
const out = { ...existing, hooks: hooksObj };
|
|
1120
|
+
const serialized = JSON.stringify(out, null, 2) + "\n";
|
|
1121
|
+
await writeFile(settingsPath, serialized, "utf8");
|
|
1122
|
+
process.stderr.write(
|
|
1123
|
+
"\n" + style.dim("Wrote ") + style.cyan(settingsPath) + style.dim(":") + "\n"
|
|
1124
|
+
);
|
|
1125
|
+
process.stderr.write(style.dim(JSON.stringify({ hooks: hooksObj }, null, 2)) + "\n");
|
|
1126
|
+
return true;
|
|
1127
|
+
}
|
|
997
1128
|
async function installGeminiCliNativeHooks(ask) {
|
|
998
1129
|
const root = multicornShieldPackageRoot();
|
|
999
1130
|
const srcBefore = join(root, "plugins", "gemini-cli", "hooks", "scripts", "before-tool.cjs");
|
|
@@ -1143,45 +1274,17 @@ async function promptHostedProxyInstallPrereq(ask, platformLabel, prereqUrl) {
|
|
|
1143
1274
|
const answer = await ask("Ready to continue? (Y/n) ");
|
|
1144
1275
|
return answer.trim().toLowerCase() !== "n";
|
|
1145
1276
|
}
|
|
1146
|
-
function isPlatformDetectedForMenu(slug) {
|
|
1147
|
-
switch (slug) {
|
|
1148
|
-
case "openclaw":
|
|
1149
|
-
return isOpenClawConnected();
|
|
1150
|
-
case "claude-code":
|
|
1151
|
-
return Promise.resolve(isClaudeCodeConnected());
|
|
1152
|
-
case "cursor":
|
|
1153
|
-
return isCursorConnected();
|
|
1154
|
-
case "windsurf":
|
|
1155
|
-
return isWindsurfConnected();
|
|
1156
|
-
default:
|
|
1157
|
-
return Promise.resolve(false);
|
|
1158
|
-
}
|
|
1159
|
-
}
|
|
1160
1277
|
async function promptPlatformSelection(ask) {
|
|
1161
1278
|
process.stderr.write(
|
|
1162
1279
|
"\n" + style.bold(style.violet("Which platform are you connecting?")) + "\n\n"
|
|
1163
1280
|
);
|
|
1164
|
-
const detectionSlugs = INIT_WIZARD_PLATFORM_REGISTRY.filter((e) => e.detectable).map(
|
|
1165
|
-
(e) => e.slug
|
|
1166
|
-
);
|
|
1167
|
-
const connectedFlags = await Promise.all(
|
|
1168
|
-
detectionSlugs.map((slug) => isPlatformDetectedForMenu(slug))
|
|
1169
|
-
);
|
|
1170
|
-
function markerFor(platform) {
|
|
1171
|
-
const idx = detectionSlugs.indexOf(platform);
|
|
1172
|
-
if (idx === -1) return "";
|
|
1173
|
-
if (!connectedFlags[idx]) return "";
|
|
1174
|
-
return " " + style.dim("\u25CF detected locally");
|
|
1175
|
-
}
|
|
1176
1281
|
let optionNum = 1;
|
|
1177
1282
|
for (const section of INIT_WIZARD_MENU_SECTIONS) {
|
|
1178
1283
|
process.stderr.write(" " + style.dim(section.title) + "\n");
|
|
1179
1284
|
for (const item of section.items) {
|
|
1180
1285
|
const indent = optionNum >= 10 ? " " : " ";
|
|
1181
|
-
process.stderr.write(
|
|
1182
|
-
|
|
1183
|
-
`
|
|
1184
|
-
);
|
|
1286
|
+
process.stderr.write(`${indent}${style.violet(String(optionNum))}. ${item.label}
|
|
1287
|
+
`);
|
|
1185
1288
|
optionNum++;
|
|
1186
1289
|
}
|
|
1187
1290
|
}
|
|
@@ -1643,6 +1746,7 @@ async function runInit(explicitBaseUrl) {
|
|
|
1643
1746
|
spinner.stop(true, "Key validated");
|
|
1644
1747
|
apiKey = key;
|
|
1645
1748
|
}
|
|
1749
|
+
await warnIfInstalledShieldIsOutdated();
|
|
1646
1750
|
const configuredAgents = [];
|
|
1647
1751
|
let currentAgents = collectAgentsFromConfig(existing);
|
|
1648
1752
|
let lastConfig = {
|
|
@@ -1714,63 +1818,41 @@ async function runInit(explicitBaseUrl) {
|
|
|
1714
1818
|
) + "\n"
|
|
1715
1819
|
);
|
|
1716
1820
|
if (agentsForPlatform.length > 0) {
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
);
|
|
1720
|
-
if (exactForWorkspace !== void 0) {
|
|
1721
|
-
process.stderr.write(
|
|
1722
|
-
`
|
|
1723
|
-
This workspace already has a ${selectedLabel} agent registered (${style.cyan(
|
|
1724
|
-
exactForWorkspace.name
|
|
1725
|
-
)}).
|
|
1726
|
-
`
|
|
1727
|
-
);
|
|
1728
|
-
process.stderr.write(
|
|
1729
|
-
style.dim(
|
|
1730
|
-
"Replace updates this directory's saved agent. (n) returns to platform selection \u2014 the wizard keeps running."
|
|
1731
|
-
) + "\n"
|
|
1732
|
-
);
|
|
1733
|
-
const replace = await ask("Replace it? (Y/n) ");
|
|
1734
|
-
if (replace.trim().toLowerCase() === "n") {
|
|
1735
|
-
process.stderr.write(style.dim("Skipping. Returning to platform selection.") + "\n");
|
|
1736
|
-
continue;
|
|
1737
|
-
}
|
|
1738
|
-
removeAgentNameBeforeSave = exactForWorkspace.name;
|
|
1739
|
-
} else {
|
|
1740
|
-
process.stderr.write(
|
|
1741
|
-
`
|
|
1821
|
+
process.stderr.write(
|
|
1822
|
+
`
|
|
1742
1823
|
You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLabel}:
|
|
1743
1824
|
`
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1825
|
+
);
|
|
1826
|
+
for (const a of agentsForPlatform) {
|
|
1827
|
+
const isThisWorkspace = typeof a.workspacePath === "string" && a.workspacePath.length > 0 && resolve(a.workspacePath) === initWorkspacePath;
|
|
1828
|
+
const wsHint = typeof a.workspacePath === "string" && a.workspacePath.length > 0 ? ` ${style.dim(a.workspacePath)}` : "";
|
|
1829
|
+
const marker = isThisWorkspace ? ` ${style.yellow("(this workspace)")}` : "";
|
|
1830
|
+
process.stderr.write(` ${style.dim("\u2022")} ${style.cyan(a.name)}${wsHint}${marker}
|
|
1748
1831
|
`);
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1832
|
+
}
|
|
1833
|
+
process.stderr.write("\n" + style.bold("What would you like to do?") + "\n");
|
|
1834
|
+
const actionIdx = await arrowSelect(
|
|
1835
|
+
[
|
|
1836
|
+
"Add a new agent alongside these",
|
|
1837
|
+
"Replace an existing agent",
|
|
1838
|
+
"Skip - choose a different platform"
|
|
1839
|
+
],
|
|
1840
|
+
ask,
|
|
1841
|
+
"Action"
|
|
1842
|
+
);
|
|
1843
|
+
if (actionIdx === 2) {
|
|
1844
|
+
continue;
|
|
1845
|
+
}
|
|
1846
|
+
if (actionIdx === 1) {
|
|
1847
|
+
process.stderr.write("\n" + style.bold("Which agent to replace?") + "\n");
|
|
1848
|
+
const replaceIdx = await arrowSelect(
|
|
1849
|
+
agentsForPlatform.map((a) => a.name),
|
|
1757
1850
|
ask,
|
|
1758
|
-
"
|
|
1851
|
+
"Agent"
|
|
1759
1852
|
);
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
if (actionIdx === 1) {
|
|
1764
|
-
process.stderr.write("\n" + style.bold("Which agent to replace?") + "\n");
|
|
1765
|
-
const replaceIdx = await arrowSelect(
|
|
1766
|
-
agentsForPlatform.map((a) => a.name),
|
|
1767
|
-
ask,
|
|
1768
|
-
"Agent"
|
|
1769
|
-
);
|
|
1770
|
-
const victim = agentsForPlatform[replaceIdx];
|
|
1771
|
-
if (victim !== void 0) {
|
|
1772
|
-
removeAgentNameBeforeSave = victim.name;
|
|
1773
|
-
}
|
|
1853
|
+
const victim = agentsForPlatform[replaceIdx];
|
|
1854
|
+
if (victim !== void 0) {
|
|
1855
|
+
removeAgentNameBeforeSave = victim.name;
|
|
1774
1856
|
}
|
|
1775
1857
|
}
|
|
1776
1858
|
}
|
|
@@ -1872,16 +1954,24 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
1872
1954
|
});
|
|
1873
1955
|
setupSucceeded = true;
|
|
1874
1956
|
} else if (selectedPlatform === "claude-code") {
|
|
1875
|
-
process.stderr.write("\nTo connect Claude Code to Shield:\n\n");
|
|
1876
|
-
process.stderr.write(
|
|
1877
|
-
" " + style.bold("Step 1") + " - Add the Multicorn marketplace:\n " + style.cyan("claude plugin marketplace add Multicorn-AI/multicorn-shield") + "\n\n"
|
|
1878
|
-
);
|
|
1879
1957
|
process.stderr.write(
|
|
1880
|
-
"
|
|
1958
|
+
"\n" + style.dim("Configuring Shield hooks in Claude Code user settings...") + "\n"
|
|
1881
1959
|
);
|
|
1960
|
+
const hooksOk = await installClaudeCodeUserSettingsHooks(ask);
|
|
1961
|
+
if (!hooksOk) {
|
|
1962
|
+
process.stderr.write(style.dim("Skipped Claude Code hook installation.\n"));
|
|
1963
|
+
continue;
|
|
1964
|
+
}
|
|
1882
1965
|
process.stderr.write(
|
|
1883
|
-
style.
|
|
1966
|
+
style.green("\u2713") + " Shield hooks added to " + style.cyan("~/.claude/settings.json") + "\n"
|
|
1884
1967
|
);
|
|
1968
|
+
if (claudeInstalledPluginsListsMulticornShield()) {
|
|
1969
|
+
process.stderr.write(
|
|
1970
|
+
style.dim(
|
|
1971
|
+
"Note: You have the multicorn-shield Claude Code plugin installed. The plugin is no longer needed - hooks are now written directly to settings.json. You can uninstall it with: "
|
|
1972
|
+
) + style.cyan("claude plugin uninstall multicorn-shield@multicorn-shield") + "\n"
|
|
1973
|
+
);
|
|
1974
|
+
}
|
|
1885
1975
|
configuredAgents.push({
|
|
1886
1976
|
selection,
|
|
1887
1977
|
platform: selectedPlatform,
|
|
@@ -2236,42 +2326,42 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
2236
2326
|
const blocks = [];
|
|
2237
2327
|
if (configuredPlatforms.has("openclaw")) {
|
|
2238
2328
|
blocks.push(
|
|
2239
|
-
"\n" + style.bold("
|
|
2329
|
+
"\n" + style.bold("OpenClaw") + "\n \u2192 Restart your gateway: " + style.cyan("openclaw gateway restart") + "\n \u2192 Start a session: " + style.cyan("openclaw tui") + "\n"
|
|
2240
2330
|
);
|
|
2241
2331
|
}
|
|
2242
2332
|
if (configuredPlatforms.has("claude-code")) {
|
|
2243
2333
|
blocks.push(
|
|
2244
|
-
"\n" + style.bold("
|
|
2334
|
+
"\n" + style.bold("Claude Code") + "\n \u2192 Start Claude Code: " + style.cyan("claude") + "\n \u2192 Shield will intercept tool calls automatically.\n"
|
|
2245
2335
|
);
|
|
2246
2336
|
}
|
|
2247
2337
|
if (configuredPlatforms.has("claude-desktop")) {
|
|
2248
2338
|
blocks.push(
|
|
2249
|
-
"\n" + style.bold("
|
|
2339
|
+
"\n" + style.bold("Claude Desktop") + "\n \u2192 Restart Claude Desktop to pick up config changes\n"
|
|
2250
2340
|
);
|
|
2251
2341
|
}
|
|
2252
2342
|
if (configuredPlatforms.has("cursor")) {
|
|
2253
2343
|
blocks.push(
|
|
2254
|
-
"\n" + style.bold("
|
|
2344
|
+
"\n" + style.bold("Cursor") + "\n \u2192 If needed, download Cursor from " + style.cyan("https://cursor.com/downloads") + "\n \u2192 Restart Cursor so it loads the MCP server\n \u2192 Ask the agent to use your Shield MCP tools by short name\n"
|
|
2255
2345
|
);
|
|
2256
2346
|
}
|
|
2257
2347
|
if (configuredPlatforms.has("kilo-code")) {
|
|
2258
2348
|
blocks.push(
|
|
2259
|
-
"\n" + style.bold("
|
|
2349
|
+
"\n" + style.bold("Kilo Code") + "\n \u2192 Restart the editor or reload the window if the MCP server does not appear\n \u2192 Run your next task in Kilo Code so it picks up Shield\n"
|
|
2260
2350
|
);
|
|
2261
2351
|
}
|
|
2262
2352
|
if (configuredPlatforms.has("github-copilot")) {
|
|
2263
2353
|
blocks.push(
|
|
2264
|
-
"\n" + style.bold("GitHub Copilot
|
|
2354
|
+
"\n" + style.bold("GitHub Copilot") + "\n \u2192 Reload the editor window if the MCP server does not appear\n \u2192 Use Copilot Agent mode and verify the MCP server connects\n"
|
|
2265
2355
|
);
|
|
2266
2356
|
}
|
|
2267
2357
|
if (configuredPlatforms.has("continue-dev")) {
|
|
2268
2358
|
blocks.push(
|
|
2269
|
-
"\n" + style.bold("Continue
|
|
2359
|
+
"\n" + style.bold("Continue") + "\n \u2192 If needed, install Continue from " + style.cyan("https://docs.continue.dev/ide-extensions/install") + "\n \u2192 Reload VS Code and open Continue agent mode\n"
|
|
2270
2360
|
);
|
|
2271
2361
|
}
|
|
2272
2362
|
if (configuredPlatforms.has("goose")) {
|
|
2273
2363
|
blocks.push(
|
|
2274
|
-
"\n" + style.bold("Goose
|
|
2364
|
+
"\n" + style.bold("Goose") + "\n \u2192 Start a new Goose session after updating config\n"
|
|
2275
2365
|
);
|
|
2276
2366
|
}
|
|
2277
2367
|
const windsurfNativeConfigured = configuredAgents.some(
|
|
@@ -2282,12 +2372,12 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
2282
2372
|
);
|
|
2283
2373
|
if (windsurfNativeConfigured) {
|
|
2284
2374
|
blocks.push(
|
|
2285
|
-
"\n" + style.bold("
|
|
2375
|
+
"\n" + style.bold("Windsurf (native)") + "\n \u2192 Restart Windsurf (quit fully, then reopen)\n"
|
|
2286
2376
|
);
|
|
2287
2377
|
}
|
|
2288
2378
|
if (windsurfHostedConfigured) {
|
|
2289
2379
|
blocks.push(
|
|
2290
|
-
"\n" + style.bold("
|
|
2380
|
+
"\n" + style.bold("Windsurf (hosted)") + "\n \u2192 If needed, install from " + style.cyan("https://windsurf.com/download") + "\n \u2192 Restart Windsurf so it loads the MCP server\n"
|
|
2291
2381
|
);
|
|
2292
2382
|
}
|
|
2293
2383
|
const clineNativeConfigured = configuredAgents.some(
|
|
@@ -2298,12 +2388,12 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
2298
2388
|
);
|
|
2299
2389
|
if (clineNativeConfigured) {
|
|
2300
2390
|
blocks.push(
|
|
2301
|
-
"\n" + style.bold("
|
|
2391
|
+
"\n" + style.bold("Cline (native)") + "\n \u2192 Enable Hooks in Cline settings (Advanced), then reload the VS Code window\n \u2192 Trigger a tool call to verify Shield is intercepting\n"
|
|
2302
2392
|
);
|
|
2303
2393
|
}
|
|
2304
2394
|
if (clineHostedConfigured) {
|
|
2305
2395
|
blocks.push(
|
|
2306
|
-
"\n" + style.bold("
|
|
2396
|
+
"\n" + style.bold("Cline (hosted)") + "\n \u2192 Restart Cline or reload the VS Code window\n"
|
|
2307
2397
|
);
|
|
2308
2398
|
}
|
|
2309
2399
|
const geminiCliNativeConfigured = configuredAgents.some(
|
|
@@ -2314,12 +2404,12 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
|
|
|
2314
2404
|
);
|
|
2315
2405
|
if (geminiCliNativeConfigured) {
|
|
2316
2406
|
blocks.push(
|
|
2317
|
-
"\n" + style.bold("Gemini CLI native
|
|
2407
|
+
"\n" + style.bold("Gemini CLI (native)") + "\n \u2192 Restart Gemini CLI to activate Shield governance\n"
|
|
2318
2408
|
);
|
|
2319
2409
|
}
|
|
2320
2410
|
if (geminiCliHostedConfigured) {
|
|
2321
2411
|
blocks.push(
|
|
2322
|
-
"\n" + style.bold("
|
|
2412
|
+
"\n" + style.bold("Gemini CLI (hosted)") + "\n \u2192 Restart Gemini CLI, then run " + style.cyan("/mcp") + " to verify the server\n"
|
|
2323
2413
|
);
|
|
2324
2414
|
}
|
|
2325
2415
|
if (blocks.length > 0) {
|
|
@@ -2362,54 +2452,48 @@ var init_config = __esm({
|
|
|
2362
2452
|
ANSI_PATTERN = new RegExp(String.fromCharCode(27) + "\\[[0-9;]*[a-zA-Z]", "g");
|
|
2363
2453
|
OPENCLAW_MIN_VERSION = "2026.2.26";
|
|
2364
2454
|
INIT_WIZARD_PLATFORM_REGISTRY = [
|
|
2365
|
-
{ slug: "openclaw", displayName: "OpenClaw", section: "native"
|
|
2366
|
-
{ slug: "claude-code", displayName: "Claude Code", section: "native"
|
|
2367
|
-
{ slug: "windsurf", displayName: "Windsurf", section: "native"
|
|
2368
|
-
{ slug: "cline", displayName: "Cline", section: "native"
|
|
2369
|
-
{ slug: "gemini-cli", displayName: "Gemini CLI", section: "native"
|
|
2455
|
+
{ slug: "openclaw", displayName: "OpenClaw", section: "native" },
|
|
2456
|
+
{ slug: "claude-code", displayName: "Claude Code", section: "native" },
|
|
2457
|
+
{ slug: "windsurf", displayName: "Windsurf", section: "native" },
|
|
2458
|
+
{ slug: "cline", displayName: "Cline", section: "native" },
|
|
2459
|
+
{ slug: "gemini-cli", displayName: "Gemini CLI", section: "native" },
|
|
2370
2460
|
{
|
|
2371
2461
|
slug: "cursor",
|
|
2372
2462
|
displayName: "Cursor",
|
|
2373
2463
|
section: "hosted",
|
|
2374
|
-
prereqUrl: "https://www.cursor.com/downloads"
|
|
2375
|
-
detectable: true
|
|
2464
|
+
prereqUrl: "https://www.cursor.com/downloads"
|
|
2376
2465
|
},
|
|
2377
2466
|
{
|
|
2378
2467
|
slug: "claude-desktop",
|
|
2379
2468
|
displayName: "Claude Desktop",
|
|
2380
2469
|
section: "hosted",
|
|
2381
|
-
prereqUrl: "https://claude.ai/download"
|
|
2382
|
-
detectable: false
|
|
2470
|
+
prereqUrl: "https://claude.ai/download"
|
|
2383
2471
|
},
|
|
2384
2472
|
{
|
|
2385
2473
|
slug: "github-copilot",
|
|
2386
2474
|
displayName: "GitHub Copilot",
|
|
2387
2475
|
section: "hosted",
|
|
2388
|
-
prereqUrl: "https://docs.github.com/en/copilot/get-started"
|
|
2389
|
-
detectable: false
|
|
2476
|
+
prereqUrl: "https://docs.github.com/en/copilot/get-started"
|
|
2390
2477
|
},
|
|
2391
2478
|
{
|
|
2392
2479
|
slug: "kilo-code",
|
|
2393
2480
|
displayName: "Kilo Code",
|
|
2394
2481
|
section: "hosted",
|
|
2395
|
-
prereqUrl: "https://kilocode.ai/docs/getting-started"
|
|
2396
|
-
detectable: false
|
|
2482
|
+
prereqUrl: "https://kilocode.ai/docs/getting-started"
|
|
2397
2483
|
},
|
|
2398
2484
|
{
|
|
2399
2485
|
slug: "continue-dev",
|
|
2400
2486
|
displayName: "Continue",
|
|
2401
2487
|
section: "hosted",
|
|
2402
|
-
prereqUrl: "https://docs.continue.dev/ide-extensions/install"
|
|
2403
|
-
detectable: false
|
|
2488
|
+
prereqUrl: "https://docs.continue.dev/ide-extensions/install"
|
|
2404
2489
|
},
|
|
2405
2490
|
{
|
|
2406
2491
|
slug: "goose",
|
|
2407
2492
|
displayName: "Goose",
|
|
2408
2493
|
section: "hosted",
|
|
2409
|
-
prereqUrl: "https://goose-docs.ai/docs/quickstart/"
|
|
2410
|
-
detectable: false
|
|
2494
|
+
prereqUrl: "https://goose-docs.ai/docs/quickstart/"
|
|
2411
2495
|
},
|
|
2412
|
-
{ slug: "other-mcp", displayName: "Local MCP / Other", section: "hosted"
|
|
2496
|
+
{ slug: "other-mcp", displayName: "Local MCP / Other", section: "hosted" }
|
|
2413
2497
|
];
|
|
2414
2498
|
INIT_WIZARD_MENU_SECTIONS = (() => {
|
|
2415
2499
|
const itemsFor = (section) => INIT_WIZARD_PLATFORM_REGISTRY.filter((e) => e.section === section).map((e) => ({
|