@orchid-labs/pluxx 0.1.22 → 0.1.24

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.
Files changed (36) hide show
  1. package/README.md +2 -0
  2. package/bin/pluxx.js +1 -6
  3. package/dist/agent-translation-registry.d.ts +9 -1
  4. package/dist/agent-translation-registry.d.ts.map +1 -1
  5. package/dist/bundle-check.d.ts +21 -0
  6. package/dist/bundle-check.d.ts.map +1 -0
  7. package/dist/claude-hook-probe.d.ts.map +1 -1
  8. package/dist/cli/agent.d.ts.map +1 -1
  9. package/dist/cli/codex-apply.d.ts +39 -0
  10. package/dist/cli/codex-apply.d.ts.map +1 -0
  11. package/dist/cli/index.d.ts.map +1 -1
  12. package/dist/cli/index.js +30809 -6240
  13. package/dist/cli/install.d.ts.map +1 -1
  14. package/dist/cli/publish.d.ts.map +1 -1
  15. package/dist/cli/sync-from-mcp.d.ts +1 -1
  16. package/dist/cli/sync-from-mcp.d.ts.map +1 -1
  17. package/dist/cli/verify-install.d.ts.map +1 -1
  18. package/dist/codex-mcp-probe.d.ts.map +1 -1
  19. package/dist/command-translation-registry.d.ts +11 -3
  20. package/dist/command-translation-registry.d.ts.map +1 -1
  21. package/dist/distribution-lifecycle.d.ts +18 -0
  22. package/dist/distribution-lifecycle.d.ts.map +1 -1
  23. package/dist/generators/codex/index.d.ts.map +1 -1
  24. package/dist/generators/index.d.ts.map +1 -1
  25. package/dist/hook-command-env.d.ts +1 -0
  26. package/dist/hook-command-env.d.ts.map +1 -1
  27. package/dist/host-detection.d.ts +38 -0
  28. package/dist/host-detection.d.ts.map +1 -0
  29. package/dist/index.d.ts +3 -0
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +865 -172
  32. package/dist/install-reference.d.ts +34 -0
  33. package/dist/install-reference.d.ts.map +1 -0
  34. package/dist/schema.d.ts +36 -36
  35. package/dist/validation/platform-rules.d.ts.map +1 -1
  36. package/package.json +5 -2
package/dist/index.js CHANGED
@@ -7841,6 +7841,61 @@ function definePlugin(config) {
7841
7841
  return PluginConfigSchema.parse(config);
7842
7842
  }
7843
7843
 
7844
+ // src/agent-translation-registry.ts
7845
+ var CODEX_AGENT_CAPABILITY_NOTE = 'Codex custom agents and subagents are real native surfaces, but they are not packaged the same way as Claude or Cursor plugin agents. Local May 13, 2026 headless probes prove explicit invocation, built-in-name override, project-local precedence, and discovered `.agents/skills` inheritance. The same maintained headless suite also showed two config-depth caveats: a parent `[[skills.config]] enabled = false` entry did not disable a discovered project skill, and an agent-local `[[skills.config]]` entry did not preload an undiscovered `skills/` path. The maintained `bun scripts/probe-codex-mcp-runtime.ts --json` headless probe was rerun on June 24, 2026: default project-scoped and user-scoped root MCP both emit a real `mcp_tool_call` item but fail with `user cancelled MCP tool call` before server-side `tools/call`; explicit `[mcp_servers.<id>.tools.<tool>] approval_mode = "approve"` unlocks project-scoped and user-scoped root MCP; custom agents that inherit those approved root MCP servers also reach `tools/call` and return `MCP_PROOF_MARKER_ALLOWED`. The June 24 run also shows that `mcp_servers = {}` in the child agent does not opt out of approved inherited root MCP for either project or user scope, while agent-local inline MCP and agent-local inline approval did not activate. Current custom-agent MCP success now surfaces a parent `mcp_tool_call` item in the `codex exec --json` stream, but `codex mcp list` still sees user-scoped servers and not project-scoped servers. The maintained `bun scripts/probe-codex-agents-interactive-runtime.ts --json` trusted interactive probe also showed the same `sandbox_mode = "read-only"` child agent still wrote to the workspace there too, so these fields are not yet uniformly trustworthy runtime boundaries.';
7846
+ var AGENT_PRIMITIVE_CAPABILITIES = {
7847
+ "claude-code": {
7848
+ mode: "preserve",
7849
+ nativeSurfaces: ["agents/*.md"],
7850
+ notes: "Claude plugin agents are a first-class native surface with rich frontmatter."
7851
+ },
7852
+ cursor: {
7853
+ mode: "translate",
7854
+ nativeSurfaces: ["agents/", ".cursor/agents/", "~/.cursor/agents/"],
7855
+ notes: "Cursor specialization and tool access often live more naturally in subagents than in skills."
7856
+ },
7857
+ codex: {
7858
+ mode: "translate",
7859
+ nativeSurfaces: [".codex/agents/*.toml", "~/.codex/agents/*.toml", "subagent workflows"],
7860
+ notes: CODEX_AGENT_CAPABILITY_NOTE
7861
+ },
7862
+ opencode: {
7863
+ mode: "preserve",
7864
+ nativeSurfaces: ["agents/*.md", "config agent definitions"],
7865
+ notes: "OpenCode agents are first-class native surfaces. Prefer permission-first agent config for new builds; legacy tools remains compatibility input, not the preferred emitted shape."
7866
+ }
7867
+ };
7868
+ function getAgentPrimitiveCapability(platform) {
7869
+ return AGENT_PRIMITIVE_CAPABILITIES[platform];
7870
+ }
7871
+
7872
+ // src/command-translation-registry.ts
7873
+ var CODEX_COMMAND_GUIDANCE_SURFACES = ["skills/", "AGENTS.md", ".codex/commands.generated.json"];
7874
+ var CODEX_COMMAND_GUIDANCE_NOTE = "Current Codex docs do not document plugin-packaged slash-command parity, so Pluxx keeps canonical command intent through routing guidance plus a generated companion mirror.";
7875
+ var COMMAND_PRIMITIVE_CAPABILITIES = {
7876
+ "claude-code": {
7877
+ mode: "preserve",
7878
+ nativeSurfaces: ["commands/*.md", "skills/<skill>/SKILL.md"],
7879
+ notes: "Claude still supports command files, but the product is increasingly converging command workflows into skills."
7880
+ },
7881
+ cursor: {
7882
+ mode: "preserve",
7883
+ nativeSurfaces: ["commands/*", "slash commands"]
7884
+ },
7885
+ codex: {
7886
+ mode: "degrade",
7887
+ nativeSurfaces: [...CODEX_COMMAND_GUIDANCE_SURFACES],
7888
+ notes: CODEX_COMMAND_GUIDANCE_NOTE
7889
+ },
7890
+ opencode: {
7891
+ mode: "preserve",
7892
+ nativeSurfaces: ["commands/*.md", "config command definitions"]
7893
+ }
7894
+ };
7895
+ function getCommandPrimitiveCapability(platform) {
7896
+ return COMMAND_PRIMITIVE_CAPABILITIES[platform];
7897
+ }
7898
+
7844
7899
  // src/hook-events.ts
7845
7900
  var CODEX_SUPPORTED_HOOK_EVENTS = [
7846
7901
  "SessionStart",
@@ -7989,6 +8044,7 @@ function getRuntimeReadinessCapability(platform, pluginRootVar = "PLUGIN_ROOT")
7989
8044
  }
7990
8045
 
7991
8046
  // src/validation/platform-rules.ts
