@orchid-labs/pluxx 0.1.20 → 0.1.22

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/dist/index.js CHANGED
@@ -2203,7 +2203,7 @@ var require_jiti = __commonJS({
2203
2203
  }, pathe_M_eThtNZ_dirname = function(e3) {
2204
2204
  const t3 = pathe_M_eThtNZ_normalizeWindowsPath(e3).replace(/\/$/, "").split("/").slice(0, -1);
2205
2205
  return 1 === t3.length && ve.test(t3[0]) && (t3[0] += "/"), t3.join("/") || (isAbsolute(e3) ? "/" : ".");
2206
- }, basename2 = function(e3, t3) {
2206
+ }, basename3 = function(e3, t3) {
2207
2207
  const i2 = pathe_M_eThtNZ_normalizeWindowsPath(e3).split("/");
2208
2208
  let s2 = "";
2209
2209
  for (let e4 = i2.length - 1; e4 >= 0; e4--) {
@@ -2911,7 +2911,7 @@ Default "index" lookups for the main are deprecated for ES modules.`, "Deprecati
2911
2911
  if (!e3.opts.fsCache || !t3.filename) return i2();
2912
2912
  const s2 = ` /* v${zt}-${utils_hash(t3.source, 16)} */
2913
2913
  `;
2914
- let r2 = `${basename2(pathe_M_eThtNZ_dirname(t3.filename))}-${(function(e4) {
2914
+ let r2 = `${basename3(pathe_M_eThtNZ_dirname(t3.filename))}-${(function(e4) {
2915
2915
  const t4 = e4.split(lt).pop();
2916
2916
  if (!t4) return;
2917
2917
  const i3 = t4.lastIndexOf(".");
@@ -2957,7 +2957,7 @@ Default "index" lookups for the main are deprecated for ES modules.`, "Deprecati
2957
2957
  return i2.startsWith("#!") && (i2 = "// " + i2), i2;
2958
2958
  }
2959
2959
  function eval_evalModule(e3, t3, i2 = {}) {
2960
- const s2 = i2.id || (i2.filename ? basename2(i2.filename) : `_jitiEval.${i2.ext || (i2.async ? "mjs" : "js")}`), r2 = i2.filename || jitiResolve(e3, s2, { async: i2.async }), n2 = i2.ext || extname(r2), a2 = i2.cache || e3.parentCache || {}, o2 = /\.[cm]?tsx?$/.test(n2), h2 = ".mjs" === n2 || ".js" === n2 && "module" === (function(e4) {
2960
+ const s2 = i2.id || (i2.filename ? basename3(i2.filename) : `_jitiEval.${i2.ext || (i2.async ? "mjs" : "js")}`), r2 = i2.filename || jitiResolve(e3, s2, { async: i2.async }), n2 = i2.ext || extname(r2), a2 = i2.cache || e3.parentCache || {}, o2 = /\.[cm]?tsx?$/.test(n2), h2 = ".mjs" === n2 || ".js" === n2 && "module" === (function(e4) {
2961
2961
  for (; e4 && "." !== e4 && "/" !== e4; ) {
2962
2962
  e4 = pathe_M_eThtNZ_join(e4, "..");
2963
2963
  try {
@@ -7841,6 +7841,20 @@ function definePlugin(config) {
7841
7841
  return PluginConfigSchema.parse(config);
7842
7842
  }
7843
7843
 
7844
+ // src/hook-events.ts
7845
+ var CODEX_SUPPORTED_HOOK_EVENTS = [
7846
+ "SessionStart",
7847
+ "SubagentStart",
7848
+ "PreToolUse",
7849
+ "PermissionRequest",
7850
+ "PostToolUse",
7851
+ "PreCompact",
7852
+ "PostCompact",
7853
+ "UserPromptSubmit",
7854
+ "SubagentStop",
7855
+ "Stop"
7856
+ ];
7857
+
7844
7858
  // src/runtime-readiness-registry.ts
7845
7859
  function getEnabledRuntimeReadinessBindings(capability, plan) {
7846
7860
  return capability.bindings.filter((binding) => {
@@ -7855,7 +7869,7 @@ function getEnabledRuntimeReadinessBindings(capability, plan) {
7855
7869
  });
7856
7870
  }
7857
7871
  var NAMED_PROMPT_TARGET_NOTE = "Named `skills` / `commands` readiness targets currently translate through prompt-entry gating with best-effort matching because the core four do not share one exact per-skill or per-command runtime interception surface.";
7858
- var CODEX_EXTERNAL_NOTE = "Codex readiness now bundles translated hooks in the plugin, and Pluxx still emits `.codex/readiness.generated.json` plus `.codex/hooks.generated.json` as debugging companions because plugin-bundled hook activation still depends on `[features].plugin_hooks = true`. The general `hooks = true` flag covers non-plugin hook config and defaults on, while `codex_hooks` is deprecated and should not be treated as a plugin-bundled hook fallback.";
7872
+ var CODEX_EXTERNAL_NOTE = "Codex readiness now bundles translated hooks in the plugin, and Pluxx still emits `.codex/readiness.generated.json` plus `.codex/hooks.generated.json` as debugging companions because hook activation still depends on `[features].hooks = true`, enabled plugin state, review, trust, and runtime support. `codex_hooks` is deprecated and should not be treated as the current hook feature key.";
7859
7873
  function getRuntimeReadinessNamedPromptTargetNote() {
7860
7874
  return NAMED_PROMPT_TARGET_NOTE;
7861
7875
  }
@@ -7929,18 +7943,18 @@ function getRuntimeReadinessCapability(platform, pluginRootVar = "PLUGIN_ROOT")
7929
7943
  {
7930
7944
  gate: "session-start",
7931
7945
  event: "SessionStart",
7932
- command: "node ./.codex/pluxx-readiness.mjs session-start"
7946
+ command: `node "\${${pluginRootVar}}/.codex/pluxx-readiness.mjs" session-start`
7933
7947
  },
7934
7948
  {
7935
7949
  gate: "mcp-gate",
7936
7950
  event: "PreToolUse",
7937
7951
  matcher: "MCP",
7938
- command: "node ./.codex/pluxx-readiness.mjs mcp-gate"
7952
+ command: `node "\${${pluginRootVar}}/.codex/pluxx-readiness.mjs" mcp-gate`
7939
7953
  },
7940
7954
  {
7941
7955
  gate: "prompt-gate",
7942
7956
  event: "UserPromptSubmit",
7943
- command: "node ./.codex/pluxx-readiness.mjs prompt-gate"
7957
+ command: `node "\${${pluginRootVar}}/.codex/pluxx-readiness.mjs" prompt-gate`
7944
7958
  }
7945
7959
  ],
7946
7960
  notes: CODEX_EXTERNAL_NOTE
@@ -8103,7 +8117,7 @@ var PLATFORM_LIMIT_POLICIES = {
8103
8117
  },
8104
8118
  hooksFeatureFlag: {
8105
8119
  kind: "hard",
8106
- notes: "Hook support depends on the Codex hooks feature flag/runtime support."
8120
+ notes: "Codex hook support depends on the canonical hooks feature flag plus enabled-plugin, trust/review, and runtime support."
8107
8121
  }
8108
8122
  },
8109
8123
  "cursor": {
@@ -8303,8 +8317,8 @@ var PLATFORM_VALIDATION_RULES = {
8303
8317
  hooks: {
8304
8318
  supported: true,
8305
8319
  files: ["hooks/hooks.json", ".codex/hooks.json", "~/.codex/hooks.json"],
8306
- eventNames: ["SessionStart", "PreToolUse", "PermissionRequest", "PostToolUse", "UserPromptSubmit", "Stop"],
8307
- notes: "Codex documents both project or user hook config and plugin-bundled hooks. Plugin-bundled hooks live at `hooks/hooks.json` in the plugin and require `[features].plugin_hooks = true`; the general `[features].hooks = true` flag covers non-plugin hook config and defaults on. `codex_hooks` is deprecated and should not be treated as a plugin-bundled hook fallback."
8320
+ eventNames: [...CODEX_SUPPORTED_HOOK_EVENTS],
8321
+ notes: "Codex documents both project/user hook config and plugin-bundled hooks. Plugin-bundled hooks live at `hooks/hooks.json` in the plugin and require the canonical `[features].hooks = true` flag, enabled plugin state, user review, trust, and runtime support. `codex_hooks` is deprecated and should not be treated as the current hook feature key."
8308
8322
  },
8309
8323
  instructions: {
8310
8324
  files: ["AGENTS.md", "AGENTS.override.md"],
@@ -8743,7 +8757,7 @@ var CORE_FOUR_PRIMITIVE_CAPABILITIES = {
8743
8757
  hooks: {
8744
8758
  mode: "translate",
8745
8759
  nativeSurfaces: ["hooks/hooks.json", ".codex/hooks.json", "~/.codex/hooks.json"],
8746
- notes: "Hooks are native. Pluxx bundles translated Codex hooks in the plugin with the documented `[features].plugin_hooks = true` plugin gate, and still tracks the broader project/user hook config paths where `[features].hooks` is the general flag and `codex_hooks` is deprecated."
8760
+ notes: "Hooks are native. Pluxx bundles translated Codex hooks in the plugin with the documented `[features].hooks = true` feature key, and still tracks broader project/user hook config paths where `codex_hooks` is deprecated. Plugin-bundled execution still also depends on enabled plugin state, review, trust, and runtime support."
8747
8761
  },
8748
8762
  permissions: {
8749
8763
  mode: "translate",
@@ -9139,6 +9153,78 @@ function getPublishReloadInstruction(target) {
9139
9153
  return PUBLISH_RELOAD_INSTRUCTIONS[target];
9140
9154
  }
9141
9155
 
9156
+ // src/mcp-native-overrides.ts
9157
+ function asRecord(value) {
9158
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
9159
+ return value;
9160
+ }
9161
+ function firstString(...values) {
9162
+ for (const value of values) {
9163
+ if (typeof value === "string" && value.trim()) return value.trim();
9164
+ }
9165
+ return void 0;
9166
+ }
9167
+ function readStringRecord(value) {
9168
+ const record = asRecord(value);
9169
+ if (!record) return void 0;
9170
+ const stringRecord = Object.fromEntries(
9171
+ Object.entries(record).filter(([, entryValue]) => typeof entryValue === "string")
9172
+ );
9173
+ return Object.keys(stringRecord).length > 0 ? stringRecord : void 0;
9174
+ }
9175
+ function extractEnvVar(value) {
9176
+ const match = value.match(/\$\{(?:env:)?([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*)/);
9177
+ return match?.[1] ?? match?.[2];
9178
+ }
9179
+ function envVarToKey(envVar) {
9180
+ return envVar.toLowerCase().replace(/_/g, "-");
9181
+ }
9182
+ function collectNativeMcpAuthUserConfigEntries(config, platforms, existingFields) {
9183
+ const next = [];
9184
+ const seenKeys = new Set(existingFields.map((field) => field.key));
9185
+ const seenEnvVars = new Set(
9186
+ existingFields.map((field) => field.envVar ?? defaultUserConfigEnvVar(field.key))
9187
+ );
9188
+ const pushDerived = (envVar, platform, serverName) => {
9189
+ if (!envVar || seenEnvVars.has(envVar)) return;
9190
+ const key = envVarToKey(envVar);
9191
+ if (seenKeys.has(key)) return;
9192
+ seenEnvVars.add(envVar);
9193
+ seenKeys.add(key);
9194
+ next.push({
9195
+ key,
9196
+ title: envVar,
9197
+ description: `Derived from native ${platform} MCP auth for ${serverName}.`,
9198
+ type: "secret",
9199
+ required: true,
9200
+ envVar,
9201
+ targets: [platform]
9202
+ });
9203
+ };
9204
+ for (const platform of platforms) {
9205
+ const platformConfig = asRecord(config.platforms?.[platform]);
9206
+ const mcpServers = asRecord(platformConfig?.mcpServers);
9207
+ if (!mcpServers) continue;
9208
+ for (const [serverName, rawOverride] of Object.entries(mcpServers)) {
9209
+ const override = asRecord(rawOverride);
9210
+ if (!override) continue;
9211
+ const bearerTokenEnv = firstString(override.bearer_token_env_var, override.bearerTokenEnvVar);
9212
+ if (bearerTokenEnv) pushDerived(bearerTokenEnv, platform, serverName);
9213
+ for (const record of [
9214
+ readStringRecord(override.env_http_headers ?? override.envHttpHeaders),
9215
+ readStringRecord(override.headers),
9216
+ readStringRecord(override.http_headers ?? override.httpHeaders)
9217
+ ]) {
9218
+ for (const value of Object.values(record ?? {})) {
9219
+ const envVar = extractEnvVar(value) ?? value;
9220
+ pushDerived(envVar, platform, serverName);
9221
+ }
9222
+ }
9223
+ }
9224
+ }
9225
+ return next;
9226
+ }
9227
+
9142
9228
  // src/cli/publish.ts
9143
9229
  var INSTALLER_TARGETS = ["claude-code", "cursor", "codex", "opencode"];
9144
9230
  function runCommandDefault(command, args, options) {
@@ -9438,8 +9524,15 @@ echo
9438
9524
  echo "Installed __DISPLAY_NAME__ across ${installerTargets.join(", ")}."
9439
9525
  `.replaceAll("__REPO__", "REPO_PLACEHOLDER").replaceAll("__DISPLAY_NAME__", "DISPLAY_PLACEHOLDER");
9440
9526
  }
9527
+ function collectInstallerUserConfigEntries(config, platforms) {
9528
+ const baseEntries = collectUserConfigEntries(config, platforms);
9529
+ return [
9530
+ ...baseEntries,
9531
+ ...collectNativeMcpAuthUserConfigEntries(config, platforms, baseEntries)
9532
+ ];
9533
+ }
9441
9534
  function renderInstallerUserConfigSnippet(config, platform, installDirVariable) {
9442
- const entries = collectUserConfigEntries(config, [platform]).map((entry) => ({
9535
+ const entries = collectInstallerUserConfigEntries(config, [platform]).map((entry) => ({
9443
9536
  key: entry.key,
9444
9537
  title: entry.title,
9445
9538
  type: entry.type ?? "string",
@@ -9447,6 +9540,7 @@ function renderInstallerUserConfigSnippet(config, platform, installDirVariable)
9447
9540
  envVar: entry.envVar ?? defaultUserConfigEnvVar(entry.key)
9448
9541
  }));
9449
9542
  if (entries.length === 0) return "";
9543
+ const preserveSecretReferences = platform === "codex";
9450
9544
  const promptLines = entries.map((entry) => {
9451
9545
  const functionName = entry.type === "secret" ? "pluxx_prompt_secret_config" : "pluxx_prompt_text_config";
9452
9546
  return `${functionName} ${JSON.stringify(entry.key)} ${JSON.stringify(entry.envVar)} ${JSON.stringify(entry.title)} ${entry.required ? "1" : "0"}`;
@@ -9457,6 +9551,8 @@ ${JSON.stringify(entries)}
9457
9551
  PLUXX_USER_CONFIG_JSON
9458
9552
  )"
9459
9553
  PLUXX_REUSED_USER_CONFIG=0
9554
+ PLUXX_PRESERVE_SECRET_REFS="${preserveSecretReferences ? "1" : "0"}"
9555
+ export PLUXX_PRESERVE_SECRET_REFS
9460
9556
 
9461
9557
  pluxx_is_placeholder_secret() {
9462
9558
  case "$1" in
@@ -9483,12 +9579,14 @@ const fs = require('fs')
9483
9579
  const filepath = process.env.PLUXX_SAVED_USER_CONFIG_PATH
9484
9580
  const key = process.env.PLUXX_SAVED_CONFIG_KEY
9485
9581
  const envVar = process.env.PLUXX_SAVED_CONFIG_ENV_VAR
9582
+ const preserveSecretRefs = process.env.PLUXX_PRESERVE_SECRET_REFS === '1'
9486
9583
 
9487
9584
  try {
9488
9585
  const payload = JSON.parse(fs.readFileSync(filepath, 'utf8'))
9489
9586
  const candidates = [
9490
9587
  payload && payload.env && envVar ? payload.env[envVar] : undefined,
9491
9588
  payload && payload.values && key ? payload.values[key] : undefined,
9589
+ preserveSecretRefs && payload && payload.envRefs && envVar && payload.envRefs[envVar] === envVar ? envVar : undefined,
9492
9590
  ]
9493
9591
 
9494
9592
  for (const candidate of candidates) {
@@ -9584,21 +9682,53 @@ const path = require('path')
9584
9682
 
9585
9683
  const installDir = process.env.PLUXX_INSTALL_DIR
9586
9684
  const spec = JSON.parse(process.env.PLUXX_USER_CONFIG_SPEC || '[]')
9685
+ const preserveSecretReferences = ${preserveSecretReferences ? "true" : "false"}
9587
9686
 
9588
9687
  if (installDir && spec.length > 0) {
9589
9688
  const env = {}
9590
9689
  const values = {}
9690
+ const envRefs = {}
9691
+ const secretKeys = []
9692
+ const secretEnv = []
9693
+ let hasSecret = false
9694
+ const secretEnvVars = new Set(
9695
+ spec
9696
+ .filter((entry) => entry && entry.type === 'secret' && typeof entry.envVar === 'string' && entry.envVar !== '')
9697
+ .map((entry) => entry.envVar),
9698
+ )
9591
9699
 
9592
9700
  for (const entry of spec) {
9701
+ if (entry.type === 'secret') {
9702
+ hasSecret = true
9703
+ if (preserveSecretReferences) {
9704
+ if (entry.key) secretKeys.push(entry.key)
9705
+ if (entry.envVar) secretEnv.push(entry.envVar)
9706
+ }
9707
+ }
9593
9708
  const value = process.env[entry.envVar]
9594
9709
  if (value === undefined || value === '') continue
9710
+ if (preserveSecretReferences && entry.type === 'secret') {
9711
+ envRefs[entry.envVar] = entry.envVar
9712
+ continue
9713
+ }
9595
9714
  values[entry.key] = value
9596
9715
  env[entry.envVar] = value
9597
9716
  }
9598
9717
 
9599
9718
  fs.writeFileSync(
9600
9719
  path.join(installDir, '.pluxx-user.json'),
9601
- JSON.stringify({ values, env }, null, 2) + '\\n',
9720
+ JSON.stringify(
9721
+ {
9722
+ ...(Object.keys(values).length > 0 ? { values } : {}),
9723
+ ...(Object.keys(env).length > 0 ? { env } : {}),
9724
+ ...(Object.keys(envRefs).length > 0 ? { envRefs } : {}),
9725
+ ...(hasSecret ? { secretStorage: preserveSecretReferences ? 'env-ref' : 'materialized' } : {}),
9726
+ ...(preserveSecretReferences && secretKeys.length > 0 ? { secretKeys: [...new Set(secretKeys)].sort() } : {}),
9727
+ ...(preserveSecretReferences && secretEnv.length > 0 ? { secretEnv: [...new Set(secretEnv)].sort() } : {}),
9728
+ },
9729
+ null,
9730
+ 2,
9731
+ ) + '\\n',
9602
9732
  )
9603
9733
 
9604
9734
  const envScriptPath = path.join(installDir, 'scripts/check-env.sh')
@@ -9611,7 +9741,10 @@ if (installDir && spec.length > 0) {
9611
9741
 
9612
9742
  const materialize = (value) =>
9613
9743
  typeof value === 'string'
9614
- ? value.replace(/\\$\\{([A-Za-z_][A-Za-z0-9_]*)\\}/g, (_match, name) => env[name] || '${" + name + "}')
9744
+ ? value.replace(
9745
+ /\\$\\{([A-Za-z_][A-Za-z0-9_]*)\\}/g,
9746
+ (_match, name) => (preserveSecretReferences && secretEnvVars.has(name) ? '${" + name + "}' : (env[name] || '${" + name + "}')),
9747
+ )
9615
9748
  : value
9616
9749
 
9617
9750
  const materializeRecord = (record) => {
@@ -9635,7 +9768,7 @@ if (installDir && spec.length > 0) {
9635
9768
  server.env = materializeRecord(server.env)
9636
9769
  }
9637
9770
 
9638
- if (server.bearer_token_env_var && env[server.bearer_token_env_var]) {
9771
+ if (!preserveSecretReferences && server.bearer_token_env_var && env[server.bearer_token_env_var]) {
9639
9772
  server.http_headers = {
9640
9773
  ...(server.http_headers || {}),
9641
9774
  Authorization: 'Bearer ' + env[server.bearer_token_env_var],
@@ -9643,7 +9776,7 @@ if (installDir && spec.length > 0) {
9643
9776
  delete server.bearer_token_env_var
9644
9777
  }
9645
9778
 
9646
- if (server.env_http_headers && typeof server.env_http_headers === 'object') {
9779
+ if (!preserveSecretReferences && server.env_http_headers && typeof server.env_http_headers === 'object') {
9647
9780
  server.http_headers = {
9648
9781
  ...(server.http_headers || {}),
9649
9782
  }
@@ -9668,7 +9801,7 @@ NODE
9668
9801
  `;
9669
9802
  }
9670
9803
  function hasInstallerUserConfig(config, platform) {
9671
- return collectUserConfigEntries(config, [platform]).length > 0;
9804
+ return collectInstallerUserConfigEntries(config, [platform]).length > 0;
9672
9805
  }
9673
9806
  function renderInstallerSavedUserConfigCaptureSnippet(config, platform, installDirVariable) {
9674
9807
  if (!hasInstallerUserConfig(config, platform)) return "";
@@ -9834,13 +9967,13 @@ for (const line of lines) {
9834
9967
  continue
9835
9968
  }
9836
9969
  if (tableName === '') {
9837
- const dottedMatch = trimmed.match(/^features\\.plugin_hooks\\s*=\\s*(.+)$/)
9970
+ const dottedMatch = trimmed.match(/^features\\.hooks\\s*=\\s*(.+)$/)
9838
9971
  if (dottedMatch && isTomlTrue(dottedMatch[1])) process.exit(0)
9839
9972
  const inlineMatch = trimmed.match(/^features\\s*=\\s*(.+)$/)
9840
- if (inlineMatch && /\\bplugin_hooks\\s*=\\s*true\\b/i.test(inlineMatch[1])) process.exit(0)
9973
+ if (inlineMatch && /\\bhooks\\s*=\\s*true\\b/i.test(inlineMatch[1])) process.exit(0)
9841
9974
  }
9842
9975
  if (tableName !== 'features') continue
9843
- const match = trimmed.match(/^plugin_hooks\\s*=\\s*(.+)$/)
9976
+ const match = trimmed.match(/^hooks\\s*=\\s*(.+)$/)
9844
9977
  if (match && isTomlTrue(match[1])) process.exit(0)
9845
9978
  }
9846
9979
  process.exit(1)
@@ -9859,7 +9992,7 @@ NODE
9859
9992
  *)
9860
9993
  if [[ -r /dev/tty ]]; then
9861
9994
  echo "This Codex plugin bundle includes startup hooks." >/dev/tty
9862
- echo "Codex requires [features].plugin_hooks = true before plugin-bundled hooks can run." >/dev/tty
9995
+ echo "Codex requires [features].hooks = true before plugin-bundled hooks can run." >/dev/tty
9863
9996
  read -r -p "Enable Codex plugin-bundled hooks in $CODEX_CONFIG_PATH now? [Y/n] " PLUXX_CODEX_HOOKS_REPLY </dev/tty
9864
9997
  case "$PLUXX_CODEX_HOOKS_REPLY" in
9865
9998
  n|N|no|NO)
@@ -9931,7 +10064,7 @@ for (let index = 0; index < lines.length; index += 1) {
9931
10064
  if (/^features\\.[A-Za-z0-9_-]+\\s*=/.test(trimmed) && firstTopLevelFeaturesDotted < 0) {
9932
10065
  firstTopLevelFeaturesDotted = index
9933
10066
  }
9934
- if (/^features\\.plugin_hooks\\s*=/.test(trimmed)) {
10067
+ if (/^features\\.hooks\\s*=/.test(trimmed)) {
9935
10068
  topLevelPluginHooksDotted = index
9936
10069
  }
9937
10070
  if (/^features\\s*=\\s*\\{/.test(trimmed)) {
@@ -9950,28 +10083,28 @@ if (start >= 0) {
9950
10083
 
9951
10084
  let updated = false
9952
10085
  for (let index = start + 1; index < end; index += 1) {
9953
- if (/^plugin_hooks\\s*=/.test(stripTomlComment(lines[index]).trim())) {
9954
- lines[index] = 'plugin_hooks = true'
10086
+ if (/^hooks\\s*=/.test(stripTomlComment(lines[index]).trim())) {
10087
+ lines[index] = 'hooks = true'
9955
10088
  updated = true
9956
10089
  }
9957
10090
  }
9958
- if (!updated) lines.splice(start + 1, 0, 'plugin_hooks = true')
10091
+ if (!updated) lines.splice(start + 1, 0, 'hooks = true')
9959
10092
  } else if (topLevelPluginHooksDotted >= 0) {
9960
- lines[topLevelPluginHooksDotted] = 'features.plugin_hooks = true'
10093
+ lines[topLevelPluginHooksDotted] = 'features.hooks = true'
9961
10094
  } else if (firstTopLevelFeaturesDotted >= 0) {
9962
- lines.splice(firstTopLevelFeaturesDotted + 1, 0, 'features.plugin_hooks = true')
10095
+ lines.splice(firstTopLevelFeaturesDotted + 1, 0, 'features.hooks = true')
9963
10096
  } else if (topLevelInlineFeatures >= 0 && lines[topLevelInlineFeatures].includes('}')) {
9964
- if (/\\bplugin_hooks\\s*=/.test(lines[topLevelInlineFeatures])) {
10097
+ if (/\\bhooks\\s*=/.test(lines[topLevelInlineFeatures])) {
9965
10098
  lines[topLevelInlineFeatures] = lines[topLevelInlineFeatures].replace(
9966
- /\\bplugin_hooks\\s*=\\s*(true|false)\\b/i,
9967
- 'plugin_hooks = true',
10099
+ /\\bhooks\\s*=\\s*(true|false)\\b/i,
10100
+ 'hooks = true',
9968
10101
  )
9969
10102
  } else {
9970
- lines[topLevelInlineFeatures] = lines[topLevelInlineFeatures].replace(/}/, ', plugin_hooks = true }')
10103
+ lines[topLevelInlineFeatures] = lines[topLevelInlineFeatures].replace(/}/, ', hooks = true }')
9971
10104
  }
9972
10105
  } else {
9973
10106
  if (lines.length > 0 && lines[lines.length - 1] !== '') lines.push('')
9974
- lines.push('[features]', 'plugin_hooks = true')
10107
+ lines.push('[features]', 'hooks = true')
9975
10108
  }
9976
10109
 
9977
10110
  fs.mkdirSync(path.dirname(filepath), { recursive: true })
@@ -9982,7 +10115,7 @@ NODE
9982
10115
  else
9983
10116
  echo "Codex plugin-bundled hooks are not enabled. Startup hooks from this plugin will not run until you add this to $CODEX_CONFIG_PATH:" >&2
9984
10117
  echo "[features]" >&2
9985
- echo "plugin_hooks = true" >&2
10118
+ echo "hooks = true" >&2
9986
10119
  echo "Then restart or refresh Codex before relying on plugin startup hooks." >&2
9987
10120
  echo "Set PLUXX_CODEX_ENABLE_PLUGIN_HOOKS=1 before running this installer to enable it noninteractively." >&2
9988
10121
  fi
@@ -10621,7 +10754,7 @@ import { resolve as resolve5 } from "path";
10621
10754
  import { spawn, spawnSync as spawnSync2 } from "child_process";
10622
10755
  import { accessSync, constants, existsSync as existsSync4, lstatSync, readFileSync as readFileSync4, readdirSync as readdirSync2, realpathSync } from "fs";
10623
10756
  import { homedir } from "os";
10624
- import { basename, dirname as dirname2, resolve as resolve4 } from "path";
10757
+ import { basename as basename2, dirname as dirname2, relative as relative2, resolve as resolve4 } from "path";
10625
10758
 
10626
10759
  // node_modules/jiti/lib/jiti.mjs
10627
10760
  var import_jiti = __toESM(require_jiti(), 1);
@@ -10634,7 +10767,7 @@ var CONFIG_FILES = [
10634
10767
  ];
10635
10768
 
10636
10769
  // src/cli/install.ts
10637
- import { resolve as resolve3, dirname } from "path";
10770
+ import { resolve as resolve3, dirname, basename, relative } from "path";
10638
10771
  import { existsSync as existsSync3, symlinkSync, mkdirSync as mkdirSync2, rmSync as rmSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2, cpSync, readdirSync } from "fs";
10639
10772
 
10640
10773
  // src/mcp-stdio-paths.ts
@@ -10779,7 +10912,7 @@ function resolveBundleReference(rootDir, value) {
10779
10912
  if (isRelativeBundlePath(value)) {
10780
10913
  return resolve3(rootDir, value);
10781
10914
  }
10782
- const pluginRootMatch = value.match(/^\$\{(?:CLAUDE_PLUGIN_ROOT|CURSOR_PLUGIN_ROOT|PLUGIN_ROOT)\}[\\/](.+)$/);
10915
+ const pluginRootMatch = value.match(/^\$\{(?:CLAUDE_PLUGIN_ROOT|CURSOR_PLUGIN_ROOT|CODEX_PLUGIN_ROOT|OPENCODE_PLUGIN_ROOT|PLUGIN_ROOT|PLUXX_PLUGIN_ROOT)\}[\\/](.+)$/);
10783
10916
  if (pluginRootMatch) {
10784
10917
  return resolve3(rootDir, pluginRootMatch[1]);
10785
10918
  }
@@ -10822,9 +10955,43 @@ function collectHookCommandStrings(value, commands) {
10822
10955
  }
10823
10956
  }
10824
10957
  function extractBundleCommandTargets(command) {
10825
- const matches = command.match(/\$\{(?:CLAUDE_PLUGIN_ROOT|CURSOR_PLUGIN_ROOT|PLUGIN_ROOT)\}[\\/][^\s"'`;$|&<>]+|\.\.?[\\/][^\s"'`;$|&<>]+/g);
10958
+ const matches = command.match(/\$\{(?:CLAUDE_PLUGIN_ROOT|CURSOR_PLUGIN_ROOT|CODEX_PLUGIN_ROOT|OPENCODE_PLUGIN_ROOT|PLUGIN_ROOT|PLUXX_PLUGIN_ROOT)\}[\\/][^\s"'`;$|&<>]+|\.\.?[\\/][^\s"'`;$|&<>]+/g);
10826
10959
  return matches ?? [];
10827
10960
  }
10961
+ function extractRelativeBundleCommandTargets(command) {
10962
+ return extractBundleCommandTargets(command).filter(isRelativeBundlePath);
10963
+ }
10964
+ function commandChangesToKnownPluginRoot(command) {
10965
+ return /\bcd\s+["']?\$\{?(?:CLAUDE_PLUGIN_ROOT|CURSOR_PLUGIN_ROOT|CODEX_PLUGIN_ROOT|OPENCODE_PLUGIN_ROOT|PLUGIN_ROOT|PLUXX_PLUGIN_ROOT)\}?/.test(command);
10966
+ }
10967
+ function formatBundleRelativePath(rootDir, filePath) {
10968
+ const relativePath = relative(rootDir, filePath);
10969
+ return relativePath && !relativePath.startsWith("..") ? relativePath : filePath;
10970
+ }
10971
+ function findHookWrapperPaths(rootDir, command) {
10972
+ return extractBundleCommandTargets(command).map((target) => resolveBundleReference(rootDir, target)).filter((target) => {
10973
+ if (!target) return false;
10974
+ return existsSync3(target) && basename(target).startsWith("pluxx-hook-command-") && target.endsWith(".sh");
10975
+ });
10976
+ }
10977
+ function findCodexCwdUnsafeHookCommands(rootDir, commands) {
10978
+ const issues = /* @__PURE__ */ new Set();
10979
+ for (const command of commands) {
10980
+ const relativeTargets = extractRelativeBundleCommandTargets(command);
10981
+ if (relativeTargets.length > 0 && !commandChangesToKnownPluginRoot(command)) {
10982
+ issues.add(`${command} uses cwd-relative bundle target(s): ${relativeTargets.join(", ")}`);
10983
+ }
10984
+ for (const wrapperPath of findHookWrapperPaths(rootDir, command)) {
10985
+ const wrapper = readFileSync3(wrapperPath, "utf-8");
10986
+ const hookCommand = wrapper.match(/^PLUXX_HOOK_COMMAND=(.*)$/m)?.[1] ?? "";
10987
+ const wrapperRelativeTargets = extractRelativeBundleCommandTargets(hookCommand);
10988
+ if (wrapperRelativeTargets.length > 0 && !commandChangesToKnownPluginRoot(wrapper)) {
10989
+ issues.add(`${formatBundleRelativePath(rootDir, wrapperPath)} evaluates cwd-relative bundle target(s): ${wrapperRelativeTargets.join(", ")}`);
10990
+ }
10991
+ }
10992
+ }
10993
+ return [...issues].sort();
10994
+ }
10828
10995
  function resolveInstalledHooksReference(rootDir, platform, manifest) {
10829
10996
  const manifestReference = typeof manifest.hooks === "string" ? manifest.hooks : void 0;
10830
10997
  if (manifestReference) {
@@ -10862,6 +11029,7 @@ function findInstalledBundleIntegrityIssues(rootDir, platform) {
10862
11029
  return {
10863
11030
  missingManifestPaths: [],
10864
11031
  missingHookTargets: [],
11032
+ cwdUnsafeHookCommands: [],
10865
11033
  invalidRuntimeScripts: []
10866
11034
  };
10867
11035
  }
@@ -10871,6 +11039,7 @@ function findInstalledBundleIntegrityIssues(rootDir, platform) {
10871
11039
  manifestIssue: `missing plugin manifest at ${manifestPath}`,
10872
11040
  missingManifestPaths: [],
10873
11041
  missingHookTargets: [],
11042
+ cwdUnsafeHookCommands: [],
10874
11043
  invalidRuntimeScripts: []
10875
11044
  };
10876
11045
  }
@@ -10882,6 +11051,7 @@ function findInstalledBundleIntegrityIssues(rootDir, platform) {
10882
11051
  manifestIssue: `plugin manifest at ${manifestPath} is not parseable: ${error instanceof Error ? error.message : String(error)}`,
10883
11052
  missingManifestPaths: [],
10884
11053
  missingHookTargets: [],
11054
+ cwdUnsafeHookCommands: [],
10885
11055
  invalidRuntimeScripts: []
10886
11056
  };
10887
11057
  }
@@ -10896,6 +11066,7 @@ function findInstalledBundleIntegrityIssues(rootDir, platform) {
10896
11066
  ...manifestIssue ? { manifestIssue } : {},
10897
11067
  missingManifestPaths,
10898
11068
  missingHookTargets: [],
11069
+ cwdUnsafeHookCommands: [],
10899
11070
  invalidRuntimeScripts: findInstalledRuntimeScriptIssues(rootDir, manifest)
10900
11071
  };
10901
11072
  }
@@ -10904,6 +11075,7 @@ function findInstalledBundleIntegrityIssues(rootDir, platform) {
10904
11075
  ...manifestIssue ? { manifestIssue } : {},
10905
11076
  missingManifestPaths,
10906
11077
  missingHookTargets: [],
11078
+ cwdUnsafeHookCommands: [],
10907
11079
  invalidRuntimeScripts: findInstalledRuntimeScriptIssues(rootDir, manifest)
10908
11080
  };
10909
11081
  }
@@ -10917,10 +11089,12 @@ function findInstalledBundleIntegrityIssues(rootDir, platform) {
10917
11089
  return resolved !== void 0 && !existsSync3(resolved);
10918
11090
  })
10919
11091
  )].sort();
11092
+ const cwdUnsafeHookCommands = platform === "codex" ? findCodexCwdUnsafeHookCommands(rootDir, commands) : [];
10920
11093
  return {
10921
11094
  ...manifestIssue ? { manifestIssue } : {},
10922
11095
  missingManifestPaths,
10923
11096
  missingHookTargets,
11097
+ cwdUnsafeHookCommands,
10924
11098
  invalidRuntimeScripts: findInstalledRuntimeScriptIssues(rootDir, manifest)
10925
11099
  };
10926
11100
  } catch (error) {
@@ -10929,6 +11103,7 @@ function findInstalledBundleIntegrityIssues(rootDir, platform) {
10929
11103
  hookConfigIssue: `hooks config at ${hooksReference} is not parseable: ${error instanceof Error ? error.message : String(error)}`,
10930
11104
  missingManifestPaths,
10931
11105
  missingHookTargets: [],
11106
+ cwdUnsafeHookCommands: [],
10932
11107
  invalidRuntimeScripts: findInstalledRuntimeScriptIssues(rootDir, manifest)
10933
11108
  };
10934
11109
  }
@@ -11114,7 +11289,7 @@ var MUTATING_PREFIX_PATTERN = new RegExp(`^(${MUTATING_PREFIXES.join("|")})\\b`,
11114
11289
  // src/codex-hooks-feature.ts
11115
11290
  var RECOMMENDED_CODEX_HOOKS_FEATURE_FLAG = "hooks";
11116
11291
  var ALTERNATE_CODEX_HOOKS_FEATURE_FLAG = "codex_hooks";
11117
- var PLUGIN_BUNDLED_CODEX_HOOKS_FEATURE_FLAG = "plugin_hooks";
11292
+ var PLUGIN_BUNDLED_CODEX_HOOKS_FEATURE_FLAG = "hooks";
11118
11293
  function getCodexHooksFeatureState(features) {
11119
11294
  if (!features) {
11120
11295
  return {
@@ -11292,12 +11467,119 @@ function splitTomlDottedPath(value) {
11292
11467
  var MATERIALIZED_ENV_MARKER = "materialized required config";
11293
11468
  var MIN_NODE_MAJOR = 18;
11294
11469
  var STDIO_LAUNCH_SMOKE_TIMEOUT_MS = 1200;
11470
+ var MAX_SECRET_SCAN_FILE_BYTES = 512 * 1024;
11471
+ var LEGACY_SECRET_IDENTIFIER_PATTERN = /(^|[-_])(api[-_]?key|secret|token|password|credential)([-_]|$)/i;
11472
+ var TEST_SECRET_SENTINELS = [
11473
+ { pattern: /\bshh-secret\b/i, label: "test secret sentinel" },
11474
+ { pattern: /\bsecret-key\b/i, label: "test secret sentinel" },
11475
+ { pattern: /\bliteral-secret-value-that-should-not-copy\b/i, label: "test secret sentinel" }
11476
+ ];
11295
11477
  function renderInstalledPluginRoot(value, rootDir) {
11296
11478
  return value.replaceAll("${PLUGIN_ROOT}", rootDir).replaceAll("${CLAUDE_PLUGIN_ROOT}", rootDir).replaceAll("${CURSOR_PLUGIN_ROOT}", rootDir).replaceAll("${CODEX_PLUGIN_ROOT}", rootDir).replaceAll("${OPENCODE_PLUGIN_ROOT}", rootDir);
11297
11479
  }
11298
11480
  function addCheck(checks, check) {
11299
11481
  checks.push(check);
11300
11482
  }
11483
+ function looksLegacySecretIdentifier(value) {
11484
+ return LEGACY_SECRET_IDENTIFIER_PATTERN.test(
11485
+ value.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[._/\s]+/g, "-")
11486
+ );
11487
+ }
11488
+ function walkInstalledBundleFiles(rootDir, currentDir = rootDir) {
11489
+ const files = [];
11490
+ for (const entry of readdirSync2(currentDir, { withFileTypes: true })) {
11491
+ const absolutePath = resolve4(currentDir, entry.name);
11492
+ if (entry.isDirectory()) {
11493
+ files.push(...walkInstalledBundleFiles(rootDir, absolutePath));
11494
+ continue;
11495
+ }
11496
+ if (!entry.isFile()) continue;
11497
+ files.push(relative2(rootDir, absolutePath).replace(/\\/g, "/"));
11498
+ }
11499
+ return files.sort();
11500
+ }
11501
+ function readInstalledTextFile(rootDir, relativePath) {
11502
+ const absolutePath = resolve4(rootDir, relativePath);
11503
+ const content = readFileSync4(absolutePath);
11504
+ if (content.length > MAX_SECRET_SCAN_FILE_BYTES) return null;
11505
+ if (content.includes(0)) return null;
11506
+ return content.toString("utf-8");
11507
+ }
11508
+ function collectInstalledPlaintextSecretCandidates(rootDir) {
11509
+ const userConfigPath = resolve4(rootDir, ".pluxx-user.json");
11510
+ if (!existsSync4(userConfigPath)) return [];
11511
+ try {
11512
+ const payload = JSON.parse(readFileSync4(userConfigPath, "utf-8"));
11513
+ if (payload.secretStorage === "materialized") return [];
11514
+ const secretKeys = new Set(Array.isArray(payload.secretKeys) ? payload.secretKeys.filter((value) => typeof value === "string") : []);
11515
+ const secretEnv = new Set(Array.isArray(payload.secretEnv) ? payload.secretEnv.filter((value) => typeof value === "string") : []);
11516
+ const hasSecretMetadata = secretKeys.size > 0 || secretEnv.size > 0;
11517
+ const candidateLabels = /* @__PURE__ */ new Map();
11518
+ const recordCandidate = (rawValue, label) => {
11519
+ if (typeof rawValue !== "string") return;
11520
+ const value = rawValue.trim();
11521
+ if (!value) return;
11522
+ if (extractEnvReference(value)) return;
11523
+ if (isPlaceholderSecretValue(value)) return;
11524
+ const labels = candidateLabels.get(value) ?? /* @__PURE__ */ new Set();
11525
+ labels.add(label);
11526
+ candidateLabels.set(value, labels);
11527
+ };
11528
+ for (const [key, value] of Object.entries(payload.values ?? {})) {
11529
+ if (hasSecretMetadata ? !secretKeys.has(key) : !looksLegacySecretIdentifier(key)) continue;
11530
+ recordCandidate(value, `userConfig "${key}"`);
11531
+ }
11532
+ for (const [key, value] of Object.entries(payload.env ?? {})) {
11533
+ if (hasSecretMetadata ? !secretEnv.has(key) : !looksLegacySecretIdentifier(key)) continue;
11534
+ recordCandidate(value, `env "${key}"`);
11535
+ }
11536
+ return [...candidateLabels.entries()].map(([value, labels]) => ({
11537
+ value,
11538
+ labels: [...labels].sort()
11539
+ }));
11540
+ } catch {
11541
+ return [];
11542
+ }
11543
+ }
11544
+ function checkInstalledPlaintextSecrets(checks, rootDir) {
11545
+ const literalCandidates = collectInstalledPlaintextSecretCandidates(rootDir);
11546
+ const findings = /* @__PURE__ */ new Map();
11547
+ for (const relativePath of walkInstalledBundleFiles(rootDir)) {
11548
+ const content = readInstalledTextFile(rootDir, relativePath);
11549
+ if (!content) continue;
11550
+ const labels = /* @__PURE__ */ new Set();
11551
+ for (const candidate of literalCandidates) {
11552
+ if (!content.includes(candidate.value)) continue;
11553
+ for (const label of candidate.labels) labels.add(label);
11554
+ }
11555
+ for (const sentinel of TEST_SECRET_SENTINELS) {
11556
+ if (sentinel.pattern.test(content)) {
11557
+ labels.add(sentinel.label);
11558
+ }
11559
+ }
11560
+ if (labels.size > 0) {
11561
+ findings.set(relativePath, labels);
11562
+ }
11563
+ }
11564
+ if (findings.size === 0) {
11565
+ addCheck(checks, {
11566
+ level: "success",
11567
+ code: "consumer-plaintext-secret-absent",
11568
+ title: "Installed bundle does not expose known plaintext secret material",
11569
+ detail: "No known prompted secret values or maintained test-secret sentinels were detected in scanned installed text files.",
11570
+ fix: "No action needed."
11571
+ });
11572
+ return;
11573
+ }
11574
+ const detail = [...findings.entries()].map(([path, labels]) => `${path} (${[...labels].sort().join(", ")})`).join("; ");
11575
+ addCheck(checks, {
11576
+ level: "error",
11577
+ code: "consumer-plaintext-secret-leak",
11578
+ title: "Installed bundle contains plaintext secret material",
11579
+ detail: `Detected plaintext secret material in installed files: ${detail}.`,
11580
+ fix: "Rotate the affected credential, remove the leaking install, reinstall after the Pluxx secret-reference fix, and rerun pluxx doctor --consumer."
11581
+ });
11582
+ }
11301
11583
  function summarizeChecks(checks) {
11302
11584
  const visibleChecks = checks.filter((check) => !check.code.startsWith("primitive-"));
11303
11585
  const errors = visibleChecks.filter((check) => check.level === "error").length;
@@ -11432,7 +11714,7 @@ function readCodexHooksFeatureFlag(filePath) {
11432
11714
  inFeaturesTable = sectionMatch[1].trim() === "features";
11433
11715
  continue;
11434
11716
  }
11435
- const dottedFeatureMatch = line.match(/^features\.(plugin_hooks|hooks|codex_hooks)\s*=\s*(.+)$/);
11717
+ const dottedFeatureMatch = line.match(/^features\.(hooks|codex_hooks)\s*=\s*(.+)$/);
11436
11718
  if (dottedFeatureMatch) {
11437
11719
  assignFeatureFlag(dottedFeatureMatch[1], dottedFeatureMatch[2]);
11438
11720
  continue;
@@ -11449,7 +11731,7 @@ function readCodexHooksFeatureFlag(filePath) {
11449
11731
  continue;
11450
11732
  }
11451
11733
  if (!inFeaturesTable) continue;
11452
- const featureMatch = line.match(/^(plugin_hooks|hooks|codex_hooks)\s*=\s*(.+)$/);
11734
+ const featureMatch = line.match(/^(hooks|codex_hooks)\s*=\s*(.+)$/);
11453
11735
  if (!featureMatch) continue;
11454
11736
  assignFeatureFlag(featureMatch[1], featureMatch[2]);
11455
11737
  }
@@ -11638,7 +11920,7 @@ function listClaudeSettingsCandidates(projectRoot) {
11638
11920
  });
11639
11921
  }
11640
11922
  function readClaudeDisableAllHooks(filePath) {
11641
- const parsed = readJsonFile(dirname2(filePath), basename(filePath));
11923
+ const parsed = readJsonFile(dirname2(filePath), basename2(filePath));
11642
11924
  return typeof parsed.disableAllHooks === "boolean" ? parsed.disableAllHooks : void 0;
11643
11925
  }
11644
11926
  function probeClaudeDisableAllHooks(projectRoot) {
@@ -11981,6 +12263,9 @@ function checkInstalledBundleIntegrity(checks, rootDir, layout) {
11981
12263
  if (issues.missingHookTargets.length > 0) {
11982
12264
  details.push(`hook commands reference missing bundle target${issues.missingHookTargets.length === 1 ? "" : "s"}: ${issues.missingHookTargets.join(", ")}`);
11983
12265
  }
12266
+ if (issues.cwdUnsafeHookCommands.length > 0) {
12267
+ details.push(`Codex hook command${issues.cwdUnsafeHookCommands.length === 1 ? " depends" : "s depend"} on plugin-root cwd: ${issues.cwdUnsafeHookCommands.join("; ")}`);
12268
+ }
11984
12269
  if (issues.invalidRuntimeScripts.length > 0) {
11985
12270
  details.push(`runtime startup still depends on installer-owned validation: ${issues.invalidRuntimeScripts.join(", ")}`);
11986
12271
  }
@@ -12000,7 +12285,7 @@ function checkInstalledBundleIntegrity(checks, rootDir, layout) {
12000
12285
  code: "consumer-bundle-integrity-invalid",
12001
12286
  title: "Installed bundle integrity is broken",
12002
12287
  detail: details.join("; "),
12003
- fix: "Reinstall the plugin or rebuild the bundle so every manifest path, hook config, and hook target is valid inside the installed plugin.",
12288
+ fix: "Reinstall the plugin or rebuild the bundle so every manifest path, hook config, and hook command resolves from the installed plugin root.",
12004
12289
  path: layout.manifestPath
12005
12290
  });
12006
12291
  }
@@ -12065,31 +12350,31 @@ function checkInstalledCodexHooksFeatureFlag(checks, rootDir, layout, options) {
12065
12350
  const enabledProbes = probes.filter((probe) => probe.enabled);
12066
12351
  const checkedPaths = probes.map((probe) => `${probe.scope} ${probe.exists ? "config" : "path"}: ${probe.path}${probe.exists ? "" : " (missing)"}`).join("; ");
12067
12352
  const describeEnabledFlags = (probe) => {
12068
- const enabledFlags = [];
12069
- if (probe.pluginBundledEnabled) enabledFlags.push(PLUGIN_BUNDLED_CODEX_HOOKS_FEATURE_FLAG);
12070
- if (probe.recommendedEnabled) enabledFlags.push(RECOMMENDED_CODEX_HOOKS_FEATURE_FLAG);
12071
- if (probe.alternateEnabled) enabledFlags.push(ALTERNATE_CODEX_HOOKS_FEATURE_FLAG);
12072
- return enabledFlags.join(" + ");
12353
+ const enabledFlags = /* @__PURE__ */ new Set();
12354
+ if (probe.pluginBundledEnabled) enabledFlags.add(PLUGIN_BUNDLED_CODEX_HOOKS_FEATURE_FLAG);
12355
+ if (probe.recommendedEnabled) enabledFlags.add(RECOMMENDED_CODEX_HOOKS_FEATURE_FLAG);
12356
+ if (probe.alternateEnabled) enabledFlags.add(ALTERNATE_CODEX_HOOKS_FEATURE_FLAG);
12357
+ return [...enabledFlags].join(" + ");
12073
12358
  };
12074
12359
  const generalOnlyProbes = probes.filter((probe) => !probe.pluginBundledEnabled && (probe.recommendedEnabled || probe.alternateEnabled));
12075
12360
  if (generalOnlyProbes.length > 0) {
12076
12361
  addCheck(checks, {
12077
12362
  level: "warning",
12078
12363
  code: "consumer-codex-plugin-hooks-feature-flag-general-only",
12079
- title: "Codex config enables only general hook flags, not the plugin-bundled hook gate",
12080
- detail: `This installed Codex bundle declares plugin-bundled hooks at ${hooksReference}, but ${generalOnlyProbes.map((probe) => `${probe.scope} config ${probe.path}`).join(" and ")} enables only general hook flags (${generalOnlyProbes.map((probe) => describeEnabledFlags(probe)).join(" and ")}). Those general flags do not activate plugin-bundled hooks by themselves.`,
12081
- fix: `Enable \`${PLUGIN_BUNDLED_CODEX_HOOKS_FEATURE_FLAG} = true\` under \`[features]\` in the active Codex config, reload Codex, and rerun pluxx verify-install.`,
12364
+ title: "Codex config enables only deprecated hook flags",
12365
+ detail: `This installed Codex bundle declares plugin-bundled hooks at ${hooksReference}, but ${generalOnlyProbes.map((probe) => `${probe.scope} config ${probe.path}`).join(" and ")} enables only deprecated hook flags (${generalOnlyProbes.map((probe) => describeEnabledFlags(probe)).join(" and ")}). Current Codex docs use \`${RECOMMENDED_CODEX_HOOKS_FEATURE_FLAG}\` as the canonical feature key.`,
12366
+ fix: `Enable \`${RECOMMENDED_CODEX_HOOKS_FEATURE_FLAG} = true\` under \`[features]\` in the active Codex config, reload Codex, and rerun pluxx verify-install.`,
12082
12367
  path: generalOnlyProbes[0].path
12083
12368
  });
12084
12369
  }
12085
- const legacyOnlyProbes = probes.filter((probe) => probe.alternateEnabled && !probe.recommendedEnabled);
12370
+ const legacyOnlyProbes = probes.filter((probe) => !probe.pluginBundledEnabled && probe.alternateEnabled && !probe.recommendedEnabled);
12086
12371
  if (legacyOnlyProbes.length > 0) {
12087
12372
  addCheck(checks, {
12088
12373
  level: "warning",
12089
12374
  code: "consumer-codex-hooks-feature-flag-legacy-only",
12090
12375
  title: "Codex config still uses the deprecated general hook compatibility flag",
12091
- detail: `This installed Codex bundle declares plugin-bundled hooks at ${hooksReference}, and the checked Codex config enables only the deprecated general hook flag \`${ALTERNATE_CODEX_HOOKS_FEATURE_FLAG} = true\` in ${legacyOnlyProbes.map((probe) => `${probe.scope} config ${probe.path}`).join(" and ")}. That flag is not the plugin-bundled hook gate.`,
12092
- fix: `Prefer \`${RECOMMENDED_CODEX_HOOKS_FEATURE_FLAG} = true\` only for non-plugin hook config if needed, and use \`${PLUGIN_BUNDLED_CODEX_HOOKS_FEATURE_FLAG} = true\` under \`[features]\` for plugin-bundled hooks. Reload Codex and rerun pluxx verify-install after updating the active config.`,
12376
+ detail: `This installed Codex bundle declares plugin-bundled hooks at ${hooksReference}, and the checked Codex config enables only the deprecated hook flag \`${ALTERNATE_CODEX_HOOKS_FEATURE_FLAG} = true\` in ${legacyOnlyProbes.map((probe) => `${probe.scope} config ${probe.path}`).join(" and ")}. Current Codex docs use \`${RECOMMENDED_CODEX_HOOKS_FEATURE_FLAG}\` as the canonical feature key.`,
12377
+ fix: `Use \`${RECOMMENDED_CODEX_HOOKS_FEATURE_FLAG} = true\` under \`[features]\`, reload Codex, and rerun pluxx verify-install after updating the active config.`,
12093
12378
  path: legacyOnlyProbes[0].path
12094
12379
  });
12095
12380
  }
@@ -12097,8 +12382,8 @@ function checkInstalledCodexHooksFeatureFlag(checks, rootDir, layout, options) {
12097
12382
  addCheck(checks, {
12098
12383
  level: "success",
12099
12384
  code: "consumer-codex-hooks-feature-flag-enabled",
12100
- title: "Codex plugin-bundled hook feature flag found for this install",
12101
- detail: `This installed Codex bundle declares plugin-bundled hooks at ${hooksReference}, and the plugin hook gate was found in ${enabledProbes.map((probe) => `${probe.scope} config ${probe.path} (${describeEnabledFlags(probe)})`).join(" and ")}. Treat that as a prerequisite, not proof of live hook execution.`,
12385
+ title: "Codex hook feature flag found for this install",
12386
+ detail: `This installed Codex bundle declares plugin-bundled hooks at ${hooksReference}, and the canonical hook feature key was found in ${enabledProbes.map((probe) => `${probe.scope} config ${probe.path} (${describeEnabledFlags(probe)})`).join(" and ")}. Treat that as a prerequisite, not proof of live hook execution.`,
12102
12387
  fix: "No action needed.",
12103
12388
  path: enabledProbes[0].path
12104
12389
  });
@@ -12109,8 +12394,8 @@ function checkInstalledCodexHooksFeatureFlag(checks, rootDir, layout, options) {
12109
12394
  level: "warning",
12110
12395
  code: "consumer-codex-hooks-feature-flag-missing",
12111
12396
  title: "Codex plugin-bundled hook activation is missing its known feature-gate prerequisite",
12112
- detail: `This installed Codex bundle declares plugin-bundled hooks at ${hooksReference}, but \`${PLUGIN_BUNDLED_CODEX_HOOKS_FEATURE_FLAG} = true\` was not found in the checked Codex config layers. The general \`${RECOMMENDED_CODEX_HOOKS_FEATURE_FLAG}\` flag covers non-plugin hook config and defaults on, while \`${ALTERNATE_CODEX_HOOKS_FEATURE_FLAG}\` is deprecated and should not be treated as a plugin-bundled hook fallback. Checked ${checkedPaths}.${parseErrors.length > 0 ? ` Unparseable config: ${parseErrors.join("; ")}.` : ""}`,
12113
- fix: `Enable \`${PLUGIN_BUNDLED_CODEX_HOOKS_FEATURE_FLAG} = true\` under \`[features]\` in the relevant project or user Codex config, reload Codex, and rerun pluxx verify-install.`,
12397
+ detail: `This installed Codex bundle declares plugin-bundled hooks at ${hooksReference}, but \`${RECOMMENDED_CODEX_HOOKS_FEATURE_FLAG} = true\` was not found in the checked Codex config layers. \`${ALTERNATE_CODEX_HOOKS_FEATURE_FLAG}\` is deprecated and should not be treated as the current hook feature key. Checked ${checkedPaths}.${parseErrors.length > 0 ? ` Unparseable config: ${parseErrors.join("; ")}.` : ""}`,
12398
+ fix: `Enable \`${RECOMMENDED_CODEX_HOOKS_FEATURE_FLAG} = true\` under \`[features]\` in the relevant project or user Codex config, reload Codex, and rerun pluxx verify-install.`,
12114
12399
  path: probes.find((probe) => probe.exists)?.path ?? hooksReference
12115
12400
  });
12116
12401
  }
@@ -12473,7 +12758,7 @@ function isLikelyLocalRuntimePath(value) {
12473
12758
  function isLikelyOpenCodeInstallPath(rootDir) {
12474
12759
  const parent = dirname2(rootDir);
12475
12760
  const grandparent = dirname2(parent);
12476
- return basename(parent) === "plugins" && basename(grandparent) === "opencode";
12761
+ return basename2(parent) === "plugins" && basename2(grandparent) === "opencode";
12477
12762
  }
12478
12763
  function checkInstalledOpenCodeHostBridge(checks, rootDir) {
12479
12764
  if (!isLikelyOpenCodeInstallPath(rootDir)) {
@@ -12487,7 +12772,7 @@ function checkInstalledOpenCodeHostBridge(checks, rootDir) {
12487
12772
  });
12488
12773
  return;
12489
12774
  }
12490
- const pluginName = basename(rootDir);
12775
+ const pluginName = basename2(rootDir);
12491
12776
  const entryPath = `${rootDir}.ts`;
12492
12777
  const entryRelativePath = `${pluginName}.ts`;
12493
12778
  if (!existsSync4(entryPath)) {
@@ -12528,7 +12813,7 @@ function checkInstalledOpenCodeSkills(checks, rootDir) {
12528
12813
  if (!isLikelyOpenCodeInstallPath(rootDir)) {
12529
12814
  return;
12530
12815
  }
12531
- const pluginName = basename(rootDir);
12816
+ const pluginName = basename2(rootDir);
12532
12817
  const sourceSkillsDir = resolve4(rootDir, "skills");
12533
12818
  if (!existsSync4(sourceSkillsDir)) {
12534
12819
  addCheck(checks, {
@@ -12644,6 +12929,7 @@ async function doctorConsumer(rootDir = process.cwd(), options = {}) {
12644
12929
  checkInstalledCodexPermissionCompanion(checks, rootDir, layout, options);
12645
12930
  checkInstalledPermissionHook(checks, rootDir, layout);
12646
12931
  checkInstalledUserConfig(checks, rootDir);
12932
+ checkInstalledPlaintextSecrets(checks, rootDir);
12647
12933
  checkInstalledEnvValidation(checks, rootDir);
12648
12934
  checkInstalledRuntimeScriptRoles(checks, rootDir);
12649
12935
  await checkInstalledMcpConfig(checks, rootDir, layout);