@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.
- package/README.md +2 -0
- package/bin/pluxx.js +1 -6
- package/dist/agent-translation-registry.d.ts +9 -1
- package/dist/agent-translation-registry.d.ts.map +1 -1
- package/dist/bundle-check.d.ts +21 -0
- package/dist/bundle-check.d.ts.map +1 -0
- package/dist/claude-hook-probe.d.ts.map +1 -1
- package/dist/cli/agent.d.ts.map +1 -1
- package/dist/cli/codex-apply.d.ts +39 -0
- package/dist/cli/codex-apply.d.ts.map +1 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +30809 -6240
- package/dist/cli/install.d.ts.map +1 -1
- package/dist/cli/publish.d.ts.map +1 -1
- package/dist/cli/sync-from-mcp.d.ts +1 -1
- package/dist/cli/sync-from-mcp.d.ts.map +1 -1
- package/dist/cli/verify-install.d.ts.map +1 -1
- package/dist/codex-mcp-probe.d.ts.map +1 -1
- package/dist/command-translation-registry.d.ts +11 -3
- package/dist/command-translation-registry.d.ts.map +1 -1
- package/dist/distribution-lifecycle.d.ts +18 -0
- package/dist/distribution-lifecycle.d.ts.map +1 -1
- package/dist/generators/codex/index.d.ts.map +1 -1
- package/dist/generators/index.d.ts.map +1 -1
- package/dist/hook-command-env.d.ts +1 -0
- package/dist/hook-command-env.d.ts.map +1 -1
- package/dist/host-detection.d.ts +38 -0
- package/dist/host-detection.d.ts.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +865 -172
- package/dist/install-reference.d.ts +34 -0
- package/dist/install-reference.d.ts.map +1 -0
- package/dist/schema.d.ts +36 -36
- package/dist/validation/platform-rules.d.ts.map +1 -1
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
8709
|
-
nativeSurfaces: ["commands/*", "slash commands"]
|
|
8760
|
+
...getCommandPrimitiveCapability("cursor")
|
|
8710
8761
|
},
|
|
8711
8762
|
agents: {
|
|
8712
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
8793
|
-
nativeSurfaces: ["commands/*.md", "config command definitions"]
|
|
8837
|
+
...getCommandPrimitiveCapability("opencode")
|
|
8794
8838
|
},
|
|
8795
8839
|
agents: {
|
|
8796
|
-
|
|
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:
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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 {
|
|
10751
|
-
import {
|
|
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
|
|
10756
|
-
import { homedir } from "os";
|
|
10757
|
-
import { basename as basename2, dirname as dirname2, relative as
|
|
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
|
|
10771
|
-
import { existsSync as
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
11455
|
+
pluginDir: resolve5(home, ".openhands/plugins", pluginName),
|
|
10826
11456
|
description: `~/.openhands/plugins/${pluginName}`
|
|
10827
11457
|
},
|
|
10828
11458
|
{
|
|
10829
11459
|
platform: "warp",
|
|
10830
|
-
pluginDir:
|
|
11460
|
+
pluginDir: resolve5(home, ".warp/plugins", pluginName),
|
|
10831
11461
|
description: `~/.warp/plugins/${pluginName}`
|
|
10832
11462
|
},
|
|
10833
11463
|
{
|
|
10834
11464
|
platform: "gemini-cli",
|
|
10835
|
-
pluginDir:
|
|
11465
|
+
pluginDir: resolve5(home, ".gemini/extensions", pluginName),
|
|
10836
11466
|
description: `~/.gemini/extensions/${pluginName}`
|
|
10837
11467
|
},
|
|
10838
11468
|
{
|
|
10839
11469
|
platform: "roo-code",
|
|
10840
|
-
pluginDir:
|
|
11470
|
+
pluginDir: resolve5(home, ".roo/plugins", pluginName),
|
|
10841
11471
|
description: `~/.roo/plugins/${pluginName}`
|
|
10842
11472
|
},
|
|
10843
11473
|
{
|
|
10844
11474
|
platform: "cline",
|
|
10845
|
-
pluginDir:
|
|
11475
|
+
pluginDir: resolve5(home, ".cline/plugins", pluginName),
|
|
10846
11476
|
description: `~/.cline/plugins/${pluginName}`
|
|
10847
11477
|
},
|
|
10848
11478
|
{
|
|
10849
11479
|
platform: "amp",
|
|
10850
|
-
pluginDir:
|
|
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 =
|
|
10862
|
-
if (!
|
|
11491
|
+
const filepath = resolve5(rootDir, manifestPath);
|
|
11492
|
+
if (!existsSync5(filepath)) return void 0;
|
|
10863
11493
|
try {
|
|
10864
|
-
const manifest = JSON.parse(
|
|
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
|
|
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 (
|
|
10889
|
-
if (
|
|
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
|
|
11541
|
+
function resolveBundleReference2(rootDir, value) {
|
|
10912
11542
|
if (isRelativeBundlePath(value)) {
|
|
10913
|
-
return
|
|
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
|
|
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 =
|
|
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) =>
|
|
11602
|
+
return extractBundleCommandTargets(command).map((target) => resolveBundleReference2(rootDir, target)).filter((target) => {
|
|
10973
11603
|
if (!target) return false;
|
|
10974
|
-
return
|
|
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 =
|
|
10986
|
-
const hookCommand = wrapper
|
|
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:
|
|
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 =
|
|
11006
|
-
if (
|
|
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 =
|
|
11020
|
-
const standardHooksPath =
|
|
11021
|
-
if (!manifestHooksPath || manifestHooksPath !== 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 =
|
|
11037
|
-
if (!
|
|
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(
|
|
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 =
|
|
11060
|
-
return resolved !== void 0 && !
|
|
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 || !
|
|
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(
|
|
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 =
|
|
11089
|
-
return resolved !== void 0 && !
|
|
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 =
|
|
11115
|
-
if (!mcpPath || !
|
|
11755
|
+
const mcpPath = resolveBundleReference2(rootDir, mcpReference);
|
|
11756
|
+
if (!mcpPath || !existsSync5(mcpPath)) return [];
|
|
11116
11757
|
try {
|
|
11117
|
-
const parsed = JSON.parse(
|
|
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 =
|
|
11129
|
-
if (!resolved || !
|
|
11130
|
-
const content =
|
|
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 =
|
|
11786
|
+
const sourceDir = resolve5(distDir, target.platform);
|
|
11146
11787
|
return {
|
|
11147
11788
|
...target,
|
|
11148
11789
|
sourceDir,
|
|
11149
|
-
built:
|
|
11150
|
-
existing:
|
|
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
|
|
11491
|
-
const absolutePath =
|
|
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(
|
|
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 =
|
|
11503
|
-
const content =
|
|
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 =
|
|
11510
|
-
if (!
|
|
12150
|
+
const userConfigPath = resolve6(rootDir, ".pluxx-user.json");
|
|
12151
|
+
if (!existsSync6(userConfigPath)) return [];
|
|
11511
12152
|
try {
|
|
11512
|
-
const payload = JSON.parse(
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 =
|
|
11649
|
-
const indexPath =
|
|
11650
|
-
if (
|
|
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(
|
|
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) =>
|
|
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) =>
|
|
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(
|
|
12318
|
+
return JSON.parse(readFileSync5(resolve6(rootDir, relativePath), "utf-8"));
|
|
11678
12319
|
}
|
|
11679
12320
|
function listCodexConfigCandidates(projectRoot) {
|
|
11680
|
-
const projectCandidate =
|
|
11681
|
-
const homeDir = process.env.HOME?.trim() ||
|
|
11682
|
-
const codexHome = process.env.CODEX_HOME?.trim() ||
|
|
11683
|
-
const userCandidate =
|
|
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 =
|
|
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 (!
|
|
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 (!
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 (!
|
|
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() ||
|
|
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:
|
|
12551
|
+
candidates.push({ path: resolve6(homeDir, ".claude/settings.json"), scope: "user" });
|
|
11911
12552
|
if (projectRoot) {
|
|
11912
|
-
candidates.push({ path:
|
|
11913
|
-
candidates.push({ path:
|
|
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 (!
|
|
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 (
|
|
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
|
-
|
|
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 =
|
|
12008
|
-
if (!
|
|
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(
|
|
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 =
|
|
12059
|
-
if (!
|
|
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 =
|
|
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) =>
|
|
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 =
|
|
12122
|
-
if (!
|
|
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 =
|
|
13083
|
+
const companionPath = resolve6(rootDir, ".codex/config.generated.toml");
|
|
12443
13084
|
const companionReference = ".codex/config.generated.toml";
|
|
12444
|
-
if (!
|
|
13085
|
+
if (!existsSync6(companionPath)) return;
|
|
12445
13086
|
let expectedEntries;
|
|
12446
13087
|
try {
|
|
12447
|
-
expectedEntries = parseCodexApprovedMcpToolsFromToml(
|
|
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 =
|
|
12577
|
-
if (!
|
|
13217
|
+
const scriptPath = resolve6(rootDir, "hooks/pluxx-permissions.mjs");
|
|
13218
|
+
if (!existsSync6(scriptPath)) {
|
|
12578
13219
|
return null;
|
|
12579
13220
|
}
|
|
12580
|
-
const rules = parseInstalledPermissionRules(
|
|
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 =
|
|
12667
|
-
if (!
|
|
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 (!
|
|
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 =
|
|
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 =
|
|
12818
|
-
if (!
|
|
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 =
|
|
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
|
|
13474
|
+
for (const entry of readdirSync3(sourceSkillsDir, { withFileTypes: true })) {
|
|
12834
13475
|
if (!entry.isDirectory()) continue;
|
|
12835
|
-
const sourceSkillPath =
|
|
12836
|
-
if (!
|
|
13476
|
+
const sourceSkillPath = resolve6(sourceSkillsDir, entry.name, "SKILL.md");
|
|
13477
|
+
if (!existsSync6(sourceSkillPath)) continue;
|
|
12837
13478
|
expectedSkillCount++;
|
|
12838
|
-
const installedSkillPath =
|
|
12839
|
-
if (!
|
|
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 =
|
|
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 &&
|
|
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:
|
|
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 =
|
|
12994
|
-
if (!
|
|
13634
|
+
const filepath = resolve7(rootDir, manifestPath);
|
|
13635
|
+
if (!existsSync7(filepath)) return void 0;
|
|
12995
13636
|
try {
|
|
12996
|
-
const manifest = JSON.parse(
|
|
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 =
|
|
13005
|
-
if (!
|
|
13645
|
+
const cacheRoot = resolve7(home, ".codex/plugins/cache");
|
|
13646
|
+
if (!existsSync7(cacheRoot)) return [];
|
|
13006
13647
|
const candidates = [];
|
|
13007
|
-
for (const marketplace of
|
|
13008
|
-
const pluginRoot =
|
|
13009
|
-
if (!
|
|
13010
|
-
for (const versionDir of
|
|
13011
|
-
const candidatePath =
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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,
|