8047
+ var CORE_FOUR_PLATFORMS = ["claude-code", "cursor", "codex", "opencode"];
7992
8048
  var STANDARD_SKILL_FRONTMATTER = [
7993
8049
  "name",
7994
8050
  "description",
@@ -8662,14 +8718,10 @@ var CORE_FOUR_PRIMITIVE_CAPABILITIES = {
8662
8718
  nativeSurfaces: ["skills/<skill>/SKILL.md"]
8663
8719
  },
8664
8720
  commands: {
8665
- mode: "preserve",
8666
- nativeSurfaces: ["commands/*.md", "skills/<skill>/SKILL.md"],
8667
- notes: "Claude still supports command files, but the product is increasingly converging command workflows into skills."
8721
+ ...getCommandPrimitiveCapability("claude-code")
8668
8722
  },
8669
8723
  agents: {
8670
- mode: "preserve",
8671
- nativeSurfaces: ["agents/*.md"],
8672
- notes: "Claude plugin agents are a first-class native surface with rich frontmatter."
8724
+ ...getAgentPrimitiveCapability("claude-code")
8673
8725
  },
8674
8726
  hooks: {
8675
8727
  mode: "preserve",
@@ -8705,13 +8757,10 @@ var CORE_FOUR_PRIMITIVE_CAPABILITIES = {
8705
8757
  notes: "Cursor skills preserve workflow meaning but document a narrower frontmatter set than Claude."
8706
8758
  },
8707
8759
  commands: {
8708
- mode: "preserve",
8709
- nativeSurfaces: ["commands/*", "slash commands"]
8760
+ ...getCommandPrimitiveCapability("cursor")
8710
8761
  },
8711
8762
  agents: {
8712
- mode: "translate",
8713
- nativeSurfaces: ["agents/", ".cursor/agents/", "~/.cursor/agents/"],
8714
- notes: "Cursor specialization and tool access often live more naturally in subagents than in skills."
8763
+ ...getAgentPrimitiveCapability("cursor")
8715
8764
  },
8716
8765
  hooks: {
8717
8766
  mode: "preserve",
@@ -8745,14 +8794,10 @@ var CORE_FOUR_PRIMITIVE_CAPABILITIES = {
8745
8794
  nativeSurfaces: ["skills/<skill>/SKILL.md"]
8746
8795
  },
8747
8796
  commands: {
8748
- mode: "degrade",
8749
- nativeSurfaces: ["skills/", "AGENTS.md", ".codex/commands.generated.json"],
8750
- notes: "Current Codex docs do not document plugin-packaged slash-command parity, so Pluxx keeps canonical command intent through routing guidance plus a generated companion mirror."
8797
+ ...getCommandPrimitiveCapability("codex")
8751
8798
  },
8752
8799
  agents: {
8753
- mode: "translate",
8754
- nativeSurfaces: [".codex/agents/*.toml", "~/.codex/agents/*.toml", "subagent workflows"],
8755
- notes: 'Codex custom agents and subagents are real native surfaces, but they are not packaged the same way as Claude or Cursor plugin agents. Local May 13, 2026 headless probes now prove explicit invocation, built-in-name override, project-local precedence, and discovered `.agents/skills` inheritance. The same maintained headless suite also showed two config-depth caveats: a parent `[[skills.config]] enabled = false` entry did not disable a discovered project skill, and an agent-local `[[skills.config]]` entry did not preload an undiscovered `skills/` path. The maintained `bun scripts/probe-codex-mcp-runtime.ts --json` headless probe now also shows a more precise MCP approval split: default project-scoped and user-scoped root MCP both emit a real `mcp_tool_call` item but fail it with `user cancelled MCP tool call` before any server-side `tools/call`, while the default inline-agent path reaches startup plus `tools/list` and then falls back to `MCP_PROOF_MARKER_MISSING`. The same maintained suite now also proves five approved allow-paths: explicit `[mcp_servers.<id>.tools.<tool>] approval_mode = "approve"` works for project-scoped root MCP, user-scoped root MCP, agent-local inline `mcp_servers`, and custom agents that inherit an approved project-scoped or user-scoped root MCP server. All three approved custom-agent MCP paths still avoid a root `mcp_tool_call` item in the parent `codex exec --json` stream and instead surface child `agents_states` moving through `pending_init` to `completed`; project-scoped servers still do not appear in `codex mcp list`, and user-scoped servers do appear there. The maintained `bun scripts/probe-codex-agents-interactive-runtime.ts --json` trusted interactive probe also showed the same `sandbox_mode = "read-only"` child agent still wrote to the workspace there too, so these fields are not yet uniformly trustworthy runtime boundaries.'
8800
+ ...getAgentPrimitiveCapability("codex")
8756
8801
  },
8757
8802
  hooks: {
8758
8803
  mode: "translate",
@@ -8767,7 +8812,7 @@ var CORE_FOUR_PRIMITIVE_CAPABILITIES = {
8767
8812
  runtime: {
8768
8813
  mode: "preserve",
8769
8814
  nativeSurfaces: [".mcp.json", ".app.json", ".codex/config.toml", "scripts/", "assets/"],
8770
- notes: `Bundle-local MCP config exists, but active MCP state also lives in config.toml. Local May 13, 2026 headless Codex MCP probes reached startup plus \`tools/list\` across project-scoped, user-scoped, and inline-agent config; default root MCP emitted a real \`mcp_tool_call\` item but failed it with \`user cancelled MCP tool call\` before any server-side \`tools/call\`, while the default inline-agent path fell back to \`MCP_PROOF_MARKER_MISSING\` after startup plus \`tools/list\`. The same maintained suite now proves five concrete approval paths: project-scoped root MCP, user-scoped root MCP, agent-local inline \`mcp_servers\`, a custom agent inheriting an approved project-scoped root MCP server, and a custom agent inheriting an approved user-scoped root MCP server all reach real server-side \`tools/call\` once explicit \`[mcp_servers.<id>.tools.<tool>] approval_mode = "approve"\` is present in the relevant layer. All three approved custom-agent MCP paths still avoid a root \`mcp_tool_call\` item in the parent \`codex exec --json\` stream and instead surface child \`agents_states\` moving through \`pending_init\` to \`completed\`; project-scoped servers still do not appear in \`codex mcp list\`, and user-scoped servers do appear there. ${getRuntimeReadinessExternalConfigNote()}`
8815
+ notes: `Bundle-local MCP config exists, but active MCP state also lives in config.toml. Local June 24, 2026 headless Codex MCP probes reached startup plus \`tools/list\` for project-scoped and user-scoped root config. Default root MCP emitted a real \`mcp_tool_call\` item but failed with \`user cancelled MCP tool call\` before server-side \`tools/call\`. Explicit per-tool \`approval_mode = "approve"\` now proves four concrete root/inheritance paths: project-scoped root MCP, user-scoped root MCP, a custom agent inheriting an approved project-scoped root MCP server, and a custom agent inheriting an approved user-scoped root MCP server. The same run also proves that an explicit child-agent \`mcp_servers = {}\` does not opt out of those approved inherited root MCP servers. Agent-local inline MCP and agent-local inline approval did not activate in the June 24 run, so least-privilege delegated MCP still belongs in the gap column. Project-scoped servers still do not appear in \`codex mcp list\`, while user-scoped servers do. ${getRuntimeReadinessExternalConfigNote()}`
8771
8816
  },
8772
8817
  distribution: {
8773
8818
  mode: "preserve",
@@ -8789,13 +8834,10 @@ var CORE_FOUR_PRIMITIVE_CAPABILITIES = {
8789
8834
  nativeSurfaces: ["skills/<skill>/SKILL.md"]
8790
8835
  },
8791
8836
  commands: {
8792
- mode: "preserve",
8793
- nativeSurfaces: ["commands/*.md", "config command definitions"]
8837
+ ...getCommandPrimitiveCapability("opencode")
8794
8838
  },
8795
8839
  agents: {
8796
- mode: "preserve",
8797
- nativeSurfaces: ["agents/*.md", "config agent definitions"],
8798
- notes: "OpenCode agents are first-class native surfaces. Prefer permission-first agent config for new builds; legacy tools remains compatibility input, not the preferred emitted shape."
8840
+ ...getAgentPrimitiveCapability("opencode")
8799
8841
  },
8800
8842
  hooks: {
8801
8843
  mode: "translate",
@@ -9137,20 +9179,77 @@ function collectUserConfigEntries(config, platforms = config.targets) {
9137
9179
  }
9138
9180
 
9139
9181
  // src/distribution-lifecycle.ts
9182
+ var CORE_FOUR_INSTALL_DISCOVERY_CAPABILITIES = [
9183
+ {
9184
+ platform: "claude-code",
9185
+ label: "Claude Code",
9186
+ installMethod: "Native Claude plugin install from a generated local marketplace, or the generated release installer.",
9187
+ localInstallPath: "~/.claude/plugins/cache/pluxx-local-<plugin>/<plugin>/<version> after native install; legacy direct installs may still appear at ~/.claude/plugins/<plugin>.",
9188
+ reloadBehavior: "Run /reload-plugins in the active Claude window.",
9189
+ cacheSemantics: "Claude copies the selected marketplace plugin into a versioned plugin cache. Pluxx verifies that cache path rather than assuming the source bundle is live.",
9190
+ discoverySurface: "Claude plugin marketplace/listing commands and the active Claude plugin list after reload.",
9191
+ brandListingSupport: "No shared manifest-backed brand fields from Pluxx brand today; instructions, skills, commands, and assets still ship inside the bundle.",
9192
+ installFollowupNote: "Claude Code note: if Claude is already open, run /reload-plugins to pick up the new install.",
9193
+ publishReloadInstruction: "If Claude is already open, run /reload-plugins."
9194
+ },
9195
+ {
9196
+ platform: "cursor",
9197
+ label: "Cursor",
9198
+ installMethod: "Local plugin install or generated release installer that replaces the local bundle.",
9199
+ localInstallPath: "~/.cursor/plugins/local/<plugin>",
9200
+ reloadBehavior: "Use Developer: Reload Window or restart Cursor.",
9201
+ cacheSemantics: "Pluxx installs the local bundle path directly; no separate Pluxx-managed active cache is modeled.",
9202
+ discoverySurface: "Cursor local plugin directory and host plugin UI after reload or restart.",
9203
+ brandListingSupport: "Narrow shared-brand translation: homepage and logo can be emitted; richer listing copy is not a shared Cursor surface today.",
9204
+ installFollowupNote: "Cursor note: if Cursor is already open, use Developer: Reload Window or restart Cursor to pick up the new install.",
9205
+ publishReloadInstruction: "If Cursor is already open, use Developer: Reload Window or restart Cursor so the plugin is picked up."
9206
+ },
9207
+ {
9208
+ platform: "codex",
9209
+ label: "Codex",
9210
+ installMethod: "Local plugin install plus a local marketplace catalog entry, or the generated release installer.",
9211
+ localInstallPath: "~/.codex/plugins/<plugin> plus a local marketplace catalog entry.",
9212
+ reloadBehavior: "Use Plugins > Refresh when that UI action is available, otherwise restart Codex.",
9213
+ cacheSemantics: "Codex may load a separate active cache under ~/.codex/plugins/cache/local-plugins/<plugin>/<version>. Pluxx clears local cache on install/uninstall and verify-install detects stale cache contents.",
9214
+ discoverySurface: "Codex Plugins view and plugin detail page. Plugin-bundled MCP servers may appear on the plugin detail page without appearing in the global MCP servers settings page.",
9215
+ brandListingSupport: "Richest current shared-brand target: display name, descriptions, category, color, icon/logo, screenshots, default prompt, website, and policy links can be emitted into .codex-plugin/plugin.json interface metadata.",
9216
+ installFollowupNote: "Codex note: if Codex is already open, use Plugins > Refresh if that action is available in your current UI, or restart Codex to pick up the new install. Plugin-bundled MCP servers may appear on the plugin detail page without appearing in the global MCP servers settings page.",
9217
+ publishReloadInstruction: "If Codex is already open, use Plugins > Refresh if that action is available in your current UI, or restart Codex so the plugin is picked up.",
9218
+ verifyStaleAction: "in Codex, use Plugins > Refresh if available, or restart Codex so the plugin cache reloads"
9219
+ },
9220
+ {
9221
+ platform: "opencode",
9222
+ label: "OpenCode",
9223
+ installMethod: "Local plugin directory plus generated entry wrapper, generated release installer, or npm-backed wrapper package path.",
9224
+ localInstallPath: "~/.config/opencode/plugins/<plugin> plus ~/.config/opencode/plugins/<plugin>.ts and synced skills under ~/.config/opencode/skills/<plugin>-<skill>.",
9225
+ reloadBehavior: "Restart or reload OpenCode.",
9226
+ cacheSemantics: "Pluxx writes the local plugin wrapper and synced skill files directly; no separate Pluxx-managed active cache is modeled.",
9227
+ discoverySurface: "OpenCode plugin loader, plugin wrapper file, and synced skill namespace.",
9228
+ brandListingSupport: "No shared manifest-backed brand fields from Pluxx brand today; OpenCode receives functional plugin module, instructions, skills, and package metadata.",
9229
+ installFollowupNote: "OpenCode note: if OpenCode is already open, restart or reload it so the plugin is picked up.",
9230
+ publishReloadInstruction: "If OpenCode is already open, restart or reload it so the plugin is picked up."
9231
+ }
9232
+ ];
9233
+ var CORE_FOUR_CAPABILITIES_BY_PLATFORM = Object.fromEntries(
9234
+ CORE_FOUR_INSTALL_DISCOVERY_CAPABILITIES.map((capability) => [capability.platform, capability])
9235
+ );
9236
+ var CORE_FOUR_PLATFORMS2 = new Set(
9237
+ CORE_FOUR_INSTALL_DISCOVERY_CAPABILITIES.map((capability) => capability.platform)
9238
+ );
9140
9239
  var VERIFY_STALE_ACTIONS = {
9141
- codex: "in Codex, use Plugins > Refresh if available, or restart Codex so the plugin cache reloads"
9142
- };
9143
- var PUBLISH_RELOAD_INSTRUCTIONS = {
9144
- "claude-code": "If Claude is already open, run /reload-plugins in the active session.",
9145
- cursor: "If Cursor is already open, use Developer: Reload Window or restart Cursor so the plugin is picked up.",
9146
- codex: "If Codex is already open, use Plugins > Refresh if that action is available in your current UI, or restart Codex so the plugin is picked up.",
9147
- opencode: "If OpenCode is already open, restart or reload it so the plugin is picked up."
9240
+ codex: CORE_FOUR_CAPABILITIES_BY_PLATFORM.codex.verifyStaleAction
9148
9241
  };
9242
+ function isCoreFourTarget(target) {
9243
+ return CORE_FOUR_PLATFORMS2.has(target);
9244
+ }
9245
+ function getHostInstallDiscoveryCapability(target) {
9246
+ return CORE_FOUR_CAPABILITIES_BY_PLATFORM[target];
9247
+ }
9149
9248
  function getVerifyInstallStaleAction(target) {
9150
9249
  return VERIFY_STALE_ACTIONS[target];
9151
9250
  }
9152
9251
  function getPublishReloadInstruction(target) {
9153
- return PUBLISH_RELOAD_INSTRUCTIONS[target];
9252
+ return isCoreFourTarget(target) ? getHostInstallDiscoveryCapability(target).publishReloadInstruction : void 0;
9154
9253
  }
9155
9254
 
9156
9255
  // src/mcp-native-overrides.ts
@@ -9603,17 +9702,23 @@ process.exit(1)
9603
9702
  NODE
9604
9703
  }
9605
9704
 
9705
+ pluxx_can_prompt_config() {
9706
+ [[ -r /dev/tty && -w /dev/tty ]] && { [[ -t 0 ]] || [[ -t 1 ]] || [[ -t 2 ]]; }
9707
+ }
9708
+
9606
9709
  pluxx_prompt_secret_config() {
9607
9710
  local key="$1"
9608
9711
  local env_var="$2"
9609
9712
  local label="$3"
9610
9713
  local required="$4"
9611
9714
  local current_value="\${!env_var:-}"
9715
+ local saved_value_invalid=0
9612
9716
 
9613
9717
  if [[ -z "$current_value" && "\${PLUXX_RECONFIGURE:-0}" != "1" ]]; then
9614
9718
  local saved_value=""
9615
9719
  if saved_value="$(pluxx_saved_config_value "$key" "$env_var")"; then
9616
9720
  if pluxx_is_placeholder_secret "$saved_value"; then
9721
+ saved_value_invalid=1
9617
9722
  echo "Ignoring placeholder-looking saved config for $env_var." >&2
9618
9723
  else
9619
9724
  current_value="$saved_value"
@@ -9623,11 +9728,15 @@ pluxx_prompt_secret_config() {
9623
9728
  fi
9624
9729
 
9625
9730
  if [[ -z "$current_value" && "$required" == "1" ]]; then
9626
- if [[ -t 0 || -r /dev/tty ]]; then
9731
+ if pluxx_can_prompt_config; then
9627
9732
  read -r -s -p "$label [$env_var]: " current_value </dev/tty
9628
9733
  echo >/dev/tty
9629
9734
  else
9630
- echo "Missing required config: export $env_var before running this installer." >&2
9735
+ if [[ "$saved_value_invalid" == "1" ]]; then
9736
+ echo "Refusing placeholder-looking saved config for $env_var. Set a real value and rerun the installer." >&2
9737
+ else
9738
+ echo "Missing required config: export $env_var before running this installer." >&2
9739
+ fi
9631
9740
  exit 1
9632
9741
  fi
9633
9742
  fi
@@ -9656,7 +9765,7 @@ pluxx_prompt_text_config() {
9656
9765
  fi
9657
9766
 
9658
9767
  if [[ -z "$current_value" && "$required" == "1" ]]; then
9659
- if [[ -t 0 || -r /dev/tty ]]; then
9768
+ if pluxx_can_prompt_config; then
9660
9769
  read -r -p "$label [$env_var]: " current_value </dev/tty
9661
9770
  else
9662
9771
  echo "Missing required config: export $env_var before running this installer." >&2
@@ -9670,7 +9779,7 @@ pluxx_prompt_text_config() {
9670
9779
  ${promptLines.join("\n")}
9671
9780
 
9672
9781
  if [[ "$PLUXX_REUSED_USER_CONFIG" == "1" ]]; then
9673
- echo "Found existing plugin config; reusing saved install values."
9782
+ echo "Found existing $PLUGIN_NAME config; reusing saved install values."
9674
9783
  fi
9675
9784
 
9676
9785
  export PLUXX_USER_CONFIG_SPEC
@@ -10746,15 +10855,536 @@ function runPublish(config, options = {}) {
10746
10855
  };
10747
10856
  }
10748
10857
 
10858
+ // src/bundle-check.ts
10859
+ import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync, statSync } from "fs";
10860
+ import { join, relative, resolve as resolve3 } from "path";
10861
+
10862
+ // src/readiness.ts
10863
+ function getRuntimeReadinessPlan(readiness) {
10864
+ if (!readiness || readiness.dependencies.length === 0 && readiness.gates.length === 0) {
10865
+ return {
10866
+ hasReadiness: false,
10867
+ dependencyIds: [],
10868
+ needsSessionStart: false,
10869
+ needsMcpGate: false,
10870
+ needsPromptGate: false,
10871
+ hasNamedMcpTools: false,
10872
+ hasNamedPromptTargets: false
10873
+ };
10874
+ }
10875
+ return {
10876
+ hasReadiness: true,
10877
+ dependencyIds: readiness.dependencies.map((dependency) => dependency.id),
10878
+ needsSessionStart: readiness.dependencies.length > 0,
10879
+ needsMcpGate: readiness.gates.some((gate) => gate.applyTo.includes("mcp-tools")),
10880
+ needsPromptGate: readiness.gates.some((gate) => gate.applyTo.includes("skills") || gate.applyTo.includes("commands")),
10881
+ hasNamedMcpTools: readiness.gates.some((gate) => (gate.tools?.length ?? 0) > 0),
10882
+ hasNamedPromptTargets: readiness.gates.some((gate) => (gate.skills?.length ?? 0) > 0 || (gate.commands?.length ?? 0) > 0)
10883
+ };
10884
+ }
10885
+
10886
+ // src/bundle-check.ts
10887
+ var MAKER_FIELD = "author";
10888
+ var CLAUDE_FAMILY_FIELDS = ["name", "version", "description", MAKER_FIELD, "license", "repository", "keywords"];
10889
+ var MANIFEST_DESCRIPTORS = {
10890
+ "claude-code": {
10891
+ path: ".claude-plugin/plugin.json",
10892
+ expectedFields: (config) => commonExpectedFields(config, CLAUDE_FAMILY_FIELDS),
10893
+ referenceFields: ["commands", "agents", "skills", "hooks", "mcpServers"]
10894
+ },
10895
+ cursor: {
10896
+ path: ".cursor-plugin/plugin.json",
10897
+ expectedFields: (config) => [
10898
+ ...commonExpectedFields(config, ["name", "version", "description", MAKER_FIELD, "repository", "license", "keywords"]),
10899
+ ...config.brand?.websiteURL ? [{ field: "homepage", expected: config.brand.websiteURL }] : [],
10900
+ ...expectedStandardHooksManifestField(config)
10901
+ ],
10902
+ expectedReferences: expectedStandardHookReferences,
10903
+ referenceFields: ["skills", "commands", "agents", "rules", "hooks", "mcpServers", "logo"]
10904
+ },
10905
+ codex: {
10906
+ path: ".codex-plugin/plugin.json",
10907
+ expectedFields: (config) => commonExpectedFields(config, ["name", "version", "description", MAKER_FIELD, "repository", "license", "keywords"]),
10908
+ referenceFields: ["skills", "mcpServers", "apps", "hooks"],
10909
+ collectExtraReferences: (manifest) => collectCodexInterfaceReferences(manifest)
10910
+ },
10911
+ opencode: {
10912
+ path: "package.json",
10913
+ expectedFields: (config) => [
10914
+ { field: "name", expected: config.platforms?.opencode?.npmPackage ?? `opencode-${config.name}` },
10915
+ { field: "version", expected: config.version },
10916
+ { field: "description", expected: `${config.description} (OpenCode plugin)` },
10917
+ { field: "main", expected: "index.ts" },
10918
+ { field: "type", expected: "module" },
10919
+ { field: MAKER_FIELD, expected: getMakerName(config) },
10920
+ { field: "license", expected: config.license }
10921
+ ]
10922
+ },
10923
+ "github-copilot": {
10924
+ path: ".claude-plugin/plugin.json",
10925
+ expectedFields: (config) => [
10926
+ ...commonExpectedFields(config, CLAUDE_FAMILY_FIELDS),
10927
+ ...expectedStandardHooksManifestField(config)
10928
+ ],
10929
+ expectedReferences: expectedStandardHookReferences,
10930
+ referenceFields: ["commands", "agents", "skills", "hooks", "mcpServers"]
10931
+ },
10932
+ openhands: {
10933
+ path: ".plugin/plugin.json",
10934
+ expectedFields: (config) => [
10935
+ ...commonExpectedFields(config, CLAUDE_FAMILY_FIELDS),
10936
+ ...expectedStandardHooksManifestField(config)
10937
+ ],
10938
+ expectedReferences: expectedStandardHookReferences,
10939
+ referenceFields: ["commands", "agents", "skills", "hooks", "mcpServers"]
10940
+ },
10941
+ "gemini-cli": {
10942
+ path: "gemini-extension.json",
10943
+ expectedFields: (config) => commonExpectedFields(config, ["name", "version", "description", MAKER_FIELD]),
10944
+ referenceFields: ["skills"]
10945
+ }
10946
+ };
10947
+ function checkGeneratedBundles(config, rootDir, targets = config.targets) {
10948
+ const reports = targets.map((target) => {
10949
+ const targetRoot = resolve3(rootDir, config.outDir, target);
10950
+ const descriptor = MANIFEST_DESCRIPTORS[target];
10951
+ const files = collectFileInventory(targetRoot);
10952
+ const issues = [];
10953
+ if (!descriptor) {
10954
+ return { target, files, issues };
10955
+ }
10956
+ const manifestFile = resolve3(targetRoot, descriptor.path);
10957
+ if (!existsSync3(manifestFile)) {
10958
+ issues.push({
10959
+ code: "manifest-missing",
10960
+ target,
10961
+ path: descriptor.path,
10962
+ detail: `Generated ${target} bundle is missing manifest ${descriptor.path}.`
10963
+ });
10964
+ return { target, manifestPath: descriptor.path, files, issues };
10965
+ }
10966
+ let manifest;
10967
+ try {
10968
+ manifest = JSON.parse(readFileSync3(manifestFile, "utf-8"));
10969
+ } catch (error) {
10970
+ issues.push({
10971
+ code: "manifest-invalid",
10972
+ target,
10973
+ path: descriptor.path,
10974
+ detail: `Generated ${target} manifest is not parseable: ${error instanceof Error ? error.message : String(error)}`
10975
+ });
10976
+ return { target, manifestPath: descriptor.path, files, issues };
10977
+ }
10978
+ for (const { field, expected } of descriptor.expectedFields(config)) {
10979
+ if (expected === void 0) continue;
10980
+ const actual = getField(manifest, field);
10981
+ if (!jsonEqual(actual, expected)) {
10982
+ issues.push({
10983
+ code: "manifest-field-drift",
10984
+ target,
10985
+ path: `${descriptor.path}#${field}`,
10986
+ detail: `Generated ${target} manifest field "${field}" drifted from source config.`,
10987
+ expected,
10988
+ actual
10989
+ });
10990
+ }
10991
+ }
10992
+ const references = Array.from(/* @__PURE__ */ new Set([
10993
+ ...collectManifestReferences(manifest, descriptor.referenceFields ?? []),
10994
+ ...descriptor.expectedReferences?.(config) ?? [],
10995
+ ...descriptor.collectExtraReferences?.(manifest) ?? []
10996
+ ]));
10997
+ for (const reference of references) {
10998
+ const resolvedReference = resolveBundleReference(targetRoot, reference);
10999
+ if (!resolvedReference || !existsSync3(resolvedReference)) {
11000
+ issues.push({
11001
+ code: "manifest-reference-missing",
11002
+ target,
11003
+ path: descriptor.path,
11004
+ detail: `Generated ${target} manifest references missing bundle path: ${reference}.`
11005
+ });
11006
+ }
11007
+ }
11008
+ return { target, manifestPath: descriptor.path, files, issues };
11009
+ });
11010
+ return {
11011
+ targets: reports,
11012
+ issues: reports.flatMap((report) => report.issues)
11013
+ };
11014
+ }
11015
+ function assertGeneratedBundlesCurrent(config, rootDir, targets = config.targets) {
11016
+ const report = checkGeneratedBundles(config, rootDir, targets);
11017
+ if (report.issues.length === 0) return report;
11018
+ const details = report.issues.map((issue) => {
11019
+ const expected = issue.expected !== void 0 ? ` expected=${JSON.stringify(issue.expected)}` : "";
11020
+ const actual = issue.actual !== void 0 ? ` actual=${JSON.stringify(issue.actual)}` : "";
11021
+ return `- [${issue.target}] ${issue.detail} (${issue.path})${expected}${actual}`;
11022
+ }).join("\n");
11023
+ throw new Error(`Generated bundle check failed:
11024
+ ${details}`);
11025
+ }
11026
+ function getField(input, field) {
11027
+ return field.split(".").reduce((value, key) => {
11028
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
11029
+ return value[key];
11030
+ }, input);
11031
+ }
11032
+ function jsonEqual(a, b) {
11033
+ return JSON.stringify(a) === JSON.stringify(b);
11034
+ }
11035
+ function commonExpectedFields(config, fields) {
11036
+ return fields.map((field) => ({ field, expected: getField(config, field) }));
11037
+ }
11038
+ function getMakerName(config) {
11039
+ return config[MAKER_FIELD]?.name;
11040
+ }
11041
+ function expectsStandardHooksManifest(config) {
11042
+ return Boolean(config.hooks || config.permissions || getRuntimeReadinessPlan(config.readiness).hasReadiness);
11043
+ }
11044
+ function expectedStandardHooksManifestField(config) {
11045
+ return expectsStandardHooksManifest(config) ? [{ field: "hooks", expected: "./hooks/hooks.json" }] : [];
11046
+ }
11047
+ function expectedStandardHookReferences(config) {
11048
+ return expectsStandardHooksManifest(config) ? ["./hooks/hooks.json"] : [];
11049
+ }
11050
+ function collectManifestReferences(manifest, fields) {
11051
+ const references = [];
11052
+ for (const field of fields) {
11053
+ const value = getField(manifest, field);
11054
+ if (typeof value === "string") {
11055
+ references.push(value);
11056
+ } else if (Array.isArray(value)) {
11057
+ for (const item of value) {
11058
+ if (typeof item === "string") references.push(item);
11059
+ }
11060
+ }
11061
+ }
11062
+ return references;
11063
+ }
11064
+ function collectCodexInterfaceReferences(manifest) {
11065
+ const iface = manifest.interface;
11066
+ if (!iface || typeof iface !== "object" || Array.isArray(iface)) return [];
11067
+ return collectManifestReferences(iface, ["composerIcon", "logo", "screenshots"]);
11068
+ }
11069
+ function resolveBundleReference(rootDir, reference) {
11070
+ if (reference.trim() === "") return void 0;
11071
+ const stripped = reference.startsWith("./") ? reference.slice(2) : reference;
11072
+ const resolvedPath = resolve3(rootDir, stripped);
11073
+ const rel = relative(rootDir, resolvedPath);
11074
+ if (rel.startsWith("..")) return void 0;
11075
+ return resolvedPath;
11076
+ }
11077
+ function collectFileInventory(rootDir) {
11078
+ if (!existsSync3(rootDir)) return [];
11079
+ const files = [];
11080
+ const visit = (dir) => {
11081
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
11082
+ const fullPath = join(dir, entry.name);
11083
+ if (entry.isDirectory()) {
11084
+ visit(fullPath);
11085
+ } else if (entry.isFile()) {
11086
+ files.push(relative(rootDir, fullPath).replace(/\\/g, "/"));
11087
+ } else if (entry.isSymbolicLink()) {
11088
+ const stats = statSync(fullPath);
11089
+ if (stats.isFile()) files.push(relative(rootDir, fullPath).replace(/\\/g, "/"));
11090
+ }
11091
+ }
11092
+ };
11093
+ visit(rootDir);
11094
+ return files.sort();
11095
+ }
11096
+
11097
+ // src/install-reference.ts
11098
+ var INSTALL_REFERENCE_SCHEMES = ["local", "github", "npm", "team"];
11099
+ var INSTALL_REFERENCE_HELP = "Install references must use <scheme>:<locator>[@<version>][#<target>] with scheme local, github, npm, or team.";
11100
+ var OWNER_PAIR_PATTERN = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
11101
+ var NPM_PACKAGE_PATTERN = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/;
11102
+ var VERSION_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._+:/-]*$/;
11103
+ function parseInstallReference(rawReference) {
11104
+ const raw = rawReference.trim();
11105
+ if (!raw) {
11106
+ throw installReferenceError("Provide an install reference.");
11107
+ }
11108
+ const schemeSeparator = raw.indexOf(":");
11109
+ if (schemeSeparator <= 0) {
11110
+ throw installReferenceError("Install reference is missing a scheme.");
11111
+ }
11112
+ const rawScheme = raw.slice(0, schemeSeparator);
11113
+ if (!isInstallReferenceScheme(rawScheme)) {
11114
+ throw installReferenceError(`Unsupported install reference scheme "${rawScheme}".`);
11115
+ }
11116
+ const rest = raw.slice(schemeSeparator + 1);
11117
+ const { body, target } = splitInstallReferenceTarget(rest);
11118
+ if (!body) {
11119
+ throw installReferenceError(`Install reference "${rawScheme}:" is missing a locator.`);
11120
+ }
11121
+ if (rawScheme === "local") {
11122
+ return buildLocalInstallReference(raw, body, target);
11123
+ }
11124
+ const { locator, version } = splitInstallReferenceVersion(body);
11125
+ if (!locator) {
11126
+ throw installReferenceError(`Install reference "${rawScheme}:" is missing a locator before the version.`);
11127
+ }
11128
+ validateInstallReferenceVersion(version);
11129
+ switch (rawScheme) {
11130
+ case "github":
11131
+ return buildGitHubInstallReference(raw, locator, version, target);
11132
+ case "npm":
11133
+ return buildNpmInstallReference(raw, locator, version, target);
11134
+ case "team":
11135
+ return buildTeamInstallReference(raw, locator, version, target);
11136
+ }
11137
+ }
11138
+ function formatInstallReference(reference) {
11139
+ return reference.normalized;
11140
+ }
11141
+ function buildLocalInstallReference(raw, locator, target) {
11142
+ return {
11143
+ raw,
11144
+ scheme: "local",
11145
+ locator,
11146
+ path: locator,
11147
+ ...target ? { target } : {},
11148
+ normalized: normalizeInstallReference("local", locator, void 0, target)
11149
+ };
11150
+ }
11151
+ function buildGitHubInstallReference(raw, locator, version, target) {
11152
+ if (!OWNER_PAIR_PATTERN.test(locator)) {
11153
+ throw installReferenceError("GitHub install references must look like github:<owner>/<repo>[@<version>][#<target>].");
11154
+ }
11155
+ const [owner, repo] = locator.split("/");
11156
+ return {
11157
+ raw,
11158
+ scheme: "github",
11159
+ locator,
11160
+ owner,
11161
+ repo,
11162
+ ...version ? { version } : {},
11163
+ ...target ? { target } : {},
11164
+ normalized: normalizeInstallReference("github", locator, version, target)
11165
+ };
11166
+ }
11167
+ function buildNpmInstallReference(raw, locator, version, target) {
11168
+ if (!NPM_PACKAGE_PATTERN.test(locator)) {
11169
+ throw installReferenceError("npm install references must look like npm:<package>[@<version>][#<target>].");
11170
+ }
11171
+ return {
11172
+ raw,
11173
+ scheme: "npm",
11174
+ locator,
11175
+ packageName: locator,
11176
+ ...version ? { version } : {},
11177
+ ...target ? { target } : {},
11178
+ normalized: normalizeInstallReference("npm", locator, version, target)
11179
+ };
11180
+ }
11181
+ function buildTeamInstallReference(raw, locator, version, target) {
11182
+ if (!OWNER_PAIR_PATTERN.test(locator)) {
11183
+ throw installReferenceError("Team install references must look like team:<team>/<plugin>[@<version>][#<target>].");
11184
+ }
11185
+ const [team, plugin] = locator.split("/");
11186
+ return {
11187
+ raw,
11188
+ scheme: "team",
11189
+ locator,
11190
+ team,
11191
+ plugin,
11192
+ ...version ? { version } : {},
11193
+ ...target ? { target } : {},
11194
+ normalized: normalizeInstallReference("team", locator, version, target)
11195
+ };
11196
+ }
11197
+ function splitInstallReferenceTarget(input) {
11198
+ const targetSeparator = input.lastIndexOf("#");
11199
+ if (targetSeparator === -1) return { body: input };
11200
+ const body = input.slice(0, targetSeparator).trim();
11201
+ const rawTarget = input.slice(targetSeparator + 1).trim();
11202
+ if (!rawTarget) {
11203
+ throw installReferenceError('Install reference target cannot be empty after "#".');
11204
+ }
11205
+ const parsedTarget = TargetPlatform.safeParse(rawTarget);
11206
+ if (!parsedTarget.success) {
11207
+ throw installReferenceError(`Unknown install reference target "${rawTarget}".`);
11208
+ }
11209
+ return { body, target: parsedTarget.data };
11210
+ }
11211
+ function splitInstallReferenceVersion(input) {
11212
+ const versionSeparator = input.lastIndexOf("@");
11213
+ if (versionSeparator <= 0) {
11214
+ return { locator: input.trim() };
11215
+ }
11216
+ const locator = input.slice(0, versionSeparator).trim();
11217
+ const version = input.slice(versionSeparator + 1).trim();
11218
+ if (!version) {
11219
+ throw installReferenceError('Install reference version cannot be empty after "@".');
11220
+ }
11221
+ return { locator, version };
11222
+ }
11223
+ function validateInstallReferenceVersion(version) {
11224
+ if (!version) return;
11225
+ if (!VERSION_PATTERN.test(version)) {
11226
+ throw installReferenceError(`Install reference version "${version}" is not valid.`);
11227
+ }
11228
+ }
11229
+ function normalizeInstallReference(scheme, locator, version, target) {
11230
+ return `${scheme}:${locator}${version ? `@${version}` : ""}${target ? `#${target}` : ""}`;
11231
+ }
11232
+ function isInstallReferenceScheme(value) {
11233
+ return INSTALL_REFERENCE_SCHEMES.includes(value);
11234
+ }
11235
+ function installReferenceError(message) {
11236
+ return new Error(`${message} ${INSTALL_REFERENCE_HELP}`);
11237
+ }
11238
+
11239
+ // src/host-detection.ts
11240
+ import { accessSync, constants, existsSync as existsSync4, statSync as statSync2 } from "fs";
11241
+ import { delimiter, resolve as resolve4 } from "path";
11242
+ import { homedir } from "os";
11243
+ var CORE_HOST_FAMILIES = CORE_FOUR_PLATFORMS;
11244
+ var HOST_EVIDENCE = {
11245
+ "claude-code": [
11246
+ { type: "cli", label: "Claude Code CLI", command: "claude" },
11247
+ { type: "user-config", label: "Claude user config", path: ({ homeDir }) => resolve4(homeDir, ".claude.json") },
11248
+ { type: "user-config", label: "Claude settings directory", path: ({ homeDir }) => resolve4(homeDir, ".claude/settings.json") },
11249
+ { type: "project-config", label: "Claude project MCP config", path: ({ rootDir }) => resolve4(rootDir, ".mcp.json") },
11250
+ { type: "project-config", label: "Claude project settings", path: ({ rootDir }) => resolve4(rootDir, ".claude/settings.json") },
11251
+ { type: "installed-plugin", label: "Claude plugin cache", path: ({ homeDir }) => resolve4(homeDir, ".claude/plugins/cache") },
11252
+ { type: "installed-plugin", label: "Legacy Claude plugin directory", path: ({ homeDir }) => resolve4(homeDir, ".claude/plugins") }
11253
+ ],
11254
+ cursor: [
11255
+ { type: "cli", label: "Cursor CLI", command: "cursor" },
11256
+ { type: "cli", label: "Cursor agent CLI", command: "cursor-agent" },
11257
+ { type: "app", label: "Cursor app bundle", path: ({ appDirs }) => resolveFirstAppPath(appDirs, "Cursor.app") },
11258
+ { type: "user-config", label: "Cursor user MCP config", path: ({ homeDir }) => resolve4(homeDir, ".cursor/mcp.json") },
11259
+ { type: "user-config", label: "Cursor user settings", path: ({ homeDir }) => resolve4(homeDir, ".cursor/settings.json") },
11260
+ { type: "project-config", label: "Cursor project MCP config", path: ({ rootDir }) => resolve4(rootDir, ".cursor/mcp.json") },
11261
+ { type: "project-config", label: "Cursor project rules", path: ({ rootDir }) => resolve4(rootDir, ".cursor/rules") },
11262
+ { type: "project-config", label: "Cursor root MCP config", path: ({ rootDir }) => resolve4(rootDir, "mcp.json") },
11263
+ { type: "installed-plugin", label: "Cursor local plugins", path: ({ homeDir }) => resolve4(homeDir, ".cursor/plugins/local") }
11264
+ ],
11265
+ codex: [
11266
+ { type: "cli", label: "Codex CLI", command: "codex" },
11267
+ { type: "app", label: "Codex app bundle", path: ({ appDirs }) => resolveFirstAppPath(appDirs, "Codex.app") },
11268
+ { type: "user-config", label: "Codex user config", path: ({ homeDir }) => resolve4(homeDir, ".codex/config.toml") },
11269
+ { type: "project-config", label: "Codex project config", path: ({ rootDir }) => resolve4(rootDir, ".codex/config.toml") },
11270
+ { type: "project-config", label: "Codex project agents", path: ({ rootDir }) => resolve4(rootDir, ".codex/agents") },
11271
+ { type: "installed-plugin", label: "Codex local plugins", path: ({ homeDir }) => resolve4(homeDir, ".codex/plugins") },
11272
+ { type: "installed-plugin", label: "Codex local marketplace catalog", path: ({ homeDir }) => resolve4(homeDir, ".agents/plugins/marketplace.json") }
11273
+ ],
11274
+ opencode: [
11275
+ { type: "cli", label: "OpenCode CLI", command: "opencode" },
11276
+ { type: "user-config", label: "OpenCode user config", path: ({ homeDir }) => resolve4(homeDir, ".config/opencode/opencode.json") },
11277
+ { type: "project-config", label: "OpenCode project config", path: ({ rootDir }) => resolve4(rootDir, "opencode.json") },
11278
+ { type: "project-config", label: "OpenCode alternate project config", path: ({ rootDir }) => resolve4(rootDir, ".opencode.json") },
11279
+ { type: "project-config", label: "OpenCode project directory", path: ({ rootDir }) => resolve4(rootDir, ".opencode") },
11280
+ { type: "installed-plugin", label: "OpenCode local plugins", path: ({ homeDir }) => resolve4(homeDir, ".config/opencode/plugins") },
11281
+ { type: "installed-plugin", label: "OpenCode synced skills", path: ({ homeDir }) => resolve4(homeDir, ".config/opencode/skills") }
11282
+ ]
11283
+ };
11284
+ function detectHostFamilies(options = {}) {
11285
+ const context = {
11286
+ rootDir: options.rootDir ?? process.cwd(),
11287
+ homeDir: options.homeDir ?? homedir(),
11288
+ pathEnv: options.pathEnv ?? process.env.PATH ?? "",
11289
+ appDirs: options.appDirs ?? defaultAppDirs(options.homeDir ?? homedir()),
11290
+ platform: options.platform ?? process.platform
11291
+ };
11292
+ const hostSet = new Set(options.hosts ?? CORE_HOST_FAMILIES);
11293
+ const hosts = CORE_HOST_FAMILIES.filter((host) => hostSet.has(host)).map((host) => detectHostFamily(host, context));
11294
+ return {
11295
+ hosts,
11296
+ detectedHosts: hosts.filter((host) => host.detected).map((host) => host.host)
11297
+ };
11298
+ }
11299
+ function buildHostTargetSelection(configTargets, explicitTargets, report) {
11300
+ const selectedTargets = explicitTargets ?? configTargets;
11301
+ const configCoreTargets = configTargets.filter(isCoreHostFamily);
11302
+ const suggestedTargets = report.detectedHosts.filter((host) => configCoreTargets.includes(host));
11303
+ const explicitOverride = explicitTargets !== void 0;
11304
+ return {
11305
+ source: explicitOverride ? "explicit-targets" : "config-targets",
11306
+ selectedTargets,
11307
+ detectedHosts: report.detectedHosts,
11308
+ suggestedTargets,
11309
+ explicitOverride,
11310
+ note: explicitOverride ? "Explicit --target selection is authoritative; host detection did not change selected targets." : "Host detection is informational; install targets still come from pluxx.config targets."
11311
+ };
11312
+ }
11313
+ function detectHostFamily(host, context) {
11314
+ const evidence = HOST_EVIDENCE[host].map((candidate) => resolveEvidence(candidate, context)).filter((item) => item !== void 0);
11315
+ return {
11316
+ host,
11317
+ detected: evidence.some(isMachineLevelEvidence),
11318
+ evidence
11319
+ };
11320
+ }
11321
+ function isMachineLevelEvidence(evidence) {
11322
+ return evidence.type !== "project-config";
11323
+ }
11324
+ function resolveEvidence(candidate, context) {
11325
+ if (candidate.command) {
11326
+ const commandPath = findExecutable(candidate.command, context.pathEnv, context.platform);
11327
+ if (!commandPath) return void 0;
11328
+ return {
11329
+ type: candidate.type,
11330
+ label: candidate.label,
11331
+ command: candidate.command,
11332
+ path: commandPath
11333
+ };
11334
+ }
11335
+ if (!candidate.path) return void 0;
11336
+ const evidencePath = candidate.path(context);
11337
+ if (!evidencePath || !existsSync4(evidencePath)) return void 0;
11338
+ return {
11339
+ type: candidate.type,
11340
+ label: candidate.label,
11341
+ path: evidencePath
11342
+ };
11343
+ }
11344
+ function findExecutable(command, pathEnv, platform) {
11345
+ if (!pathEnv.trim()) return void 0;
11346
+ const names = platform === "win32" ? [command, `${command}.exe`, `${command}.cmd`, `${command}.bat`] : [command];
11347
+ for (const dir of pathEnv.split(delimiter).filter(Boolean)) {
11348
+ for (const name of names) {
11349
+ const candidate = resolve4(dir, name);
11350
+ if (isExecutableFile(candidate, platform)) return candidate;
11351
+ }
11352
+ }
11353
+ return void 0;
11354
+ }
11355
+ function isExecutableFile(path, platform) {
11356
+ try {
11357
+ if (!statSync2(path).isFile()) return false;
11358
+ if (platform === "win32") return true;
11359
+ accessSync(path, constants.X_OK);
11360
+ return true;
11361
+ } catch {
11362
+ return false;
11363
+ }
11364
+ }
11365
+ function defaultAppDirs(homeDir) {
11366
+ return [
11367
+ "/Applications",
11368
+ resolve4(homeDir, "Applications")
11369
+ ];
11370
+ }
11371
+ function resolveFirstAppPath(appDirs, appName) {
11372
+ return appDirs.map((dir) => resolve4(dir, appName)).find((path) => existsSync4(path)) ?? resolve4(appDirs[0] ?? "/", appName);
11373
+ }
11374
+ function isCoreHostFamily(target) {
11375
+ return CORE_HOST_FAMILIES.includes(target);
11376
+ }
11377
+
10749
11378
  // src/cli/verify-install.ts
10750
- import { existsSync as existsSync5, lstatSync as lstatSync2, readdirSync as readdirSync3, readFileSync as readFileSync5, readlinkSync, realpathSync as realpathSync2, statSync } from "fs";
10751
- import { resolve as resolve5 } from "path";
11379
+ import { createHash as createHash2 } from "crypto";
11380
+ import { existsSync as existsSync7, lstatSync as lstatSync2, readdirSync as readdirSync4, readFileSync as readFileSync6, readlinkSync, realpathSync as realpathSync2, statSync as statSync3 } from "fs";
11381
+ import { resolve as resolve7 } from "path";
10752
11382
 
10753
11383
  // src/cli/doctor.ts
10754
11384
  import { spawn, spawnSync as spawnSync2 } from "child_process";
10755
- import { accessSync, constants, existsSync as existsSync4, lstatSync, readFileSync as readFileSync4, readdirSync as readdirSync2, realpathSync } from "fs";
10756
- import { homedir } from "os";
10757
- import { basename as basename2, dirname as dirname2, relative as relative2, resolve as resolve4 } from "path";
11385
+ import { accessSync as accessSync2, constants as constants2, existsSync as existsSync6, lstatSync, readFileSync as readFileSync5, readdirSync as readdirSync3, realpathSync } from "fs";
11386
+ import { homedir as homedir2 } from "os";
11387
+ import { basename as basename2, dirname as dirname2, relative as relative3, resolve as resolve6 } from "path";
10758
11388
 
10759
11389
  // node_modules/jiti/lib/jiti.mjs
10760
11390
  var import_jiti = __toESM(require_jiti(), 1);
@@ -10767,8 +11397,8 @@ var CONFIG_FILES = [
10767
11397
  ];
10768
11398
 
10769
11399
  // src/cli/install.ts
10770
- import { resolve as resolve3, dirname, basename, relative } from "path";
10771
- import { existsSync as existsSync3, symlinkSync, mkdirSync as mkdirSync2, rmSync as rmSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2, cpSync, readdirSync } from "fs";
11400
+ import { resolve as resolve5, dirname, basename, relative as relative2 } from "path";
11401
+ import { existsSync as existsSync5, symlinkSync, mkdirSync as mkdirSync2, rmSync as rmSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2, cpSync, readdirSync as readdirSync2 } from "fs";
10772
11402
 
10773
11403
  // src/mcp-stdio-paths.ts
10774
11404
  function findHostPluginRootVars(value) {
@@ -10797,57 +11427,57 @@ function getInstallTargets(pluginName) {
10797
11427
  return [
10798
11428
  {
10799
11429
  platform: "claude-code",
10800
- pluginDir: resolve3(home, ".claude/plugins", pluginName),
11430
+ pluginDir: resolve5(home, ".claude/plugins", pluginName),
10801
11431
  description: `claude plugin install ${pluginName}@${getClaudeMarketplaceName(pluginName)}`
10802
11432
  },
10803
11433
  {
10804
11434
  platform: "cursor",
10805
- pluginDir: resolve3(home, ".cursor/plugins/local", pluginName),
11435
+ pluginDir: resolve5(home, ".cursor/plugins/local", pluginName),
10806
11436
  description: `~/.cursor/plugins/local/${pluginName}`
10807
11437
  },
10808
11438
  {
10809
11439
  platform: "codex",
10810
- pluginDir: resolve3(home, ".codex/plugins", pluginName),
11440
+ pluginDir: resolve5(home, ".codex/plugins", pluginName),
10811
11441
  description: `~/.codex/plugins/${pluginName} (via ~/.agents/plugins/marketplace.json)`
10812
11442
  },
10813
11443
  {
10814
11444
  platform: "opencode",
10815
- pluginDir: resolve3(home, ".config/opencode/plugins", pluginName),
11445
+ pluginDir: resolve5(home, ".config/opencode/plugins", pluginName),
10816
11446
  description: `~/.config/opencode/plugins/${pluginName}.ts + ~/.config/opencode/plugins/${pluginName}/`
10817
11447
  },
10818
11448
  {
10819
11449
  platform: "github-copilot",
10820
- pluginDir: resolve3(home, ".github-copilot/plugins", pluginName),
11450
+ pluginDir: resolve5(home, ".github-copilot/plugins", pluginName),
10821
11451
  description: `~/.github-copilot/plugins/${pluginName}`
10822
11452
  },
10823
11453
  {
10824
11454
  platform: "openhands",
10825
- pluginDir: resolve3(home, ".openhands/plugins", pluginName),
11455
+ pluginDir: resolve5(home, ".openhands/plugins", pluginName),
10826
11456
  description: `~/.openhands/plugins/${pluginName}`
10827
11457
  },
10828
11458
  {
10829
11459
  platform: "warp",
10830
- pluginDir: resolve3(home, ".warp/plugins", pluginName),
11460
+ pluginDir: resolve5(home, ".warp/plugins", pluginName),
10831
11461
  description: `~/.warp/plugins/${pluginName}`
10832
11462
  },
10833
11463
  {
10834
11464
  platform: "gemini-cli",
10835
- pluginDir: resolve3(home, ".gemini/extensions", pluginName),
11465
+ pluginDir: resolve5(home, ".gemini/extensions", pluginName),
10836
11466
  description: `~/.gemini/extensions/${pluginName}`
10837
11467
  },
10838
11468
  {
10839
11469
  platform: "roo-code",
10840
- pluginDir: resolve3(home, ".roo/plugins", pluginName),
11470
+ pluginDir: resolve5(home, ".roo/plugins", pluginName),
10841
11471
  description: `~/.roo/plugins/${pluginName}`
10842
11472
  },
10843
11473
  {
10844
11474
  platform: "cline",
10845
- pluginDir: resolve3(home, ".cline/plugins", pluginName),
11475
+ pluginDir: resolve5(home, ".cline/plugins", pluginName),
10846
11476
  description: `~/.cline/plugins/${pluginName}`
10847
11477
  },
10848
11478
  {
10849
11479
  platform: "amp",
10850
- pluginDir: resolve3(home, ".amp/plugins", pluginName),
11480
+ pluginDir: resolve5(home, ".amp/plugins", pluginName),
10851
11481
  description: `~/.amp/plugins/${pluginName}`
10852
11482
  }
10853
11483
  ];
@@ -10858,10 +11488,10 @@ function getClaudeMarketplaceName(pluginName) {
10858
11488
  function readBundleManifestVersion(rootDir, platform) {
10859
11489
  const manifestPath = manifestPathForPlatform(platform);
10860
11490
  if (!manifestPath) return void 0;
10861
- const filepath = resolve3(rootDir, manifestPath);
10862
- if (!existsSync3(filepath)) return void 0;
11491
+ const filepath = resolve5(rootDir, manifestPath);
11492
+ if (!existsSync5(filepath)) return void 0;
10863
11493
  try {
10864
- const manifest = JSON.parse(readFileSync3(filepath, "utf-8"));
11494
+ const manifest = JSON.parse(readFileSync4(filepath, "utf-8"));
10865
11495
  return typeof manifest.version === "string" ? manifest.version : void 0;
10866
11496
  } catch {
10867
11497
  return void 0;
@@ -10870,7 +11500,7 @@ function readBundleManifestVersion(rootDir, platform) {
10870
11500
  function resolveClaudeInstalledCachePath(pluginName, version) {
10871
11501
  if (!version) return void 0;
10872
11502
  const home = process.env.HOME ?? "~";
10873
- return resolve3(home, ".claude/plugins/cache", getClaudeMarketplaceName(pluginName), pluginName, version);
11503
+ return resolve5(home, ".claude/plugins/cache", getClaudeMarketplaceName(pluginName), pluginName, version);
10874
11504
  }
10875
11505
  function resolveExpectedInstalledConsumerPath(target, pluginName) {
10876
11506
  if (target.platform === "claude-code" && pluginName !== "") {
@@ -10885,8 +11515,8 @@ function resolveExpectedInstalledConsumerPath(target, pluginName) {
10885
11515
  function resolveInstalledConsumerPath(target, pluginName) {
10886
11516
  if (target.platform === "claude-code" && pluginName !== "") {
10887
11517
  const expectedPath = resolveExpectedInstalledConsumerPath(target, pluginName);
10888
- if (existsSync3(expectedPath)) return expectedPath;
10889
- if (existsSync3(target.pluginDir)) return target.pluginDir;
11518
+ if (existsSync5(expectedPath)) return expectedPath;
11519
+ if (existsSync5(target.pluginDir)) return target.pluginDir;
10890
11520
  return expectedPath;
10891
11521
  }
10892
11522
  return target.pluginDir;
@@ -10908,13 +11538,13 @@ function manifestPathForPlatform(platform) {
10908
11538
  function isRelativeBundlePath(value) {
10909
11539
  return value.startsWith("./") || value.startsWith("../") || value.startsWith(".\\") || value.startsWith("..\\");
10910
11540
  }
10911
- function resolveBundleReference(rootDir, value) {
11541
+ function resolveBundleReference2(rootDir, value) {
10912
11542
  if (isRelativeBundlePath(value)) {
10913
- return resolve3(rootDir, value);
11543
+ return resolve5(rootDir, value);
10914
11544
  }
10915
11545
  const pluginRootMatch = value.match(/^\$\{(?:CLAUDE_PLUGIN_ROOT|CURSOR_PLUGIN_ROOT|CODEX_PLUGIN_ROOT|OPENCODE_PLUGIN_ROOT|PLUGIN_ROOT|PLUXX_PLUGIN_ROOT)\}[\\/](.+)$/);
10916
11546
  if (pluginRootMatch) {
10917
- return resolve3(rootDir, pluginRootMatch[1]);
11547
+ return resolve5(rootDir, pluginRootMatch[1]);
10918
11548
  }
10919
11549
  return void 0;
10920
11550
  }
@@ -10965,15 +11595,26 @@ function commandChangesToKnownPluginRoot(command) {
10965
11595
  return /\bcd\s+["']?\$\{?(?:CLAUDE_PLUGIN_ROOT|CURSOR_PLUGIN_ROOT|CODEX_PLUGIN_ROOT|OPENCODE_PLUGIN_ROOT|PLUGIN_ROOT|PLUXX_PLUGIN_ROOT)\}?/.test(command);
10966
11596
  }
10967
11597
  function formatBundleRelativePath(rootDir, filePath) {
10968
- const relativePath = relative(rootDir, filePath);
11598
+ const relativePath = relative2(rootDir, filePath);
10969
11599
  return relativePath && !relativePath.startsWith("..") ? relativePath : filePath;
10970
11600
  }
10971
11601
  function findHookWrapperPaths(rootDir, command) {
10972
- return extractBundleCommandTargets(command).map((target) => resolveBundleReference(rootDir, target)).filter((target) => {
11602
+ return extractBundleCommandTargets(command).map((target) => resolveBundleReference2(rootDir, target)).filter((target) => {
10973
11603
  if (!target) return false;
10974
- return existsSync3(target) && basename(target).startsWith("pluxx-hook-command-") && target.endsWith(".sh");
11604
+ return existsSync5(target) && basename(target).startsWith("pluxx-hook-command-") && (target.endsWith(".sh") || target.endsWith(".mjs"));
10975
11605
  });
10976
11606
  }
11607
+ function extractGeneratedHookWrapperCommand(wrapper) {
11608
+ const shellAssignment = wrapper.match(/^PLUXX_HOOK_COMMAND=(.*)$/m)?.[1];
11609
+ if (shellAssignment) return shellAssignment;
11610
+ const nodeAssignment = wrapper.match(/^const COMMAND = (.*)$/m)?.[1];
11611
+ if (!nodeAssignment) return "";
11612
+ try {
11613
+ return JSON.parse(nodeAssignment);
11614
+ } catch {
11615
+ return nodeAssignment;
11616
+ }
11617
+ }
10977
11618
  function findCodexCwdUnsafeHookCommands(rootDir, commands) {
10978
11619
  const issues = /* @__PURE__ */ new Set();
10979
11620
  for (const command of commands) {
@@ -10982,8 +11623,8 @@ function findCodexCwdUnsafeHookCommands(rootDir, commands) {
10982
11623
  issues.add(`${command} uses cwd-relative bundle target(s): ${relativeTargets.join(", ")}`);
10983
11624
  }
10984
11625
  for (const wrapperPath of findHookWrapperPaths(rootDir, command)) {
10985
- const wrapper = readFileSync3(wrapperPath, "utf-8");
10986
- const hookCommand = wrapper.match(/^PLUXX_HOOK_COMMAND=(.*)$/m)?.[1] ?? "";
11626
+ const wrapper = readFileSync4(wrapperPath, "utf-8");
11627
+ const hookCommand = extractGeneratedHookWrapperCommand(wrapper);
10987
11628
  const wrapperRelativeTargets = extractRelativeBundleCommandTargets(hookCommand);
10988
11629
  if (wrapperRelativeTargets.length > 0 && !commandChangesToKnownPluginRoot(wrapper)) {
10989
11630
  issues.add(`${formatBundleRelativePath(rootDir, wrapperPath)} evaluates cwd-relative bundle target(s): ${wrapperRelativeTargets.join(", ")}`);
@@ -10997,13 +11638,13 @@ function resolveInstalledHooksReference(rootDir, platform, manifest) {
10997
11638
  if (manifestReference) {
10998
11639
  return {
10999
11640
  reference: manifestReference,
11000
- path: resolveBundleReference(rootDir, manifestReference)
11641
+ path: resolveBundleReference2(rootDir, manifestReference)
11001
11642
  };
11002
11643
  }
11003
11644
  if (platform === "claude-code") {
11004
11645
  const fallbackReference = "./hooks/hooks.json";
11005
- const fallbackPath = resolve3(rootDir, "hooks/hooks.json");
11006
- if (existsSync3(fallbackPath)) {
11646
+ const fallbackPath = resolve5(rootDir, "hooks/hooks.json");
11647
+ if (existsSync5(fallbackPath)) {
11007
11648
  return {
11008
11649
  reference: fallbackReference,
11009
11650
  path: fallbackPath
@@ -11016,9 +11657,9 @@ function getClaudeStandardHooksManifestIssue(rootDir, platform, manifest) {
11016
11657
  if (platform !== "claude-code") return void 0;
11017
11658
  const manifestReference = typeof manifest.hooks === "string" ? manifest.hooks : void 0;
11018
11659
  if (!manifestReference) return void 0;
11019
- const manifestHooksPath = resolveBundleReference(rootDir, manifestReference);
11020
- const standardHooksPath = resolve3(rootDir, "hooks/hooks.json");
11021
- if (!manifestHooksPath || manifestHooksPath !== standardHooksPath || !existsSync3(standardHooksPath)) {
11660
+ const manifestHooksPath = resolveBundleReference2(rootDir, manifestReference);
11661
+ const standardHooksPath = resolve5(rootDir, "hooks/hooks.json");
11662
+ if (!manifestHooksPath || manifestHooksPath !== standardHooksPath || !existsSync5(standardHooksPath)) {
11022
11663
  return void 0;
11023
11664
  }
11024
11665
  return "Claude auto-loads hooks/hooks.json. Current Claude CLI releases report a duplicate hooks file load error when manifest.hooks also points at ./hooks/hooks.json, so manifest.hooks should only reference additional hook files.";
@@ -11033,8 +11674,8 @@ function findInstalledBundleIntegrityIssues(rootDir, platform) {
11033
11674
  invalidRuntimeScripts: []
11034
11675
  };
11035
11676
  }
11036
- const manifestFile = resolve3(rootDir, manifestPath);
11037
- if (!existsSync3(manifestFile)) {
11677
+ const manifestFile = resolve5(rootDir, manifestPath);
11678
+ if (!existsSync5(manifestFile)) {
11038
11679
  return {
11039
11680
  manifestIssue: `missing plugin manifest at ${manifestPath}`,
11040
11681
  missingManifestPaths: [],
@@ -11045,7 +11686,7 @@ function findInstalledBundleIntegrityIssues(rootDir, platform) {
11045
11686
  }
11046
11687
  let manifest;
11047
11688
  try {
11048
- manifest = JSON.parse(readFileSync3(manifestFile, "utf-8"));
11689
+ manifest = JSON.parse(readFileSync4(manifestFile, "utf-8"));
11049
11690
  } catch (error) {
11050
11691
  return {
11051
11692
  manifestIssue: `plugin manifest at ${manifestPath} is not parseable: ${error instanceof Error ? error.message : String(error)}`,
@@ -11056,8 +11697,8 @@ function findInstalledBundleIntegrityIssues(rootDir, platform) {
11056
11697
  };
11057
11698
  }
11058
11699
  const missingManifestPaths = readBundleManifestReferences(manifest).filter((value) => {
11059
- const resolved = resolveBundleReference(rootDir, value);
11060
- return resolved !== void 0 && !existsSync3(resolved);
11700
+ const resolved = resolveBundleReference2(rootDir, value);
11701
+ return resolved !== void 0 && !existsSync5(resolved);
11061
11702
  }).sort();
11062
11703
  const manifestIssue = getClaudeStandardHooksManifestIssue(rootDir, platform, manifest);
11063
11704
  const { reference: hooksReference, path: hooksPath } = resolveInstalledHooksReference(rootDir, platform, manifest);
@@ -11070,7 +11711,7 @@ function findInstalledBundleIntegrityIssues(rootDir, platform) {
11070
11711
  invalidRuntimeScripts: findInstalledRuntimeScriptIssues(rootDir, manifest)
11071
11712
  };
11072
11713
  }
11073
- if (!hooksPath || !existsSync3(hooksPath)) {
11714
+ if (!hooksPath || !existsSync5(hooksPath)) {
11074
11715
  return {
11075
11716
  ...manifestIssue ? { manifestIssue } : {},
11076
11717
  missingManifestPaths,
@@ -11080,13 +11721,13 @@ function findInstalledBundleIntegrityIssues(rootDir, platform) {
11080
11721
  };
11081
11722
  }
11082
11723
  try {
11083
- const hooks = JSON.parse(readFileSync3(hooksPath, "utf-8"));
11724
+ const hooks = JSON.parse(readFileSync4(hooksPath, "utf-8"));
11084
11725
  const commands = [];
11085
11726
  collectHookCommandStrings(hooks, commands);
11086
11727
  const missingHookTargets = [...new Set(
11087
11728
  commands.flatMap(extractBundleCommandTargets).filter((value) => {
11088
- const resolved = resolveBundleReference(rootDir, value);
11089
- return resolved !== void 0 && !existsSync3(resolved);
11729
+ const resolved = resolveBundleReference2(rootDir, value);
11730
+ return resolved !== void 0 && !existsSync5(resolved);
11090
11731
  })
11091
11732
  )].sort();
11092
11733
  const cwdUnsafeHookCommands = platform === "codex" ? findCodexCwdUnsafeHookCommands(rootDir, commands) : [];
@@ -11111,10 +11752,10 @@ function findInstalledBundleIntegrityIssues(rootDir, platform) {
11111
11752
  function findInstalledRuntimeScriptIssues(rootDir, manifest) {
11112
11753
  const mcpReference = typeof manifest.mcpServers === "string" ? manifest.mcpServers : void 0;
11113
11754
  if (!mcpReference) return [];
11114
- const mcpPath = resolveBundleReference(rootDir, mcpReference);
11115
- if (!mcpPath || !existsSync3(mcpPath)) return [];
11755
+ const mcpPath = resolveBundleReference2(rootDir, mcpReference);
11756
+ if (!mcpPath || !existsSync5(mcpPath)) return [];
11116
11757
  try {
11117
- const parsed = JSON.parse(readFileSync3(mcpPath, "utf-8"));
11758
+ const parsed = JSON.parse(readFileSync4(mcpPath, "utf-8"));
11118
11759
  const issues = /* @__PURE__ */ new Set();
11119
11760
  for (const [serverName, server] of Object.entries(parsed.mcpServers ?? {})) {
11120
11761
  if (!server || typeof server !== "object") continue;
@@ -11125,9 +11766,9 @@ function findInstalledRuntimeScriptIssues(rootDir, manifest) {
11125
11766
  ...args
11126
11767
  ].flatMap(extractBundleCommandTargets);
11127
11768
  for (const target of commandTargets) {
11128
- const resolved = resolveBundleReference(rootDir, target);
11129
- if (!resolved || !existsSync3(resolved) || !resolved.endsWith(".sh")) continue;
11130
- const content = readFileSync3(resolved, "utf-8");
11769
+ const resolved = resolveBundleReference2(rootDir, target);
11770
+ if (!resolved || !existsSync5(resolved) || !resolved.endsWith(".sh")) continue;
11771
+ const content = readFileSync4(resolved, "utf-8");
11131
11772
  if (!content.includes("check-env.sh")) continue;
11132
11773
  const relativePath = resolved.startsWith(`${rootDir}/`) ? resolved.slice(rootDir.length + 1) : resolved;
11133
11774
  issues.add(`runtime script ${relativePath} for MCP server "${serverName}" still references installer-owned scripts/check-env.sh`);
@@ -11142,12 +11783,12 @@ function planInstallPlugin(distDir, pluginName, platforms) {
11142
11783
  const targets = getInstallTargets(pluginName);
11143
11784
  const filtered = platforms ? targets.filter((t) => platforms.includes(t.platform)) : targets;
11144
11785
  return filtered.map((target) => {
11145
- const sourceDir = resolve3(distDir, target.platform);
11786
+ const sourceDir = resolve5(distDir, target.platform);
11146
11787
  return {
11147
11788
  ...target,
11148
11789
  sourceDir,
11149
- built: existsSync3(sourceDir),
11150
- existing: existsSync3(target.pluginDir)
11790
+ built: existsSync5(sourceDir),
11791
+ existing: existsSync5(target.pluginDir)
11151
11792
  };
11152
11793
  });
11153
11794
  }
@@ -11487,29 +12128,29 @@ function looksLegacySecretIdentifier(value) {
11487
12128
  }
11488
12129
  function walkInstalledBundleFiles(rootDir, currentDir = rootDir) {
11489
12130
  const files = [];
11490
- for (const entry of readdirSync2(currentDir, { withFileTypes: true })) {
11491
- const absolutePath = resolve4(currentDir, entry.name);
12131
+ for (const entry of readdirSync3(currentDir, { withFileTypes: true })) {
12132
+ const absolutePath = resolve6(currentDir, entry.name);
11492
12133
  if (entry.isDirectory()) {
11493
12134
  files.push(...walkInstalledBundleFiles(rootDir, absolutePath));
11494
12135
  continue;
11495
12136
  }
11496
12137
  if (!entry.isFile()) continue;
11497
- files.push(relative2(rootDir, absolutePath).replace(/\\/g, "/"));
12138
+ files.push(relative3(rootDir, absolutePath).replace(/\\/g, "/"));
11498
12139
  }
11499
12140
  return files.sort();
11500
12141
  }
11501
12142
  function readInstalledTextFile(rootDir, relativePath) {
11502
- const absolutePath = resolve4(rootDir, relativePath);
11503
- const content = readFileSync4(absolutePath);
12143
+ const absolutePath = resolve6(rootDir, relativePath);
12144
+ const content = readFileSync5(absolutePath);
11504
12145
  if (content.length > MAX_SECRET_SCAN_FILE_BYTES) return null;
11505
12146
  if (content.includes(0)) return null;
11506
12147
  return content.toString("utf-8");
11507
12148
  }
11508
12149
  function collectInstalledPlaintextSecretCandidates(rootDir) {
11509
- const userConfigPath = resolve4(rootDir, ".pluxx-user.json");
11510
- if (!existsSync4(userConfigPath)) return [];
12150
+ const userConfigPath = resolve6(rootDir, ".pluxx-user.json");
12151
+ if (!existsSync6(userConfigPath)) return [];
11511
12152
  try {
11512
- const payload = JSON.parse(readFileSync4(userConfigPath, "utf-8"));
12153
+ const payload = JSON.parse(readFileSync5(userConfigPath, "utf-8"));
11513
12154
  if (payload.secretStorage === "materialized") return [];
11514
12155
  const secretKeys = new Set(Array.isArray(payload.secretKeys) ? payload.secretKeys.filter((value) => typeof value === "string") : []);
11515
12156
  const secretEnv = new Set(Array.isArray(payload.secretEnv) ? payload.secretEnv.filter((value) => typeof value === "string") : []);
@@ -11621,7 +12262,7 @@ function addRuntimeChecks(checks, mode) {
11621
12262
  }
11622
12263
  }
11623
12264
  function detectConsumerLayout(rootDir) {
11624
- if (existsSync4(resolve4(rootDir, ".claude-plugin/plugin.json"))) {
12265
+ if (existsSync6(resolve6(rootDir, ".claude-plugin/plugin.json"))) {
11625
12266
  return {
11626
12267
  kind: "installed-platform",
11627
12268
  platform: "claude-code",
@@ -11629,7 +12270,7 @@ function detectConsumerLayout(rootDir) {
11629
12270
  mcpConfigPath: ".mcp.json"
11630
12271
  };
11631
12272
  }
11632
- if (existsSync4(resolve4(rootDir, ".cursor-plugin/plugin.json"))) {
12273
+ if (existsSync6(resolve6(rootDir, ".cursor-plugin/plugin.json"))) {
11633
12274
  return {
11634
12275
  kind: "installed-platform",
11635
12276
  platform: "cursor",
@@ -11637,7 +12278,7 @@ function detectConsumerLayout(rootDir) {
11637
12278
  mcpConfigPath: "mcp.json"
11638
12279
  };
11639
12280
  }
11640
- if (existsSync4(resolve4(rootDir, ".codex-plugin/plugin.json"))) {
12281
+ if (existsSync6(resolve6(rootDir, ".codex-plugin/plugin.json"))) {
11641
12282
  return {
11642
12283
  kind: "installed-platform",
11643
12284
  platform: "codex",
@@ -11645,11 +12286,11 @@ function detectConsumerLayout(rootDir) {
11645
12286
  mcpConfigPath: ".mcp.json"
11646
12287
  };
11647
12288
  }
11648
- const packagePath = resolve4(rootDir, "package.json");
11649
- const indexPath = resolve4(rootDir, "index.ts");
11650
- if (existsSync4(packagePath) && existsSync4(indexPath)) {
12289
+ const packagePath = resolve6(rootDir, "package.json");
12290
+ const indexPath = resolve6(rootDir, "index.ts");
12291
+ if (existsSync6(packagePath) && existsSync6(indexPath)) {
11651
12292
  try {
11652
- const pkg = JSON.parse(readFileSync4(packagePath, "utf-8"));
12293
+ const pkg = JSON.parse(readFileSync5(packagePath, "utf-8"));
11653
12294
  if (pkg.peerDependencies?.["@opencode-ai/plugin"] || pkg.keywords?.includes("opencode-plugin")) {
11654
12295
  return {
11655
12296
  kind: "installed-platform",
@@ -11665,22 +12306,22 @@ function detectConsumerLayout(rootDir) {
11665
12306
  };
11666
12307
  }
11667
12308
  }
11668
- if (CONFIG_FILES.some((filename) => existsSync4(resolve4(rootDir, filename)))) {
12309
+ if (CONFIG_FILES.some((filename) => existsSync6(resolve6(rootDir, filename)))) {
11669
12310
  return { kind: "source-project" };
11670
12311
  }
11671
- if (["claude-code", "cursor", "codex", "opencode"].some((dir) => existsSync4(resolve4(rootDir, dir)))) {
12312
+ if (["claude-code", "cursor", "codex", "opencode"].some((dir) => existsSync6(resolve6(rootDir, dir)))) {
11672
12313
  return { kind: "multi-target-dist" };
11673
12314
  }
11674
12315
  return { kind: "unknown" };
11675
12316
  }
11676
12317
  function readJsonFile(rootDir, relativePath) {
11677
- return JSON.parse(readFileSync4(resolve4(rootDir, relativePath), "utf-8"));
12318
+ return JSON.parse(readFileSync5(resolve6(rootDir, relativePath), "utf-8"));
11678
12319
  }
11679
12320
  function listCodexConfigCandidates(projectRoot) {
11680
- const projectCandidate = resolve4(projectRoot ?? process.cwd(), ".codex/config.toml");
11681
- const homeDir = process.env.HOME?.trim() || homedir();
11682
- const codexHome = process.env.CODEX_HOME?.trim() || resolve4(homeDir, ".codex");
11683
- const userCandidate = resolve4(codexHome, "config.toml");
12321
+ const projectCandidate = resolve6(projectRoot ?? process.cwd(), ".codex/config.toml");
12322
+ const homeDir = process.env.HOME?.trim() || homedir2();
12323
+ const codexHome = process.env.CODEX_HOME?.trim() || resolve6(homeDir, ".codex");
12324
+ const userCandidate = resolve6(codexHome, "config.toml");
11684
12325
  const seen = /* @__PURE__ */ new Set();
11685
12326
  const candidates = [];
11686
12327
  for (const candidate of [
@@ -11695,7 +12336,7 @@ function listCodexConfigCandidates(projectRoot) {
11695
12336
  }
11696
12337
  function readCodexHooksFeatureFlag(filePath) {
11697
12338
  let inFeaturesTable = false;
11698
- const lines = readFileSync4(filePath, "utf-8").split(/\r?\n/);
12339
+ const lines = readFileSync5(filePath, "utf-8").split(/\r?\n/);
11699
12340
  let pluginBundled;
11700
12341
  let recommended;
11701
12342
  let alternate;
@@ -11744,7 +12385,7 @@ function readCodexHooksFeatureFlag(filePath) {
11744
12385
  }
11745
12386
  function probeCodexHooksFeatureFlags(projectRoot) {
11746
12387
  return listCodexConfigCandidates(projectRoot).map((candidate) => {
11747
- if (!existsSync4(candidate.path)) {
12388
+ if (!existsSync6(candidate.path)) {
11748
12389
  return {
11749
12390
  ...candidate,
11750
12391
  exists: false,
@@ -11779,7 +12420,7 @@ function probeCodexHooksFeatureFlags(projectRoot) {
11779
12420
  }
11780
12421
  function probeCodexMcpApprovalEntries(projectRoot) {
11781
12422
  return listCodexConfigCandidates(projectRoot).map((candidate) => {
11782
- if (!existsSync4(candidate.path)) {
12423
+ if (!existsSync6(candidate.path)) {
11783
12424
  return {
11784
12425
  ...candidate,
11785
12426
  exists: false,
@@ -11790,7 +12431,7 @@ function probeCodexMcpApprovalEntries(projectRoot) {
11790
12431
  return {
11791
12432
  ...candidate,
11792
12433
  exists: true,
11793
- approvals: parseCodexApprovedMcpToolsFromToml(readFileSync4(candidate.path, "utf-8"))
12434
+ approvals: parseCodexApprovedMcpToolsFromToml(readFileSync5(candidate.path, "utf-8"))
11794
12435
  };
11795
12436
  } catch (error) {
11796
12437
  return {
@@ -11805,7 +12446,7 @@ function probeCodexMcpApprovalEntries(projectRoot) {
11805
12446
  function getCodexProjectPathCandidates(projectRoot) {
11806
12447
  if (!projectRoot) return [];
11807
12448
  const candidates = /* @__PURE__ */ new Set();
11808
- const resolvedProjectRoot = resolve4(projectRoot);
12449
+ const resolvedProjectRoot = resolve6(projectRoot);
11809
12450
  candidates.add(resolvedProjectRoot);
11810
12451
  try {
11811
12452
  candidates.add(realpathSync(resolvedProjectRoot));
@@ -11825,7 +12466,7 @@ function parseCodexProjectKeySegment(segment) {
11825
12466
  function readCodexProjectTrust(filePath, projectPaths) {
11826
12467
  const matchedPaths = /* @__PURE__ */ new Set();
11827
12468
  let currentProjectKey = null;
11828
- const lines = readFileSync4(filePath, "utf-8").split(/\r?\n/);
12469
+ const lines = readFileSync5(filePath, "utf-8").split(/\r?\n/);
11829
12470
  for (const rawLine of lines) {
11830
12471
  const line = stripTomlComment(rawLine).trim();
11831
12472
  if (!line) continue;
@@ -11862,7 +12503,7 @@ function probeCodexProjectTrust(projectRoot) {
11862
12503
  if (projectPaths.length === 0) return void 0;
11863
12504
  const userConfig = listCodexConfigCandidates(projectRoot).find((candidate) => candidate.scope === "user");
11864
12505
  if (!userConfig) return void 0;
11865
- if (!existsSync4(userConfig.path)) {
12506
+ if (!existsSync6(userConfig.path)) {
11866
12507
  return {
11867
12508
  path: userConfig.path,
11868
12509
  exists: false,
@@ -11901,16 +12542,16 @@ function getClaudeManagedSettingsPath() {
11901
12542
  }
11902
12543
  }
11903
12544
  function listClaudeSettingsCandidates(projectRoot) {
11904
- const homeDir = process.env.HOME?.trim() || homedir();
12545
+ const homeDir = process.env.HOME?.trim() || homedir2();
11905
12546
  const candidates = [];
11906
12547
  const managedPath = getClaudeManagedSettingsPath();
11907
12548
  if (managedPath) {
11908
12549
  candidates.push({ path: managedPath, scope: "managed" });
11909
12550
  }
11910
- candidates.push({ path: resolve4(homeDir, ".claude/settings.json"), scope: "user" });
12551
+ candidates.push({ path: resolve6(homeDir, ".claude/settings.json"), scope: "user" });
11911
12552
  if (projectRoot) {
11912
- candidates.push({ path: resolve4(projectRoot, ".claude/settings.json"), scope: "project" });
11913
- candidates.push({ path: resolve4(projectRoot, ".claude/settings.local.json"), scope: "local" });
12553
+ candidates.push({ path: resolve6(projectRoot, ".claude/settings.json"), scope: "project" });
12554
+ candidates.push({ path: resolve6(projectRoot, ".claude/settings.local.json"), scope: "local" });
11914
12555
  }
11915
12556
  const seen = /* @__PURE__ */ new Set();
11916
12557
  return candidates.filter((candidate) => {
@@ -11925,7 +12566,7 @@ function readClaudeDisableAllHooks(filePath) {
11925
12566
  }
11926
12567
  function probeClaudeDisableAllHooks(projectRoot) {
11927
12568
  return listClaudeSettingsCandidates(projectRoot).map((candidate) => {
11928
- if (!existsSync4(candidate.path)) {
12569
+ if (!existsSync6(candidate.path)) {
11929
12570
  return {
11930
12571
  ...candidate,
11931
12572
  exists: false,
@@ -11949,7 +12590,7 @@ function probeClaudeDisableAllHooks(projectRoot) {
11949
12590
  });
11950
12591
  }
11951
12592
  function getInstalledClaudeHooksReference(rootDir, manifest) {
11952
- if (existsSync4(resolve4(rootDir, "hooks/hooks.json"))) {
12593
+ if (existsSync6(resolve6(rootDir, "hooks/hooks.json"))) {
11953
12594
  return "./hooks/hooks.json";
11954
12595
  }
11955
12596
  const manifestReference = typeof manifest.hooks === "string" ? manifest.hooks : void 0;
@@ -11957,7 +12598,7 @@ function getInstalledClaudeHooksReference(rootDir, manifest) {
11957
12598
  }
11958
12599
  function checkConsumerBundlePath(checks, rootDir) {
11959
12600
  try {
11960
- accessSync(rootDir, constants.R_OK);
12601
+ accessSync2(rootDir, constants2.R_OK);
11961
12602
  const details = lstatSync(rootDir);
11962
12603
  addCheck(checks, {
11963
12604
  level: "success",
@@ -12004,8 +12645,8 @@ function checkConsumerManifest(checks, rootDir, layout) {
12004
12645
  }
12005
12646
  function checkInstalledUserConfig(checks, rootDir) {
12006
12647
  const userConfigPath = ".pluxx-user.json";
12007
- const resolvedPath = resolve4(rootDir, userConfigPath);
12008
- if (!existsSync4(resolvedPath)) {
12648
+ const resolvedPath = resolve6(rootDir, userConfigPath);
12649
+ if (!existsSync6(resolvedPath)) {
12009
12650
  addCheck(checks, {
12010
12651
  level: "info",
12011
12652
  code: "consumer-user-config-missing",
@@ -12017,7 +12658,7 @@ function checkInstalledUserConfig(checks, rootDir) {
12017
12658
  return;
12018
12659
  }
12019
12660
  try {
12020
- const payload = JSON.parse(readFileSync4(resolvedPath, "utf-8"));
12661
+ const payload = JSON.parse(readFileSync5(resolvedPath, "utf-8"));
12021
12662
  const valueCount = Object.keys(payload.values ?? {}).length;
12022
12663
  const envCount = Object.keys(payload.env ?? {}).length;
12023
12664
  const placeholderKeys = [
@@ -12055,8 +12696,8 @@ function checkInstalledUserConfig(checks, rootDir) {
12055
12696
  }
12056
12697
  function checkInstalledEnvValidation(checks, rootDir) {
12057
12698
  const envScriptPath = INSTALLER_OWNED_CHECK_ENV_PATH;
12058
- const resolvedPath = resolve4(rootDir, envScriptPath);
12059
- if (!existsSync4(resolvedPath)) {
12699
+ const resolvedPath = resolve6(rootDir, envScriptPath);
12700
+ if (!existsSync6(resolvedPath)) {
12060
12701
  addCheck(checks, {
12061
12702
  level: "info",
12062
12703
  code: "consumer-env-script-missing",
@@ -12067,7 +12708,7 @@ function checkInstalledEnvValidation(checks, rootDir) {
12067
12708
  });
12068
12709
  return;
12069
12710
  }
12070
- const content = readFileSync4(resolvedPath, "utf-8");
12711
+ const content = readFileSync5(resolvedPath, "utf-8");
12071
12712
  if (content.includes(MATERIALIZED_ENV_MARKER)) {
12072
12713
  addCheck(checks, {
12073
12714
  level: "success",
@@ -12095,7 +12736,7 @@ function checkInstalledRuntimeScriptRoles(checks, rootDir) {
12095
12736
  "scripts/bootstrap-runtime.sh",
12096
12737
  "scripts/start-mcp.sh"
12097
12738
  ];
12098
- const presentRoles = roleFiles.filter((relativePath) => existsSync4(resolve4(rootDir, relativePath))).map((relativePath) => getRuntimeScriptRoleForPath(relativePath)).filter((role) => role !== null);
12739
+ const presentRoles = roleFiles.filter((relativePath) => existsSync6(resolve6(rootDir, relativePath))).map((relativePath) => getRuntimeScriptRoleForPath(relativePath)).filter((role) => role !== null);
12099
12740
  if (presentRoles.length === 0) return;
12100
12741
  addCheck(checks, {
12101
12742
  level: "info",
@@ -12118,8 +12759,8 @@ async function checkInstalledMcpConfig(checks, rootDir, layout) {
12118
12759
  });
12119
12760
  return;
12120
12761
  }
12121
- const resolvedPath = resolve4(rootDir, layout.mcpConfigPath);
12122
- if (!existsSync4(resolvedPath)) {
12762
+ const resolvedPath = resolve6(rootDir, layout.mcpConfigPath);
12763
+ if (!existsSync6(resolvedPath)) {
12123
12764
  addCheck(checks, {
12124
12765
  level: "info",
12125
12766
  code: "consumer-mcp-config-missing",
@@ -12439,12 +13080,12 @@ function checkInstalledCodexProjectTrust(checks, rootDir, layout, options) {
12439
13080
  function checkInstalledCodexPermissionCompanion(checks, rootDir, layout, options) {
12440
13081
  if (layout.platform !== "codex") return;
12441
13082
  if (checks.some((check) => check.code === "consumer-bundle-integrity-invalid")) return;
12442
- const companionPath = resolve4(rootDir, ".codex/config.generated.toml");
13083
+ const companionPath = resolve6(rootDir, ".codex/config.generated.toml");
12443
13084
  const companionReference = ".codex/config.generated.toml";
12444
- if (!existsSync4(companionPath)) return;
13085
+ if (!existsSync6(companionPath)) return;
12445
13086
  let expectedEntries;
12446
13087
  try {
12447
- expectedEntries = parseCodexApprovedMcpToolsFromToml(readFileSync4(companionPath, "utf-8"));
13088
+ expectedEntries = parseCodexApprovedMcpToolsFromToml(readFileSync5(companionPath, "utf-8"));
12448
13089
  } catch (error) {
12449
13090
  addCheck(checks, {
12450
13091
  level: "error",
@@ -12573,11 +13214,11 @@ function smokeCheckInstalledPermissionHook(rootDir, layout) {
12573
13214
  if (layout.platform !== "claude-code" && layout.platform !== "cursor") {
12574
13215
  return null;
12575
13216
  }
12576
- const scriptPath = resolve4(rootDir, "hooks/pluxx-permissions.mjs");
12577
- if (!existsSync4(scriptPath)) {
13217
+ const scriptPath = resolve6(rootDir, "hooks/pluxx-permissions.mjs");
13218
+ if (!existsSync6(scriptPath)) {
12578
13219
  return null;
12579
13220
  }
12580
- const rules = parseInstalledPermissionRules(readFileSync4(scriptPath, "utf-8"));
13221
+ const rules = parseInstalledPermissionRules(readFileSync5(scriptPath, "utf-8"));
12581
13222
  const selected = /* @__PURE__ */ new Map();
12582
13223
  for (const rule of rules) {
12583
13224
  if (selected.has(rule.action)) continue;
@@ -12663,8 +13304,8 @@ function findMissingInstalledStdioRuntimePaths(rootDir, stdioEntries) {
12663
13304
  const args = Array.isArray(server.args) ? server.args.filter((value) => typeof value === "string") : [];
12664
13305
  for (const candidate of [command, ...args]) {
12665
13306
  if (!candidate || !isLikelyLocalRuntimePath(candidate)) continue;
12666
- const resolvedPath = resolve4(rootDir, candidate);
12667
- if (!existsSync4(resolvedPath)) {
13307
+ const resolvedPath = resolve6(rootDir, candidate);
13308
+ if (!existsSync6(resolvedPath)) {
12668
13309
  missing.add(candidate);
12669
13310
  }
12670
13311
  }
@@ -12775,7 +13416,7 @@ function checkInstalledOpenCodeHostBridge(checks, rootDir) {
12775
13416
  const pluginName = basename2(rootDir);
12776
13417
  const entryPath = `${rootDir}.ts`;
12777
13418
  const entryRelativePath = `${pluginName}.ts`;
12778
- if (!existsSync4(entryPath)) {
13419
+ if (!existsSync6(entryPath)) {
12779
13420
  addCheck(checks, {
12780
13421
  level: "error",
12781
13422
  code: "consumer-opencode-entry-missing",
@@ -12786,7 +13427,7 @@ function checkInstalledOpenCodeHostBridge(checks, rootDir) {
12786
13427
  });
12787
13428
  return;
12788
13429
  }
12789
- const entryContent = readFileSync4(entryPath, "utf-8");
13430
+ const entryContent = readFileSync5(entryPath, "utf-8");
12790
13431
  const expectedImport = `import * as PluginModule from "./${pluginName}/index.ts"`;
12791
13432
  const expectedBridge = `directory: join(context.directory, "${pluginName}")`;
12792
13433
  if (!entryContent.includes(expectedImport) || !entryContent.includes(expectedBridge)) {
@@ -12814,8 +13455,8 @@ function checkInstalledOpenCodeSkills(checks, rootDir) {
12814
13455
  return;
12815
13456
  }
12816
13457
  const pluginName = basename2(rootDir);
12817
- const sourceSkillsDir = resolve4(rootDir, "skills");
12818
- if (!existsSync4(sourceSkillsDir)) {
13458
+ const sourceSkillsDir = resolve6(rootDir, "skills");
13459
+ if (!existsSync6(sourceSkillsDir)) {
12819
13460
  addCheck(checks, {
12820
13461
  level: "info",
12821
13462
  code: "consumer-opencode-skill-sync-not-applicable",
@@ -12826,21 +13467,21 @@ function checkInstalledOpenCodeSkills(checks, rootDir) {
12826
13467
  });
12827
13468
  return;
12828
13469
  }
12829
- const skillRoot = resolve4(dirname2(dirname2(rootDir)), "skills");
13470
+ const skillRoot = resolve6(dirname2(dirname2(rootDir)), "skills");
12830
13471
  const missingSkills = [];
12831
13472
  const malformedSkills = [];
12832
13473
  let expectedSkillCount = 0;
12833
- for (const entry of readdirSync2(sourceSkillsDir, { withFileTypes: true })) {
13474
+ for (const entry of readdirSync3(sourceSkillsDir, { withFileTypes: true })) {
12834
13475
  if (!entry.isDirectory()) continue;
12835
- const sourceSkillPath = resolve4(sourceSkillsDir, entry.name, "SKILL.md");
12836
- if (!existsSync4(sourceSkillPath)) continue;
13476
+ const sourceSkillPath = resolve6(sourceSkillsDir, entry.name, "SKILL.md");
13477
+ if (!existsSync6(sourceSkillPath)) continue;
12837
13478
  expectedSkillCount++;
12838
- const installedSkillPath = resolve4(skillRoot, `${pluginName}-${entry.name}`, "SKILL.md");
12839
- if (!existsSync4(installedSkillPath)) {
13479
+ const installedSkillPath = resolve6(skillRoot, `${pluginName}-${entry.name}`, "SKILL.md");
13480
+ if (!existsSync6(installedSkillPath)) {
12840
13481
  missingSkills.push(`${pluginName}-${entry.name}`);
12841
13482
  continue;
12842
13483
  }
12843
- const installedContent = readFileSync4(installedSkillPath, "utf-8");
13484
+ const installedContent = readFileSync5(installedSkillPath, "utf-8");
12844
13485
  if (!installedContent.includes(`${pluginName}/`)) {
12845
13486
  malformedSkills.push(`${pluginName}-${entry.name}`);
12846
13487
  }
@@ -12943,7 +13584,7 @@ async function doctorConsumer(rootDir = process.cwd(), options = {}) {
12943
13584
  // src/cli/verify-install.ts
12944
13585
  function buildCheckFromReport(target, pluginName, report) {
12945
13586
  const consumerPath = resolveInstalledConsumerPath(target, pluginName);
12946
- const staleReason = target.built && existsSync5(consumerPath) ? detectStaleInstall(target, pluginName, consumerPath) : void 0;
13587
+ const staleReason = target.built && existsSync7(consumerPath) ? detectStaleInstall(target, pluginName, consumerPath) : void 0;
12947
13588
  const stale = staleReason !== void 0;
12948
13589
  const issues = listVerifyInstallIssues(report.checks);
12949
13590
  return {
@@ -12951,7 +13592,7 @@ function buildCheckFromReport(target, pluginName, report) {
12951
13592
  installPath: consumerPath,
12952
13593
  consumerPath,
12953
13594
  built: target.built,
12954
- installed: existsSync5(consumerPath),
13595
+ installed: existsSync7(consumerPath),
12955
13596
  stale,
12956
13597
  ...staleReason ? { staleReason } : {},
12957
13598
  ok: report.errors === 0 && !stale,
@@ -12990,10 +13631,10 @@ function manifestPathForPlatform2(platform) {
12990
13631
  function readInstalledManifestVersion(rootDir, platform) {
12991
13632
  const manifestPath = manifestPathForPlatform2(platform);
12992
13633
  if (!manifestPath) return void 0;
12993
- const filepath = resolve5(rootDir, manifestPath);
12994
- if (!existsSync5(filepath)) return void 0;
13634
+ const filepath = resolve7(rootDir, manifestPath);
13635
+ if (!existsSync7(filepath)) return void 0;
12995
13636
  try {
12996
- const manifest = JSON.parse(readFileSync5(filepath, "utf-8"));
13637
+ const manifest = JSON.parse(readFileSync6(filepath, "utf-8"));
12997
13638
  return typeof manifest.version === "string" ? manifest.version : void 0;
12998
13639
  } catch {
12999
13640
  return void 0;
@@ -13001,16 +13642,16 @@ function readInstalledManifestVersion(rootDir, platform) {
13001
13642
  }
13002
13643
  function findCodexCacheCandidates(pluginName) {
13003
13644
  const home = process.env.HOME ?? "~";
13004
- const cacheRoot = resolve5(home, ".codex/plugins/cache");
13005
- if (!existsSync5(cacheRoot)) return [];
13645
+ const cacheRoot = resolve7(home, ".codex/plugins/cache");
13646
+ if (!existsSync7(cacheRoot)) return [];
13006
13647
  const candidates = [];
13007
- for (const marketplace of readdirSync3(cacheRoot)) {
13008
- const pluginRoot = resolve5(cacheRoot, marketplace, pluginName);
13009
- if (!existsSync5(pluginRoot)) continue;
13010
- for (const versionDir of readdirSync3(pluginRoot)) {
13011
- const candidatePath = resolve5(pluginRoot, versionDir);
13648
+ for (const marketplace of readdirSync4(cacheRoot)) {
13649
+ const pluginRoot = resolve7(cacheRoot, marketplace, pluginName);
13650
+ if (!existsSync7(pluginRoot)) continue;
13651
+ for (const versionDir of readdirSync4(pluginRoot)) {
13652
+ const candidatePath = resolve7(pluginRoot, versionDir);
13012
13653
  try {
13013
- const stats = statSync(candidatePath);
13654
+ const stats = statSync3(candidatePath);
13014
13655
  if (!stats.isDirectory()) continue;
13015
13656
  candidates.push({
13016
13657
  path: candidatePath,
@@ -13023,11 +13664,55 @@ function findCodexCacheCandidates(pluginName) {
13023
13664
  }
13024
13665
  return candidates.sort((a, b) => b.mtimeMs - a.mtimeMs);
13025
13666
  }
13026
- function detectCodexCacheStaleness(pluginName, builtVersion) {
13667
+ function hashInstalledBundle(rootDir) {
13668
+ const hash = createHash2("sha256");
13669
+ const visit = (currentDir, relativePrefix = "") => {
13670
+ for (const entry of readdirSync4(currentDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
13671
+ const absolutePath = resolve7(currentDir, entry.name);
13672
+ const relativePath = relativePrefix ? `${relativePrefix}/${entry.name}` : entry.name;
13673
+ if (entry.isDirectory()) {
13674
+ visit(absolutePath, relativePath);
13675
+ continue;
13676
+ }
13677
+ const stats = lstatSync2(absolutePath);
13678
+ if (stats.isSymbolicLink()) {
13679
+ hash.update(relativePath);
13680
+ hash.update("\0symlink\0");
13681
+ hash.update(readlinkSync(absolutePath));
13682
+ hash.update("\0");
13683
+ continue;
13684
+ }
13685
+ if (!entry.isFile()) continue;
13686
+ hash.update(relativePath);
13687
+ hash.update("\0file\0");
13688
+ hash.update(readFileSync6(absolutePath));
13689
+ hash.update("\0");
13690
+ }
13691
+ };
13692
+ visit(rootDir);
13693
+ return hash.digest("hex");
13694
+ }
13695
+ function detectCodexCacheStaleness(pluginName, builtVersion, consumerPath) {
13027
13696
  if (!builtVersion) return void 0;
13028
13697
  const candidates = findCodexCacheCandidates(pluginName);
13029
13698
  if (candidates.length === 0) return void 0;
13030
- if (candidates.some((candidate) => candidate.version === builtVersion)) return void 0;
13699
+ const matchingVersionCandidates = candidates.filter((candidate) => candidate.version === builtVersion);
13700
+ if (matchingVersionCandidates.length > 0) {
13701
+ let installedSignature;
13702
+ try {
13703
+ installedSignature = hashInstalledBundle(consumerPath);
13704
+ } catch {
13705
+ return void 0;
13706
+ }
13707
+ for (const candidate of matchingVersionCandidates) {
13708
+ try {
13709
+ if (hashInstalledBundle(candidate.path) === installedSignature) return void 0;
13710
+ } catch {
13711
+ }
13712
+ }
13713
+ const newestMatchingVersion = matchingVersionCandidates[0];
13714
+ return `Codex active cache appears stale at ${newestMatchingVersion.path}; cached version ${newestMatchingVersion.version ?? "unknown"} matches the built version but the cached bundle contents do not match the active local install at ${consumerPath}. Use Plugins > Refresh if available, or restart/reinstall Codex to load the current plugin bundle.`;
13715
+ }
13031
13716
  const newest = candidates[0];
13032
13717
  return `Codex active cache appears stale at ${newest.path}; cached version ${newest.version ?? "unknown"} does not match built version ${builtVersion}. Use Plugins > Refresh if available, or restart/reinstall Codex to load the current plugin bundle.`;
13033
13718
  }
@@ -13050,13 +13735,13 @@ function detectStaleInstall(target, pluginName, consumerPath) {
13050
13735
  return `installed version ${installedVersion} does not match built version ${builtVersion}`;
13051
13736
  }
13052
13737
  if (target.platform === "codex") {
13053
- return detectCodexCacheStaleness(pluginName, builtVersion);
13738
+ return detectCodexCacheStaleness(pluginName, builtVersion, consumerPath);
13054
13739
  }
13055
13740
  return void 0;
13056
13741
  }
13057
13742
  async function verifyInstall(config, options = {}) {
13058
13743
  const rootDir = options.rootDir ?? process.cwd();
13059
- const distDir = resolve5(rootDir, config.outDir);
13744
+ const distDir = resolve7(rootDir, config.outDir);
13060
13745
  const targets = options.targets ?? config.targets;
13061
13746
  const installPlan = planInstallPlugin(distDir, config.name, targets);
13062
13747
  const filteredPlan = options.builtOnly ? installPlan.filter((target) => target.built) : installPlan;
@@ -13119,7 +13804,9 @@ function getVerifyInstallRecoveryActions(check) {
13119
13804
  }
13120
13805
  export {
13121
13806
  CORE_FOUR_PRIMITIVE_CAPABILITIES,
13807
+ CORE_HOST_FAMILIES,
13122
13808
  INSTALLER_OWNED_CHECK_ENV_PATH,
13809
+ INSTALL_REFERENCE_SCHEMES,
13123
13810
  PLATFORM_LIMITS,
13124
13811
  PLATFORM_LIMIT_POLICIES,
13125
13812
  PLATFORM_VALIDATION_RULES,
@@ -13127,10 +13814,15 @@ export {
13127
13814
  PLUXX_COMPILER_INTENT_PATH,
13128
13815
  PORTABLE_RUNTIME_SCRIPT_ROLES,
13129
13816
  PluginConfigSchema,
13817
+ assertGeneratedBundlesCurrent,
13130
13818
  buildGeneratedPermissionHookScript,
13819
+ buildHostTargetSelection,
13131
13820
  buildOpenCodePermissionMap,
13821
+ checkGeneratedBundles,
13132
13822
  collectPermissionRules,
13133
13823
  definePlugin,
13824
+ detectHostFamilies,
13825
+ formatInstallReference,
13134
13826
  formatPublishPlan,
13135
13827
  getConfiguredCompilerBuckets,
13136
13828
  getConsumerEnvScriptActiveDetail,
@@ -13146,6 +13838,7 @@ export {
13146
13838
  getRuntimeReadinessCapability,
13147
13839
  getRuntimeReadinessExternalConfigNote,
13148
13840
  getRuntimeReadinessNamedPromptTargetNote,
13841
+ parseInstallReference,
13149
13842
  parsePermissionRule,
13150
13843
  permissionRulesNeedToolLevelDowngrade,
13151
13844
  planPublish,