multicorn-shield 1.8.0 → 1.9.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.
package/CHANGELOG.md CHANGED
@@ -9,6 +9,40 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  - Bump `version` in `package.json` before publishing to npm.
11
11
 
12
+ ## [1.9.1] - 2026-05-12
13
+
14
+ ### Fixed
15
+
16
+ - Include `plugins/codex-cli` in published npm package (missing from `files` in package.json)
17
+
18
+ ## [1.9.0] - 2026-05-12
19
+
20
+ ### Added
21
+
22
+ - OpenAI Codex CLI as a supported platform (native hooks + hosted proxy)
23
+ - Codex CLI hook scripts (PreToolUse, PostToolUse) for terminal command interception
24
+ - Codex CLI agent resolution and tool name mapping
25
+ - CLI wizard support for Codex CLI: native plugin install and hosted proxy TOML snippet
26
+ - `plugins/codex-cli/README.md` documenting hook script build and test workflow
27
+
28
+ ### Fixed
29
+
30
+ - Codex config.toml feature flag updated from deprecated `codex_hooks` to `hooks`
31
+ - Config.toml migration: init now detects and replaces deprecated `codex_hooks` flag automatically
32
+ - Hook scripts reject plaintext HTTP for non-localhost API calls
33
+ - Config file permission warning when `~/.multicorn/config.json` is readable by other users
34
+ - Destructive command detection uses word-boundary matching instead of substring includes
35
+ - Unknown tool names default to restrictive `write` permission level instead of `execute`
36
+ - Audit log payloads size-bounded and secret patterns redacted before transmission
37
+ - Error messages sanitised: internal details hidden unless `MULTICORN_DEBUG` is set
38
+ - Test-mode polling escape hatch removed from production hook scripts
39
+ - Shared utility module extracted to eliminate duplication between hook scripts
40
+
41
+ ### Changed
42
+
43
+ - Error message prefix shortened from `[multicorn-shield]` to `[Shield]`
44
+ - "Audit trail" terminology replaced with "record the action" in user-facing messages
45
+
12
46
  ## [1.8.0] - 2026-05-11
13
47
 
14
48
  ### Added
