multicorn-shield 1.2.0 → 1.3.1

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.
@@ -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
- async function isOpenClawConnected() {
697
- try {
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 isClaudeCodeConnected() {
726
+ function multicornShieldInstallRoot() {
712
727
  try {
713
- return existsSync(join(homedir(), ".claude", "plugins", "cache", "multicorn-shield"));
728
+ const req = createRequire(import.meta.url);
729
+ return dirname(req.resolve("multicorn-shield/package.json"));
714
730
  } catch {
715
- return false;
731
+ return multicornShieldPackageRoot();
716
732
  }
717
733
  }
718
- function getCursorConfigPath() {
719
- return join(homedir(), ".cursor", "mcp.json");
734
+ function shieldInstalledVersionOlderThan(latest, installed) {
735
+ return latest.localeCompare(installed, void 0, { numeric: true }) > 0;
720
736
  }
721
- async function isCursorConnected() {
737
+ async function warnIfInstalledShieldIsOutdated() {
722
738
  try {
723
- const raw = await readFile(getCursorConfigPath(), "utf8");
724
- const obj = JSON.parse(raw);
725
- const mcpServers = obj["mcpServers"];
726
- if (mcpServers === void 0 || typeof mcpServers !== "object") return false;
727
- for (const entry of Object.values(mcpServers)) {
728
- if (typeof entry !== "object" || entry === null) continue;
729
- const rec = entry;
730
- const url = rec["url"];
731
- if (typeof url === "string" && url.includes("multicorn")) return true;
732
- const args = rec["args"];
733
- if (Array.isArray(args) && (args.includes("multicorn-shield") || args.includes("multicorn-proxy")))
734
- return true;
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
- `Warning: could not check Cursor connection status: ${err instanceof Error ? err.message : String(err)}
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
- `${indent}${style.violet(String(optionNum))}. ${item.label}${markerFor(item.platform)}
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
- const exactForWorkspace = agentsForPlatform.find(
1718
- (a) => typeof a.workspacePath === "string" && a.workspacePath.length > 0 && resolve(a.workspacePath) === initWorkspacePath
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
- for (const a of agentsForPlatform) {
1746
- const wsHint = typeof a.workspacePath === "string" && a.workspacePath.length > 0 ? ` ${style.dim(a.workspacePath)}` : "";
1747
- process.stderr.write(` ${style.dim("\u2022")} ${style.cyan(a.name)}${wsHint}
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
- process.stderr.write("\n" + style.bold("What would you like to do?") + "\n");
1751
- const actionIdx = await arrowSelect(
1752
- [
1753
- "Add a new agent alongside these",
1754
- "Replace an existing agent",
1755
- "Skip \u2014 choose a different platform"
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
- "Action"
1851
+ "Agent"
1759
1852
  );
1760
- if (actionIdx === 2) {
1761
- continue;
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
- " " + style.bold("Step 2") + " - Install the plugin:\n " + style.cyan("claude plugin install multicorn-shield@multicorn-shield") + "\n\n"
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.dim("Requires Claude Code to be installed. Get it at https://code.claude.com") + "\n"
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("To complete your OpenClaw setup:") + "\n \u2192 Restart your gateway: " + style.cyan("openclaw gateway restart") + "\n \u2192 Start a session: " + style.cyan("openclaw tui") + "\n"
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("To complete your Claude Code setup:") + "\n \u2192 Add marketplace: " + style.cyan("claude plugin marketplace add Multicorn-AI/multicorn-shield") + "\n \u2192 Install plugin: " + style.cyan("claude plugin install multicorn-shield@multicorn-shield") + "\n"
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("To complete your Claude Desktop setup:") + "\n \u2192 Restart Claude Desktop to pick up config changes\n"
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("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"
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("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"
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 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"
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 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"
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 MCP extension:") + "\n 1. Edit " + style.cyan("~/.config/goose/config.yaml") + " (or use goose configure)\n 2. Restart Goose CLI or Desktop\n"
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("To complete native Windsurf (Shield) setup:") + "\n 1. Hook scripts: " + style.cyan(getWindsurfHooksInstallDir()) + "\n 2. Hooks config: " + style.cyan(getWindsurfCascadeHooksJsonPath()) + "\n 3. Restart Windsurf (quit fully, then reopen)\n"
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("To complete your Windsurf hosted-proxy setup:") + "\n 1. If you don't have Windsurf yet, download it from " + style.cyan("https://windsurf.com/download") + "\n 2. Open " + style.cyan("~/.codeium/windsurf/mcp_config.json") + " and paste the config snippet shown above\n 3. Restart Windsurf (or launch it for the first time) to load the new MCP server\n"
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("To complete native Cline (Shield) setup:") + "\n 1. Enable Hooks in Cline: open VS Code, click the Cline sidebar icon, click the gear icon,\n scroll down to the Advanced section, and toggle Hooks on.\n 2. Reload the VS Code window (Cmd+Shift+P > Reload Window)\n 3. Trigger any tool call to verify Shield is intercepting\n"
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("To complete your Cline hosted-proxy setup:") + "\n 1. If you don't have Cline yet, install it from the VS Code marketplace\n 2. Open your Cline MCP settings file and paste the config snippet shown above\n 3. Restart Cline or reload the VS Code window\n"
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 hooks:") + "\n Your Gemini CLI hooks are installed. Restart Gemini CLI to activate Shield governance.\n"
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("To complete your Gemini CLI setup:") + "\n 1. Open " + style.cyan("~/.gemini/settings.json") + "\n 2. Paste the config snippet shown above\n 3. Restart Gemini CLI, then run /mcp to verify\n"
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", detectable: true },
2366
- { slug: "claude-code", displayName: "Claude Code", section: "native", detectable: true },
2367
- { slug: "windsurf", displayName: "Windsurf", section: "native", detectable: true },
2368
- { slug: "cline", displayName: "Cline", section: "native", detectable: false },
2369
- { slug: "gemini-cli", displayName: "Gemini CLI", section: "native", detectable: false },
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", detectable: false }
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) => ({