package/dist/index.cjs CHANGED
@@ -27,6 +27,7 @@ var AGENT_PLATFORM_SLUGS = [
27
27
  "goose",
28
28
  "kilo-code",
29
29
  "opencode",
30
+ "codex-cli",
30
31
  "other-mcp",
31
32
  "github-actions",
32
33
  "unknown"
package/dist/index.d.cts CHANGED
@@ -12,7 +12,7 @@ import { LitElement, PropertyValues, HTMLTemplateResult } from 'lit';
12
12
  /**
13
13
  * Agent client platforms supported by hosted proxy and native hooks (aligned with API validation).
14
14
  */
15
- declare const AGENT_PLATFORM_SLUGS: readonly ["openclaw", "claude-code", "claude-desktop", "cursor", "windsurf", "cline", "gemini-cli", "continue-dev", "github-copilot", "goose", "kilo-code", "opencode", "other-mcp", "github-actions", "unknown"];
15
+ declare const AGENT_PLATFORM_SLUGS: readonly ["openclaw", "claude-code", "claude-desktop", "cursor", "windsurf", "cline", "gemini-cli", "continue-dev", "github-copilot", "goose", "kilo-code", "opencode", "codex-cli", "other-mcp", "github-actions", "unknown"];
16
16
  type AgentPlatformSlug = (typeof AGENT_PLATFORM_SLUGS)[number];
17
17
  /**
18
18
  * Possible operational states for an agent.
package/dist/index.d.ts CHANGED
@@ -12,7 +12,7 @@ import { LitElement, PropertyValues, HTMLTemplateResult } from 'lit';
12
12
  /**
13
13
  * Agent client platforms supported by hosted proxy and native hooks (aligned with API validation).
14
14
  */
15
- declare const AGENT_PLATFORM_SLUGS: readonly ["openclaw", "claude-code", "claude-desktop", "cursor", "windsurf", "cline", "gemini-cli", "continue-dev", "github-copilot", "goose", "kilo-code", "opencode", "other-mcp", "github-actions", "unknown"];
15
+ declare const AGENT_PLATFORM_SLUGS: readonly ["openclaw", "claude-code", "claude-desktop", "cursor", "windsurf", "cline", "gemini-cli", "continue-dev", "github-copilot", "goose", "kilo-code", "opencode", "codex-cli", "other-mcp", "github-actions", "unknown"];
16
16
  type AgentPlatformSlug = (typeof AGENT_PLATFORM_SLUGS)[number];
17
17
  /**
18
18
  * Possible operational states for an agent.
package/dist/index.js CHANGED
@@ -25,6 +25,7 @@ var AGENT_PLATFORM_SLUGS = [
25
25
  "goose",
26
26
  "kilo-code",
27
27
  "opencode",
28
+ "codex-cli",
28
29
  "other-mcp",
29
30
  "github-actions",
30
31
  "unknown"
@@ -941,6 +941,99 @@ async function installOpenCodeNativePlugin() {
941
941
  const dest = join(destDir, "multicorn-shield.ts");
942
942
  await copyFile(src, dest);
943
943
  }
944
+ function getCodexCliHooksInstallDir() {
945
+ return join(homedir(), ".multicorn", "codex-cli-hooks");
946
+ }
947
+ function getCodexConfigTomlPath() {
948
+ return join(homedir(), ".codex", "config.toml");
949
+ }
950
+ function getCodexHooksJsonPath() {
951
+ return join(homedir(), ".codex", "hooks.json");
952
+ }
953
+ async function installCodexCliNativeHooks() {
954
+ const root = multicornShieldPackageRoot();
955
+ const scriptsDir = join(root, "plugins", "codex-cli", "hooks", "scripts");
956
+ const preHook = join(scriptsDir, "pre-tool-use.cjs");
957
+ const postHook = join(scriptsDir, "post-tool-use.cjs");
958
+ const toolMap = join(scriptsDir, "codex-cli-tool-map.cjs");
959
+ const sharedHook = join(scriptsDir, "codex-cli-hooks-shared.cjs");
960
+ if (!existsSync(preHook) || !existsSync(postHook) || !existsSync(toolMap) || !existsSync(sharedHook)) {
961
+ throw new Error(
962
+ `Could not find Shield Codex CLI hook scripts at ${scriptsDir}. If you use npm, install the latest multicorn-shield package.`
963
+ );
964
+ }
965
+ const destDir = getCodexCliHooksInstallDir();
966
+ await mkdir(destDir, { recursive: true });
967
+ await copyFile(preHook, join(destDir, "pre-tool-use.cjs"));
968
+ await copyFile(postHook, join(destDir, "post-tool-use.cjs"));
969
+ await copyFile(toolMap, join(destDir, "codex-cli-tool-map.cjs"));
970
+ await copyFile(sharedHook, join(destDir, "codex-cli-hooks-shared.cjs"));
971
+ const configTomlPath = getCodexConfigTomlPath();
972
+ await mkdir(join(homedir(), ".codex"), { recursive: true });
973
+ let tomlContent = "";
974
+ try {
975
+ tomlContent = await readFile(configTomlPath, "utf8");
976
+ } catch (err) {
977
+ if (!isErrnoException(err) || err.code !== "ENOENT") {
978
+ throw err;
979
+ }
980
+ }
981
+ if (tomlContent.includes("codex_hooks")) {
982
+ tomlContent = tomlContent.replace(/codex_hooks/g, "hooks");
983
+ await writeFile(configTomlPath, tomlContent, "utf8");
984
+ }
985
+ if (!/\bhooks\s*=\s*true/.test(tomlContent)) {
986
+ tomlContent = tomlContent.includes("[features]") ? tomlContent.replace("[features]", "[features]\nhooks = true") : tomlContent.trimEnd() + (tomlContent.length > 0 ? "\n\n" : "") + "[features]\nhooks = true\n";
987
+ await writeFile(configTomlPath, tomlContent, "utf8");
988
+ }
989
+ const hooksConfig = {
990
+ hooks: {
991
+ PreToolUse: [
992
+ {
993
+ matcher: "",
994
+ hooks: [
995
+ {
996
+ type: "command",
997
+ command: `node ${join(destDir, "pre-tool-use.cjs")}`,
998
+ statusMessage: "Checking Shield permissions"
999
+ }
1000
+ ]
1001
+ }
1002
+ ],
1003
+ PostToolUse: [
1004
+ {
1005
+ matcher: "",
1006
+ hooks: [
1007
+ {
1008
+ type: "command",
1009
+ command: `node ${join(destDir, "post-tool-use.cjs")}`,
1010
+ timeout: 30,
1011
+ statusMessage: "Logging to Shield"
1012
+ }
1013
+ ]
1014
+ }
1015
+ ]
1016
+ }
1017
+ };
1018
+ await writeFile(getCodexHooksJsonPath(), JSON.stringify(hooksConfig, null, 2) + "\n", "utf8");
1019
+ }
1020
+ async function promptCodexCliIntegrationMode(ask) {
1021
+ process.stderr.write("\n" + style.bold("Codex CLI integration") + "\n");
1022
+ process.stderr.write(
1023
+ " " + style.violet("1") + ". Native plugin - Shield checks terminal (Bash) commands via Codex Hooks\n"
1024
+ );
1025
+ process.stderr.write(
1026
+ " " + style.violet("2") + ". Hosted proxy - govern MCP server traffic via config.toml\n"
1027
+ );
1028
+ let choice = 0;
1029
+ while (choice === 0) {
1030
+ const input = await ask("Choose integration (1-2): ");
1031
+ const num = parseInt(input.trim(), 10);
1032
+ if (num === 1) choice = 1;
1033
+ if (num === 2) choice = 2;
1034
+ }
1035
+ return choice === 1 ? "native" : "hosted";
1036
+ }
944
1037
  async function promptClineIntegrationMode(ask) {
945
1038
  process.stderr.write("\n" + style.bold("Cline integration") + "\n");
946
1039
  process.stderr.write(
@@ -2059,6 +2152,9 @@ async function applyHostedProxyMcpConfig(platform, proxyUrl, shortName, apiKey,
2059
2152
  if (result === "parse-error") {
2060
2153
  printHostedProxyJsonParseWarning(join(workspacePath, "opencode.json"));
2061
2154
  }
2155
+ } else if (platform === "codex-cli") {
2156
+ printPlatformSnippet(platform, proxyUrl, shortName, apiKey);
2157
+ return;
2062
2158
  } else if (platform === "continue-dev") {
2063
2159
  result = await mergeContinueHostedMcp(
2064
2160
  workspacePath,
@@ -2194,7 +2290,8 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
2194
2290
  "github-copilot",
2195
2291
  "continue-dev",
2196
2292
  "goose",
2197
- "opencode"
2293
+ "opencode",
2294
+ "codex-cli"
2198
2295
  ]);
2199
2296
  const usesInlineKey = hostedInlinePlatforms.has(platform);
2200
2297
  const authHeader = usesInlineKey ? `Bearer ${apiKey}` : "Bearer YOUR_SHIELD_API_KEY";
@@ -2295,6 +2392,11 @@ mcpServers:
2295
2392
  null,
2296
2393
  2
2297
2394
  );
2395
+ } else if (platform === "codex-cli") {
2396
+ snippetText = `[mcp_servers.${shortName}]
2397
+ url = "${urlInSnippet}"
2398
+ bearer_token_env_var = "MULTICORN_API_KEY"
2399
+ `;
2298
2400
  } else {
2299
2401
  const urlKey = platform === "windsurf" ? "serverUrl" : "url";
2300
2402
  snippetText = JSON.stringify(
@@ -2338,6 +2440,12 @@ mcpServers:
2338
2440
  "Add this to opencode.json in your project root (or ~/.config/opencode/opencode.json for global config). OpenCode detects configured MCP servers on the next session start."
2339
2441
  ) + "\n\n"
2340
2442
  );
2443
+ } else if (platform === "codex-cli") {
2444
+ process.stderr.write(
2445
+ "\n" + style.dim(
2446
+ "Add this to ~/.codex/config.toml (create the file if it does not exist). Set the MULTICORN_API_KEY environment variable to your Shield API key. Restart Codex CLI after saving."
2447
+ ) + "\n\n"
2448
+ );
2341
2449
  } else if (platform === "github-copilot") {
2342
2450
  process.stderr.write(
2343
2451
  "\n" + style.dim(
@@ -3054,6 +3162,100 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
3054
3162
  setupSucceeded = true;
3055
3163
  }
3056
3164
  }
3165
+ } else if (selectedPlatform === "codex-cli") {
3166
+ const codexMode = await promptCodexCliIntegrationMode(ask);
3167
+ if (codexMode === "native") {
3168
+ try {
3169
+ await installCodexCliNativeHooks();
3170
+ process.stderr.write("\n" + style.bold("Shield Codex CLI hooks installed") + "\n\n");
3171
+ process.stderr.write(
3172
+ style.dim("Hook scripts: ") + style.cyan(getCodexCliHooksInstallDir()) + "\n"
3173
+ );
3174
+ process.stderr.write(
3175
+ style.dim("Hooks config: ") + style.cyan(getCodexHooksJsonPath()) + "\n"
3176
+ );
3177
+ process.stderr.write(
3178
+ style.dim("Feature flag: ") + style.cyan(getCodexConfigTomlPath()) + "\n"
3179
+ );
3180
+ process.stderr.write("\n");
3181
+ process.stderr.write(
3182
+ style.dim(
3183
+ "Codex hooks currently intercept terminal (Bash) commands only. File edits and MCP tool calls are not yet covered by hooks."
3184
+ ) + "\n\n"
3185
+ );
3186
+ process.stderr.write(
3187
+ style.dim(
3188
+ "Start Codex CLI, then type /hooks to review the Shield hooks. Press 't' to trust each one before they can run."
3189
+ ) + "\n"
3190
+ );
3191
+ configuredAgents.push({
3192
+ selection,
3193
+ platform: selectedPlatform,
3194
+ platformLabel: selectedLabel,
3195
+ agentName,
3196
+ codexCliIntegration: "native"
3197
+ });
3198
+ setupSucceeded = true;
3199
+ } catch (error) {
3200
+ const detail = error instanceof Error ? error.message : String(error);
3201
+ process.stderr.write(style.red("\u2717 ") + detail + "\n");
3202
+ }
3203
+ } else {
3204
+ const { targetUrl, shortName, upstreamHeaders } = await promptProxyConfig(ask, agentName);
3205
+ let proxyUrl = "";
3206
+ let created = false;
3207
+ while (!created) {
3208
+ const spinner = withSpinner("Creating proxy config...");
3209
+ try {
3210
+ proxyUrl = await createProxyConfig(
3211
+ resolvedBaseUrl,
3212
+ apiKey,
3213
+ agentName,
3214
+ targetUrl,
3215
+ shortName,
3216
+ selectedPlatform,
3217
+ upstreamHeaders
3218
+ );
3219
+ spinner.stop(true, "Proxy config created!");
3220
+ created = true;
3221
+ } catch (error) {
3222
+ const detail = error instanceof Error ? error.message : String(error);
3223
+ spinner.stop(false, detail);
3224
+ const retry = await ask("Try again? (Y/n) ");
3225
+ if (retry.trim().toLowerCase() === "n") {
3226
+ break;
3227
+ }
3228
+ }
3229
+ }
3230
+ if (created && proxyUrl.length > 0) {
3231
+ process.stderr.write("\n" + style.bold("Your Shield proxy URL:") + "\n");
3232
+ process.stderr.write(
3233
+ " " + style.cyan(formatHostedProxyUrlForStderr(selectedPlatform, proxyUrl, apiKey)) + "\n"
3234
+ );
3235
+ await applyHostedProxyMcpConfig(
3236
+ selectedPlatform,
3237
+ proxyUrl,
3238
+ shortName,
3239
+ apiKey,
3240
+ initWorkspacePath
3241
+ );
3242
+ process.stderr.write(
3243
+ "\n" + style.dim(
3244
+ "Add the TOML snippet above to ~/.codex/config.toml. Set the MULTICORN_API_KEY environment variable to your Shield API key. Restart Codex CLI after saving."
3245
+ ) + "\n"
3246
+ );
3247
+ configuredAgents.push({
3248
+ selection,
3249
+ platform: selectedPlatform,
3250
+ platformLabel: selectedLabel,
3251
+ agentName,
3252
+ shortName,
3253
+ proxyUrl,
3254
+ codexCliIntegration: "hosted"
3255
+ });
3256
+ setupSucceeded = true;
3257
+ }
3258
+ }
3057
3259
  } else if (selectedPlatform === "cline") {
3058
3260
  const clineMode = await promptClineIntegrationMode(ask);
3059
3261
  if (clineMode === "native") {
@@ -3366,6 +3568,22 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
3366
3568
  "\n" + style.bold("OpenCode (hosted)") + '\n \u2192 Restart OpenCode or start a new session after saving opencode.json\n \u2192 Try it: paste this into OpenCode:\n "Use the ' + ocLabel + ' MCP server to list allowed directories"\n'
3367
3569
  );
3368
3570
  }
3571
+ const codexNativeConfigured = configuredAgents.some(
3572
+ (a) => a.platform === "codex-cli" && a.codexCliIntegration === "native"
3573
+ );
3574
+ const codexHostedConfigured = configuredAgents.some(
3575
+ (a) => a.platform === "codex-cli" && a.codexCliIntegration === "hosted"
3576
+ );
3577
+ if (codexNativeConfigured) {
3578
+ blocks.push(
3579
+ "\n" + style.bold("Codex CLI (native)") + "\n \u2192 Start Codex CLI (run 'codex' in your terminal)\n \u2192 Type /hooks to review the Shield hooks - press 't' to trust each one\n \u2192 Once both hooks are trusted, try a Bash command - Shield will intercept and check permissions\n \u2192 Note: hooks currently cover terminal (Bash) commands only\n"
3580
+ );
3581
+ }
3582
+ if (codexHostedConfigured) {
3583
+ blocks.push(
3584
+ "\n" + style.bold("Codex CLI (hosted)") + "\n \u2192 Set the MULTICORN_API_KEY environment variable to your Shield API key\n \u2192 Restart Codex CLI after saving config.toml\n \u2192 Try it: make a request that uses an MCP tool through Shield\n"
3585
+ );
3586
+ }
3369
3587
  if (configuredPlatforms.has("other-mcp")) {
3370
3588
  blocks.push(
3371
3589
  "\n" + style.bold("Local MCP / Other") + "\n \u2192 Run your configured wrap command (for example " + style.cyan("npx multicorn-shield --wrap ...") + ")\n \u2192 Try it: make a request in your coding agent - Shield will intercept the first tool call and ask for your consent\n"
@@ -3431,6 +3649,12 @@ var init_config = __esm({
3431
3649
  section: "native",
3432
3650
  prereqUrl: "https://opencode.ai"
3433
3651
  },
3652
+ {
3653
+ slug: "codex-cli",
3654
+ displayName: "Codex CLI",
3655
+ section: "native",
3656
+ prereqUrl: "https://github.com/openai/codex"
3657
+ },
3434
3658
  {
3435
3659
  slug: "cursor",
3436
3660
  displayName: "Cursor",
@@ -4436,7 +4660,7 @@ var init_package = __esm({
4436
4660
  "package.json"() {
4437
4661
  package_default = {
4438
4662
  name: "multicorn-shield",
4439
- version: "1.8.0",
4663
+ version: "1.9.1",
4440
4664
  description: "The control layer for AI agents: permissions, consent, spending limits, and audit logging.",
4441
4665
  license: "MIT",
4442
4666
  author: "Multicorn AI Pty Ltd",
@@ -4477,6 +4701,7 @@ var init_package = __esm({
4477
4701
  "plugins/cline",
4478
4702
  "plugins/gemini-cli",
4479
4703
  "plugins/opencode",
4704
+ "plugins/codex-cli",
4480
4705
  "LICENSE",
4481
4706
  "README.md",
4482
4707
  "CHANGELOG.md"
@@ -955,6 +955,99 @@ async function installOpenCodeNativePlugin() {
955
955
  const dest = join(destDir, "multicorn-shield.ts");
956
956
  await copyFile(src, dest);
957
957
  }
958
+ function getCodexCliHooksInstallDir() {
959
+ return join(homedir(), ".multicorn", "codex-cli-hooks");
960
+ }
961
+ function getCodexConfigTomlPath() {
962
+ return join(homedir(), ".codex", "config.toml");
963
+ }
964
+ function getCodexHooksJsonPath() {
965
+ return join(homedir(), ".codex", "hooks.json");
966
+ }
967
+ async function installCodexCliNativeHooks() {
968
+ const root = multicornShieldPackageRoot();
969
+ const scriptsDir = join(root, "plugins", "codex-cli", "hooks", "scripts");
970
+ const preHook = join(scriptsDir, "pre-tool-use.cjs");
971
+ const postHook = join(scriptsDir, "post-tool-use.cjs");
972
+ const toolMap = join(scriptsDir, "codex-cli-tool-map.cjs");
973
+ const sharedHook = join(scriptsDir, "codex-cli-hooks-shared.cjs");
974
+ if (!existsSync(preHook) || !existsSync(postHook) || !existsSync(toolMap) || !existsSync(sharedHook)) {
975
+ throw new Error(
976
+ `Could not find Shield Codex CLI hook scripts at ${scriptsDir}. If you use npm, install the latest multicorn-shield package.`
977
+ );
978
+ }
979
+ const destDir = getCodexCliHooksInstallDir();
980
+ await mkdir(destDir, { recursive: true });
981
+ await copyFile(preHook, join(destDir, "pre-tool-use.cjs"));
982
+ await copyFile(postHook, join(destDir, "post-tool-use.cjs"));
983
+ await copyFile(toolMap, join(destDir, "codex-cli-tool-map.cjs"));
984
+ await copyFile(sharedHook, join(destDir, "codex-cli-hooks-shared.cjs"));
985
+ const configTomlPath = getCodexConfigTomlPath();
986
+ await mkdir(join(homedir(), ".codex"), { recursive: true });
987
+ let tomlContent = "";
988
+ try {
989
+ tomlContent = await readFile(configTomlPath, "utf8");
990
+ } catch (err) {
991
+ if (!isErrnoException(err) || err.code !== "ENOENT") {
992
+ throw err;
993
+ }
994
+ }
995
+ if (tomlContent.includes("codex_hooks")) {
996
+ tomlContent = tomlContent.replace(/codex_hooks/g, "hooks");
997
+ await writeFile(configTomlPath, tomlContent, "utf8");
998
+ }
999
+ if (!/\bhooks\s*=\s*true/.test(tomlContent)) {
1000
+ tomlContent = tomlContent.includes("[features]") ? tomlContent.replace("[features]", "[features]\nhooks = true") : tomlContent.trimEnd() + (tomlContent.length > 0 ? "\n\n" : "") + "[features]\nhooks = true\n";
1001
+ await writeFile(configTomlPath, tomlContent, "utf8");
1002
+ }
1003
+ const hooksConfig = {
1004
+ hooks: {
1005
+ PreToolUse: [
1006
+ {
1007
+ matcher: "",
1008
+ hooks: [
1009
+ {
1010
+ type: "command",
1011
+ command: `node ${join(destDir, "pre-tool-use.cjs")}`,
1012
+ statusMessage: "Checking Shield permissions"
1013
+ }
1014
+ ]
1015
+ }
1016
+ ],
1017
+ PostToolUse: [
1018
+ {
1019
+ matcher: "",
1020
+ hooks: [
1021
+ {
1022
+ type: "command",
1023
+ command: `node ${join(destDir, "post-tool-use.cjs")}`,
1024
+ timeout: 30,
1025
+ statusMessage: "Logging to Shield"
1026
+ }
1027
+ ]
1028
+ }
1029
+ ]
1030
+ }
1031
+ };
1032
+ await writeFile(getCodexHooksJsonPath(), JSON.stringify(hooksConfig, null, 2) + "\n", "utf8");
1033
+ }
1034
+ async function promptCodexCliIntegrationMode(ask) {
1035
+ process.stderr.write("\n" + style.bold("Codex CLI integration") + "\n");
1036
+ process.stderr.write(
1037
+ " " + style.violet("1") + ". Native plugin - Shield checks terminal (Bash) commands via Codex Hooks\n"
1038
+ );
1039
+ process.stderr.write(
1040
+ " " + style.violet("2") + ". Hosted proxy - govern MCP server traffic via config.toml\n"
1041
+ );
1042
+ let choice = 0;
1043
+ while (choice === 0) {
1044
+ const input = await ask("Choose integration (1-2): ");
1045
+ const num = parseInt(input.trim(), 10);
1046
+ if (num === 1) choice = 1;
1047
+ if (num === 2) choice = 2;
1048
+ }
1049
+ return choice === 1 ? "native" : "hosted";
1050
+ }
958
1051
  async function promptClineIntegrationMode(ask) {
959
1052
  process.stderr.write("\n" + style.bold("Cline integration") + "\n");
960
1053
  process.stderr.write(
@@ -1406,6 +1499,12 @@ var INIT_WIZARD_PLATFORM_REGISTRY = [
1406
1499
  section: "native",
1407
1500
  prereqUrl: "https://opencode.ai"
1408
1501
  },
1502
+ {
1503
+ slug: "codex-cli",
1504
+ displayName: "Codex CLI",
1505
+ section: "native",
1506
+ prereqUrl: "https://github.com/openai/codex"
1507
+ },
1409
1508
  {
1410
1509
  slug: "cursor",
1411
1510
  displayName: "Cursor",
@@ -2146,6 +2245,9 @@ async function applyHostedProxyMcpConfig(platform, proxyUrl, shortName, apiKey,
2146
2245
  if (result === "parse-error") {
2147
2246
  printHostedProxyJsonParseWarning(join(workspacePath, "opencode.json"));
2148
2247
  }
2248
+ } else if (platform === "codex-cli") {
2249
+ printPlatformSnippet(platform, proxyUrl, shortName, apiKey);
2250
+ return;
2149
2251
  } else if (platform === "continue-dev") {
2150
2252
  result = await mergeContinueHostedMcp(
2151
2253
  workspacePath,
@@ -2281,7 +2383,8 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
2281
2383
  "github-copilot",
2282
2384
  "continue-dev",
2283
2385
  "goose",
2284
- "opencode"
2386
+ "opencode",
2387
+ "codex-cli"
2285
2388
  ]);
2286
2389
  const usesInlineKey = hostedInlinePlatforms.has(platform);
2287
2390
  const authHeader = usesInlineKey ? `Bearer ${apiKey}` : "Bearer YOUR_SHIELD_API_KEY";
@@ -2382,6 +2485,11 @@ mcpServers:
2382
2485
  null,
2383
2486
  2
2384
2487
  );
2488
+ } else if (platform === "codex-cli") {
2489
+ snippetText = `[mcp_servers.${shortName}]
2490
+ url = "${urlInSnippet}"
2491
+ bearer_token_env_var = "MULTICORN_API_KEY"
2492
+ `;
2385
2493
  } else {
2386
2494
  const urlKey = platform === "windsurf" ? "serverUrl" : "url";
2387
2495
  snippetText = JSON.stringify(
@@ -2425,6 +2533,12 @@ mcpServers:
2425
2533
  "Add this to opencode.json in your project root (or ~/.config/opencode/opencode.json for global config). OpenCode detects configured MCP servers on the next session start."
2426
2534
  ) + "\n\n"
2427
2535
  );
2536
+ } else if (platform === "codex-cli") {
2537
+ process.stderr.write(
2538
+ "\n" + style.dim(
2539
+ "Add this to ~/.codex/config.toml (create the file if it does not exist). Set the MULTICORN_API_KEY environment variable to your Shield API key. Restart Codex CLI after saving."
2540
+ ) + "\n\n"
2541
+ );
2428
2542
  } else if (platform === "github-copilot") {
2429
2543
  process.stderr.write(
2430
2544
  "\n" + style.dim(
@@ -3142,6 +3256,100 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
3142
3256
  setupSucceeded = true;
3143
3257
  }
3144
3258
  }
3259
+ } else if (selectedPlatform === "codex-cli") {
3260
+ const codexMode = await promptCodexCliIntegrationMode(ask);
3261
+ if (codexMode === "native") {
3262
+ try {
3263
+ await installCodexCliNativeHooks();
3264
+ process.stderr.write("\n" + style.bold("Shield Codex CLI hooks installed") + "\n\n");
3265
+ process.stderr.write(
3266
+ style.dim("Hook scripts: ") + style.cyan(getCodexCliHooksInstallDir()) + "\n"
3267
+ );
3268
+ process.stderr.write(
3269
+ style.dim("Hooks config: ") + style.cyan(getCodexHooksJsonPath()) + "\n"
3270
+ );
3271
+ process.stderr.write(
3272
+ style.dim("Feature flag: ") + style.cyan(getCodexConfigTomlPath()) + "\n"
3273
+ );
3274
+ process.stderr.write("\n");
3275
+ process.stderr.write(
3276
+ style.dim(
3277
+ "Codex hooks currently intercept terminal (Bash) commands only. File edits and MCP tool calls are not yet covered by hooks."
3278
+ ) + "\n\n"
3279
+ );
3280
+ process.stderr.write(
3281
+ style.dim(
3282
+ "Start Codex CLI, then type /hooks to review the Shield hooks. Press 't' to trust each one before they can run."
3283
+ ) + "\n"
3284
+ );
3285
+ configuredAgents.push({
3286
+ selection,
3287
+ platform: selectedPlatform,
3288
+ platformLabel: selectedLabel,
3289
+ agentName,
3290
+ codexCliIntegration: "native"
3291
+ });
3292
+ setupSucceeded = true;
3293
+ } catch (error) {
3294
+ const detail = error instanceof Error ? error.message : String(error);
3295
+ process.stderr.write(style.red("\u2717 ") + detail + "\n");
3296
+ }
3297
+ } else {
3298
+ const { targetUrl, shortName, upstreamHeaders } = await promptProxyConfig(ask, agentName);
3299
+ let proxyUrl = "";
3300
+ let created = false;
3301
+ while (!created) {
3302
+ const spinner = withSpinner("Creating proxy config...");
3303
+ try {
3304
+ proxyUrl = await createProxyConfig(
3305
+ resolvedBaseUrl,
3306
+ apiKey,
3307
+ agentName,
3308
+ targetUrl,
3309
+ shortName,
3310
+ selectedPlatform,
3311
+ upstreamHeaders
3312
+ );
3313
+ spinner.stop(true, "Proxy config created!");
3314
+ created = true;
3315
+ } catch (error) {
3316
+ const detail = error instanceof Error ? error.message : String(error);
3317
+ spinner.stop(false, detail);
3318
+ const retry = await ask("Try again? (Y/n) ");
3319
+ if (retry.trim().toLowerCase() === "n") {
3320
+ break;
3321
+ }
3322
+ }
3323
+ }
3324
+ if (created && proxyUrl.length > 0) {
3325
+ process.stderr.write("\n" + style.bold("Your Shield proxy URL:") + "\n");
3326
+ process.stderr.write(
3327
+ " " + style.cyan(formatHostedProxyUrlForStderr(selectedPlatform, proxyUrl, apiKey)) + "\n"
3328
+ );
3329
+ await applyHostedProxyMcpConfig(
3330
+ selectedPlatform,
3331
+ proxyUrl,
3332
+ shortName,
3333
+ apiKey,
3334
+ initWorkspacePath
3335
+ );
3336
+ process.stderr.write(
3337
+ "\n" + style.dim(
3338
+ "Add the TOML snippet above to ~/.codex/config.toml. Set the MULTICORN_API_KEY environment variable to your Shield API key. Restart Codex CLI after saving."
3339
+ ) + "\n"
3340
+ );
3341
+ configuredAgents.push({
3342
+ selection,
3343
+ platform: selectedPlatform,
3344
+ platformLabel: selectedLabel,
3345
+ agentName,
3346
+ shortName,
3347
+ proxyUrl,
3348
+ codexCliIntegration: "hosted"
3349
+ });
3350
+ setupSucceeded = true;
3351
+ }
3352
+ }
3145
3353
  } else if (selectedPlatform === "cline") {
3146
3354
  const clineMode = await promptClineIntegrationMode(ask);
3147
3355
  if (clineMode === "native") {
@@ -3454,6 +3662,22 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
3454
3662
  "\n" + style.bold("OpenCode (hosted)") + '\n \u2192 Restart OpenCode or start a new session after saving opencode.json\n \u2192 Try it: paste this into OpenCode:\n "Use the ' + ocLabel + ' MCP server to list allowed directories"\n'
3455
3663
  );
3456
3664
  }
3665
+ const codexNativeConfigured = configuredAgents.some(
3666
+ (a) => a.platform === "codex-cli" && a.codexCliIntegration === "native"
3667
+ );
3668
+ const codexHostedConfigured = configuredAgents.some(
3669
+ (a) => a.platform === "codex-cli" && a.codexCliIntegration === "hosted"
3670
+ );
3671
+ if (codexNativeConfigured) {
3672
+ blocks.push(
3673
+ "\n" + style.bold("Codex CLI (native)") + "\n \u2192 Start Codex CLI (run 'codex' in your terminal)\n \u2192 Type /hooks to review the Shield hooks - press 't' to trust each one\n \u2192 Once both hooks are trusted, try a Bash command - Shield will intercept and check permissions\n \u2192 Note: hooks currently cover terminal (Bash) commands only\n"
3674
+ );
3675
+ }
3676
+ if (codexHostedConfigured) {
3677
+ blocks.push(
3678
+ "\n" + style.bold("Codex CLI (hosted)") + "\n \u2192 Set the MULTICORN_API_KEY environment variable to your Shield API key\n \u2192 Restart Codex CLI after saving config.toml\n \u2192 Try it: make a request that uses an MCP tool through Shield\n"
3679
+ );
3680
+ }
3457
3681
  if (configuredPlatforms.has("other-mcp")) {
3458
3682
  blocks.push(
3459
3683
  "\n" + style.bold("Local MCP / Other") + "\n \u2192 Run your configured wrap command (for example " + style.cyan("npx multicorn-shield --wrap ...") + ")\n \u2192 Try it: make a request in your coding agent - Shield will intercept the first tool call and ask for your consent\n"
@@ -4359,7 +4583,7 @@ async function restoreClaudeDesktopMcpFromBackup() {
4359
4583
 
4360
4584
  // package.json
4361
4585
  var package_default = {
4362
- version: "1.8.0"};
4586
+ version: "1.9.1"};
4363
4587
 
4364
4588
  // src/package-meta.ts
4365
4589
  var PACKAGE_VERSION = package_default.version;