@orchid-labs/pluxx 0.1.10 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/branding-completeness.d.ts +10 -0
  2. package/dist/branding-completeness.d.ts.map +1 -0
  3. package/dist/cli/agent.d.ts.map +1 -1
  4. package/dist/cli/doctor.d.ts.map +1 -1
  5. package/dist/cli/index.js +2141 -952
  6. package/dist/cli/install.d.ts +1 -0
  7. package/dist/cli/install.d.ts.map +1 -1
  8. package/dist/cli/lint.d.ts.map +1 -1
  9. package/dist/cli/migrate.d.ts.map +1 -1
  10. package/dist/commands.d.ts +1 -0
  11. package/dist/commands.d.ts.map +1 -1
  12. package/dist/compiler-intent.d.ts +28 -28
  13. package/dist/generators/base.d.ts.map +1 -1
  14. package/dist/generators/claude-code/index.d.ts.map +1 -1
  15. package/dist/generators/codex/index.d.ts +1 -0
  16. package/dist/generators/codex/index.d.ts.map +1 -1
  17. package/dist/generators/cursor/index.d.ts.map +1 -1
  18. package/dist/generators/hooks-warning.d.ts.map +1 -1
  19. package/dist/generators/opencode/index.d.ts +1 -0
  20. package/dist/generators/opencode/index.d.ts.map +1 -1
  21. package/dist/generators/shared/claude-family.d.ts.map +1 -1
  22. package/dist/hook-translation-registry.d.ts +15 -0
  23. package/dist/hook-translation-registry.d.ts.map +1 -0
  24. package/dist/index.d.ts +4 -2
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +587 -35
  27. package/dist/mcp-stdio-paths.d.ts +9 -0
  28. package/dist/mcp-stdio-paths.d.ts.map +1 -0
  29. package/dist/readiness.d.ts +13 -0
  30. package/dist/readiness.d.ts.map +1 -0
  31. package/dist/runtime-readiness-registry.d.ts +26 -0
  32. package/dist/runtime-readiness-registry.d.ts.map +1 -0
  33. package/dist/runtime-script-contract.d.ts +20 -0
  34. package/dist/runtime-script-contract.d.ts.map +1 -0
  35. package/dist/schema.d.ts +1444 -706
  36. package/dist/schema.d.ts.map +1 -1
  37. package/dist/skills.d.ts +27 -0
  38. package/dist/skills.d.ts.map +1 -0
  39. package/dist/validation/platform-rules.d.ts.map +1 -1
  40. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -7461,6 +7461,99 @@ var HooksSchema = external_exports.object({
7461
7461
  beforeTabFileRead: external_exports.array(HookEntrySchema).optional(),
7462
7462
  afterTabFileEdit: external_exports.array(HookEntrySchema).optional()
7463
7463
  }).catchall(external_exports.array(HookEntrySchema));
7464
+ var RuntimeReadinessRefreshSchema = external_exports.object({
7465
+ command: external_exports.string(),
7466
+ timeoutMs: external_exports.number().int().positive().default(1e4),
7467
+ detached: external_exports.boolean().default(true)
7468
+ });
7469
+ var RuntimeReadinessDependencySchema = external_exports.object({
7470
+ id: external_exports.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "Use lowercase kebab-case for readiness dependency ids"),
7471
+ kind: external_exports.enum(["status-file"]).default("status-file"),
7472
+ path: external_exports.string(),
7473
+ format: external_exports.enum(["json"]).default("json"),
7474
+ statusField: external_exports.string().default("status"),
7475
+ readyValues: external_exports.array(external_exports.string()).default(["succeeded"]),
7476
+ pendingValues: external_exports.array(external_exports.string()).default(["running"]),
7477
+ failedValues: external_exports.array(external_exports.string()).default(["failed"]),
7478
+ refresh: RuntimeReadinessRefreshSchema,
7479
+ description: external_exports.string().optional()
7480
+ }).superRefine((dependency, ctx) => {
7481
+ const ready = new Set(dependency.readyValues);
7482
+ const pending = new Set(dependency.pendingValues);
7483
+ const failed = new Set(dependency.failedValues);
7484
+ const overlap = [
7485
+ ...ready,
7486
+ ...pending
7487
+ ].filter(
7488
+ (value, index, values) => values.indexOf(value) === index && (ready.has(value) ? 1 : 0) + (pending.has(value) ? 1 : 0) + (failed.has(value) ? 1 : 0) > 1
7489
+ );
7490
+ if (overlap.length > 0) {
7491
+ ctx.addIssue({
7492
+ code: external_exports.ZodIssueCode.custom,
7493
+ path: ["readyValues"],
7494
+ message: `Readiness dependency values must not overlap across ready/pending/failed buckets: ${overlap.join(", ")}`
7495
+ });
7496
+ }
7497
+ });
7498
+ var RuntimeReadinessGateSchema = external_exports.object({
7499
+ dependency: external_exports.string(),
7500
+ applyTo: external_exports.array(external_exports.enum(["mcp-tools", "skills", "commands"])).nonempty().default(["mcp-tools"]),
7501
+ tools: external_exports.array(external_exports.string()).nonempty().optional(),
7502
+ skills: external_exports.array(external_exports.string()).nonempty().optional(),
7503
+ commands: external_exports.array(external_exports.string()).nonempty().optional(),
7504
+ timeoutMs: external_exports.number().int().positive().default(15e3),
7505
+ pollMs: external_exports.number().int().positive().default(500),
7506
+ onTimeout: external_exports.enum(["continue", "warn", "fail"]).default("warn"),
7507
+ message: external_exports.string().optional()
7508
+ }).superRefine((gate, ctx) => {
7509
+ if (gate.tools && !gate.applyTo.includes("mcp-tools")) {
7510
+ ctx.addIssue({
7511
+ code: external_exports.ZodIssueCode.custom,
7512
+ path: ["tools"],
7513
+ message: 'Runtime readiness gate.tools requires applyTo to include "mcp-tools".'
7514
+ });
7515
+ }
7516
+ if (gate.skills && !gate.applyTo.includes("skills")) {
7517
+ ctx.addIssue({
7518
+ code: external_exports.ZodIssueCode.custom,
7519
+ path: ["skills"],
7520
+ message: 'Runtime readiness gate.skills requires applyTo to include "skills".'
7521
+ });
7522
+ }
7523
+ if (gate.commands && !gate.applyTo.includes("commands")) {
7524
+ ctx.addIssue({
7525
+ code: external_exports.ZodIssueCode.custom,
7526
+ path: ["commands"],
7527
+ message: 'Runtime readiness gate.commands requires applyTo to include "commands".'
7528
+ });
7529
+ }
7530
+ });
7531
+ var RuntimeReadinessSchema = external_exports.object({
7532
+ dependencies: external_exports.array(RuntimeReadinessDependencySchema).default([]),
7533
+ gates: external_exports.array(RuntimeReadinessGateSchema).default([])
7534
+ }).superRefine((config, ctx) => {
7535
+ const seenDependencyIds = /* @__PURE__ */ new Set();
7536
+ for (const [index, dependency] of config.dependencies.entries()) {
7537
+ if (seenDependencyIds.has(dependency.id)) {
7538
+ ctx.addIssue({
7539
+ code: external_exports.ZodIssueCode.custom,
7540
+ path: ["dependencies", index, "id"],
7541
+ message: `Runtime readiness dependency id "${dependency.id}" is duplicated.`
7542
+ });
7543
+ }
7544
+ seenDependencyIds.add(dependency.id);
7545
+ }
7546
+ const dependencyIds = new Set(config.dependencies.map((dependency) => dependency.id));
7547
+ for (const [index, gate] of config.gates.entries()) {
7548
+ if (!dependencyIds.has(gate.dependency)) {
7549
+ ctx.addIssue({
7550
+ code: external_exports.ZodIssueCode.custom,
7551
+ path: ["gates", index, "dependency"],
7552
+ message: `Runtime readiness gate references unknown dependency "${gate.dependency}".`
7553
+ });
7554
+ }
7555
+ }
7556
+ });
7464
7557
  var BrandSchema = external_exports.object({
7465
7558
  displayName: external_exports.string(),
7466
7559
  shortDescription: external_exports.string().optional(),
@@ -7590,6 +7683,8 @@ var PluginConfigSchema = external_exports.object({
7590
7683
  instructions: external_exports.string().optional(),
7591
7684
  // MCP servers
7592
7685
  mcp: external_exports.record(external_exports.string(), McpServerSchema).optional(),
7686
+ // Runtime readiness gates
7687
+ readiness: RuntimeReadinessSchema.optional(),
7593
7688
  // Hooks
7594
7689
  hooks: HooksSchema.optional(),
7595
7690
  // Scripts (copied to all targets)
@@ -7616,6 +7711,37 @@ var PLUXX_COMPILER_BUCKETS = [
7616
7711
  "distribution"
7617
7712
  ];
7618
7713
  function getPluginCompilerBuckets(config) {
7714
+ const runtimeMcpSurface = {
7715
+ servers: config.mcp,
7716
+ hasRuntimeAuth: Object.values(config.mcp ?? {}).some((server) => server.auth?.type !== "none" && server.auth !== void 0)
7717
+ };
7718
+ const runtimeReadinessSurface = {
7719
+ config: config.readiness
7720
+ };
7721
+ const runtimePayloadSurface = {
7722
+ scriptsPath: config.scripts,
7723
+ assetsPath: config.assets,
7724
+ passthroughPaths: config.passthrough ?? []
7725
+ };
7726
+ const distributionBrandingSurface = {
7727
+ identity: {
7728
+ name: config.name,
7729
+ version: config.version,
7730
+ description: config.description,
7731
+ author: config.author,
7732
+ repository: config.repository,
7733
+ license: config.license,
7734
+ keywords: config.keywords
7735
+ },
7736
+ brand: config.brand
7737
+ };
7738
+ const distributionInstallSurface = {
7739
+ userConfig: config.userConfig ?? []
7740
+ };
7741
+ const distributionOutputSurface = {
7742
+ targets: config.targets,
7743
+ outDir: config.outDir
7744
+ };
7619
7745
  return {
7620
7746
  instructions: {
7621
7747
  path: config.instructions
@@ -7636,25 +7762,24 @@ function getPluginCompilerBuckets(config) {
7636
7762
  rules: config.permissions
7637
7763
  },
7638
7764
  runtime: {
7639
- mcp: config.mcp,
7640
- scriptsPath: config.scripts,
7641
- assetsPath: config.assets,
7642
- passthroughPaths: config.passthrough ?? []
7765
+ mcp: runtimeMcpSurface.servers,
7766
+ readiness: runtimeReadinessSurface.config,
7767
+ scriptsPath: runtimePayloadSurface.scriptsPath,
7768
+ assetsPath: runtimePayloadSurface.assetsPath,
7769
+ passthroughPaths: runtimePayloadSurface.passthroughPaths,
7770
+ mcpSurface: runtimeMcpSurface,
7771
+ readinessSurface: runtimeReadinessSurface,
7772
+ payloadSurface: runtimePayloadSurface
7643
7773
  },
7644
7774
  distribution: {
7645
- identity: {
7646
- name: config.name,
7647
- version: config.version,
7648
- description: config.description,
7649
- author: config.author,
7650
- repository: config.repository,
7651
- license: config.license,
7652
- keywords: config.keywords
7653
- },
7654
- brand: config.brand,
7655
- userConfig: config.userConfig ?? [],
7656
- targets: config.targets,
7657
- outDir: config.outDir
7775
+ identity: distributionBrandingSurface.identity,
7776
+ brand: distributionBrandingSurface.brand,
7777
+ userConfig: distributionInstallSurface.userConfig,
7778
+ targets: distributionOutputSurface.targets,
7779
+ outDir: distributionOutputSurface.outDir,
7780
+ brandingSurface: distributionBrandingSurface,
7781
+ installSurface: distributionInstallSurface,
7782
+ outputSurface: distributionOutputSurface
7658
7783
  }
7659
7784
  };
7660
7785
  }
@@ -7671,7 +7796,7 @@ function getConfiguredCompilerBuckets(config) {
7671
7796
  );
7672
7797
  if (hasPermissions) configured.push("permissions");
7673
7798
  const hasRuntime = Boolean(
7674
- buckets.runtime.mcp && Object.keys(buckets.runtime.mcp).length > 0 || buckets.runtime.scriptsPath || buckets.runtime.assetsPath || buckets.runtime.passthroughPaths.length > 0
7799
+ buckets.runtime.mcp && Object.keys(buckets.runtime.mcp).length > 0 || buckets.runtime.readiness && (buckets.runtime.readiness.dependencies.length > 0 || buckets.runtime.readiness.gates.length > 0) || buckets.runtime.scriptsPath || buckets.runtime.assetsPath || buckets.runtime.passthroughPaths.length > 0
7675
7800
  );
7676
7801
  if (hasRuntime) configured.push("runtime");
7677
7802
  configured.push("distribution");
@@ -7683,6 +7808,139 @@ function definePlugin(config) {
7683
7808
  return PluginConfigSchema.parse(config);
7684
7809
  }
7685
7810
 
7811
+ // src/runtime-readiness-registry.ts
7812
+ function getEnabledRuntimeReadinessBindings(capability, plan) {
7813
+ return capability.bindings.filter((binding) => {
7814
+ switch (binding.gate) {
7815
+ case "session-start":
7816
+ return plan.needsSessionStart;
7817
+ case "mcp-gate":
7818
+ return plan.needsMcpGate;
7819
+ case "prompt-gate":
7820
+ return plan.needsPromptGate;
7821
+ }
7822
+ });
7823
+ }
7824
+ var NAMED_PROMPT_TARGET_NOTE = "Named `skills` / `commands` readiness targets currently translate through prompt-entry gating with best-effort matching because the core four do not share one exact per-skill or per-command runtime interception surface.";
7825
+ var CODEX_EXTERNAL_NOTE = "Codex readiness currently translates into generated hook/config guidance rather than an enforced plugin-bundled runtime surface.";
7826
+ function getRuntimeReadinessNamedPromptTargetNote() {
7827
+ return NAMED_PROMPT_TARGET_NOTE;
7828
+ }
7829
+ function getRuntimeReadinessExternalConfigNote() {
7830
+ return CODEX_EXTERNAL_NOTE;
7831
+ }
7832
+ function getRuntimeReadinessCapability(platform, pluginRootVar = "PLUGIN_ROOT") {
7833
+ switch (platform) {
7834
+ case "claude-code":
7835
+ return {
7836
+ platform,
7837
+ delivery: "bundled-hooks",
7838
+ bundleEnforced: true,
7839
+ namedPromptTargetScope: "best-effort",
7840
+ scriptPath: "hooks/pluxx-readiness.mjs",
7841
+ companionArtifacts: [],
7842
+ bindings: [
7843
+ {
7844
+ gate: "session-start",
7845
+ event: "SessionStart",
7846
+ command: `node \${${pluginRootVar}}/hooks/pluxx-readiness.mjs session-start`
7847
+ },
7848
+ {
7849
+ gate: "mcp-gate",
7850
+ event: "PreToolUse",
7851
+ matcher: "MCP",
7852
+ command: `node \${${pluginRootVar}}/hooks/pluxx-readiness.mjs mcp-gate`
7853
+ },
7854
+ {
7855
+ gate: "prompt-gate",
7856
+ event: "UserPromptSubmit",
7857
+ command: `node \${${pluginRootVar}}/hooks/pluxx-readiness.mjs prompt-gate`
7858
+ }
7859
+ ]
7860
+ };
7861
+ case "cursor":
7862
+ return {
7863
+ platform,
7864
+ delivery: "bundled-hooks",
7865
+ bundleEnforced: true,
7866
+ namedPromptTargetScope: "best-effort",
7867
+ scriptPath: "hooks/pluxx-readiness.mjs",
7868
+ companionArtifacts: [],
7869
+ bindings: [
7870
+ {
7871
+ gate: "session-start",
7872
+ event: "sessionStart",
7873
+ command: "node ./hooks/pluxx-readiness.mjs session-start"
7874
+ },
7875
+ {
7876
+ gate: "mcp-gate",
7877
+ event: "beforeMCPExecution",
7878
+ command: "node ./hooks/pluxx-readiness.mjs mcp-gate"
7879
+ },
7880
+ {
7881
+ gate: "prompt-gate",
7882
+ event: "beforeSubmitPrompt",
7883
+ command: "node ./hooks/pluxx-readiness.mjs prompt-gate"
7884
+ }
7885
+ ]
7886
+ };
7887
+ case "codex":
7888
+ return {
7889
+ platform,
7890
+ delivery: "generated-guidance",
7891
+ bundleEnforced: false,
7892
+ namedPromptTargetScope: "best-effort",
7893
+ scriptPath: ".codex/pluxx-readiness.mjs",
7894
+ companionArtifacts: [".codex/readiness.generated.json", ".codex/hooks.generated.json"],
7895
+ bindings: [
7896
+ {
7897
+ gate: "session-start",
7898
+ event: "SessionStart",
7899
+ command: "node ./.codex/pluxx-readiness.mjs session-start"
7900
+ },
7901
+ {
7902
+ gate: "mcp-gate",
7903
+ event: "PreToolUse",
7904
+ matcher: "MCP",
7905
+ command: "node ./.codex/pluxx-readiness.mjs mcp-gate"
7906
+ },
7907
+ {
7908
+ gate: "prompt-gate",
7909
+ event: "UserPromptSubmit",
7910
+ command: "node ./.codex/pluxx-readiness.mjs prompt-gate"
7911
+ }
7912
+ ],
7913
+ notes: CODEX_EXTERNAL_NOTE
7914
+ };
7915
+ case "opencode":
7916
+ return {
7917
+ platform,
7918
+ delivery: "runtime-callbacks",
7919
+ bundleEnforced: true,
7920
+ namedPromptTargetScope: "best-effort",
7921
+ scriptPath: "runtime/pluxx-readiness.mjs",
7922
+ companionArtifacts: [],
7923
+ bindings: [
7924
+ {
7925
+ gate: "session-start",
7926
+ event: "session.created",
7927
+ command: "node ./runtime/pluxx-readiness.mjs session-start"
7928
+ },
7929
+ {
7930
+ gate: "mcp-gate",
7931
+ event: "tool.execute.before",
7932
+ command: "node ./runtime/pluxx-readiness.mjs mcp-gate"
7933
+ },
7934
+ {
7935
+ gate: "prompt-gate",
7936
+ event: "chat.message",
7937
+ command: "node ./runtime/pluxx-readiness.mjs prompt-gate"
7938
+ }
7939
+ ]
7940
+ };
7941
+ }
7942
+ }
7943
+
7686
7944
  // src/validation/platform-rules.ts
7687
7945
  var STANDARD_SKILL_FRONTMATTER = [
7688
7946
  "name",
@@ -8456,7 +8714,7 @@ var CORE_FOUR_PRIMITIVE_CAPABILITIES = {
8456
8714
  runtime: {
8457
8715
  mode: "preserve",
8458
8716
  nativeSurfaces: [".mcp.json", ".app.json", ".codex/config.toml", "scripts/", "assets/"],
8459
- notes: "Bundle-local MCP config exists, but active MCP state also lives in config.toml."
8717
+ notes: `Bundle-local MCP config exists, but active MCP state also lives in config.toml. ${getRuntimeReadinessExternalConfigNote()}`
8460
8718
  },
8461
8719
  distribution: {
8462
8720
  mode: "preserve",
@@ -8630,6 +8888,57 @@ function renderCompatibilityMatrixMarkdown() {
8630
8888
  `;
8631
8889
  }
8632
8890
 
8891
+ // src/runtime-script-contract.ts
8892
+ var INSTALLER_OWNED_CHECK_ENV_PATH = "scripts/check-env.sh";
8893
+ var RUNTIME_SCRIPT_ROLE_PATHS = {
8894
+ "install-validation": INSTALLER_OWNED_CHECK_ENV_PATH,
8895
+ "runtime-env": "scripts/load-env.sh",
8896
+ "runtime-bootstrap": "scripts/bootstrap-runtime.sh",
8897
+ "runtime-entrypoint": "scripts/start-mcp.sh"
8898
+ };
8899
+ var PORTABLE_RUNTIME_SCRIPT_ROLES = [
8900
+ RUNTIME_SCRIPT_ROLE_PATHS["runtime-env"],
8901
+ RUNTIME_SCRIPT_ROLE_PATHS["runtime-bootstrap"],
8902
+ RUNTIME_SCRIPT_ROLE_PATHS["runtime-entrypoint"]
8903
+ ];
8904
+ function getPortableRuntimeScriptRoleGuidance() {
8905
+ return `Use separate runtime scripts such as ${PORTABLE_RUNTIME_SCRIPT_ROLES.join(", ")} instead.`;
8906
+ }
8907
+ function getRuntimeScriptRoleForPath(path) {
8908
+ const normalized = path.replace(/\\/g, "/").replace(/^\.\//, "");
8909
+ for (const [role, rolePath] of Object.entries(RUNTIME_SCRIPT_ROLE_PATHS)) {
8910
+ if (normalized === rolePath) return role;
8911
+ }
8912
+ return null;
8913
+ }
8914
+ function getRuntimeScriptPathsForRoles(roles) {
8915
+ return roles.map((role) => RUNTIME_SCRIPT_ROLE_PATHS[role]);
8916
+ }
8917
+ function formatRuntimeScriptRoles(roles) {
8918
+ return getRuntimeScriptPathsForRoles(roles).join(", ");
8919
+ }
8920
+ function referencesInstallerOwnedCheckEnv(command) {
8921
+ return command.includes("check-env.sh");
8922
+ }
8923
+ function getInstallerOwnedCheckEnvRuntimeMessage(serverName) {
8924
+ return `MCP server "${serverName}" references ${INSTALLER_OWNED_CHECK_ENV_PATH} in its runtime command or args. Pluxx install rewrites that file into a no-op after userConfig materialization, so runtime startup must not depend on it. ${getPortableRuntimeScriptRoleGuidance()}`;
8925
+ }
8926
+ function getInstallerOwnedCheckEnvHookMessage(eventName) {
8927
+ return `Hook "${eventName}" references ${INSTALLER_OWNED_CHECK_ENV_PATH} as part of a broader runtime command. Treat that script as installer-owned and install-time only, because local installs may rewrite it into a no-op after required config is materialized.`;
8928
+ }
8929
+ function getConsumerEnvScriptMissingDetail() {
8930
+ return `This bundle does not ship a ${INSTALLER_OWNED_CHECK_ENV_PATH} file.`;
8931
+ }
8932
+ function getConsumerEnvScriptActiveDetail() {
8933
+ return `This bundle still runs ${INSTALLER_OWNED_CHECK_ENV_PATH}, which usually means required config was not materialized into the installed plugin.`;
8934
+ }
8935
+ function getConsumerRuntimeScriptRolesDetail(roles) {
8936
+ if (roles.length === 0) {
8937
+ return "This bundle does not include any of the known portable runtime script-role files.";
8938
+ }
8939
+ return `This bundle includes the following known runtime script-role files: ${formatRuntimeScriptRoles(roles)}.`;
8940
+ }
8941
+
8633
8942
  // src/compiler-intent.ts
8634
8943
  import { existsSync, readFileSync } from "fs";
8635
8944
  import { resolve } from "path";
@@ -9856,6 +10165,7 @@ import { existsSync as existsSync5, lstatSync as lstatSync2, readdirSync as read
9856
10165
  import { resolve as resolve5 } from "path";
9857
10166
 
9858
10167
  // src/cli/doctor.ts
10168
+ import { spawn } from "child_process";
9859
10169
  import { accessSync, constants, existsSync as existsSync4, lstatSync, readFileSync as readFileSync4, readdirSync as readdirSync2 } from "fs";
9860
10170
  import { basename, dirname as dirname2, resolve as resolve4 } from "path";
9861
10171
 
@@ -9872,6 +10182,29 @@ var CONFIG_FILES = [
9872
10182
  // src/cli/install.ts
9873
10183
  import { resolve as resolve3, dirname } from "path";
9874
10184
  import { existsSync as existsSync3, symlinkSync, mkdirSync as mkdirSync2, rmSync as rmSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2, cpSync, readdirSync } from "fs";
10185
+
10186
+ // src/mcp-stdio-paths.ts
10187
+ function findHostPluginRootVars(value) {
10188
+ const matches = value.match(/\$\{(?:CLAUDE_PLUGIN_ROOT|CURSOR_PLUGIN_ROOT|PLUGIN_ROOT)\}/g) ?? [];
10189
+ return [...new Set(matches.map((match) => match.slice(2, -1)))];
10190
+ }
10191
+ function findLeakedPluginRootVars(platform, values) {
10192
+ const leaks = /* @__PURE__ */ new Set();
10193
+ for (const value of values) {
10194
+ for (const pluginRootVar of findHostPluginRootVars(value)) {
10195
+ if (platform === "claude-code") {
10196
+ if (pluginRootVar !== "CLAUDE_PLUGIN_ROOT") {
10197
+ leaks.add(pluginRootVar);
10198
+ }
10199
+ continue;
10200
+ }
10201
+ leaks.add(pluginRootVar);
10202
+ }
10203
+ }
10204
+ return [...leaks];
10205
+ }
10206
+
10207
+ // src/cli/install.ts
9875
10208
  function getInstallTargets(pluginName) {
9876
10209
  const home = process.env.HOME ?? "~";
9877
10210
  return [
@@ -9935,9 +10268,39 @@ function getInstallTargets(pluginName) {
9935
10268
  function getClaudeMarketplaceName(pluginName) {
9936
10269
  return `pluxx-local-${pluginName}`;
9937
10270
  }
10271
+ function readBundleManifestVersion(rootDir, platform) {
10272
+ const manifestPath = manifestPathForPlatform(platform);
10273
+ if (!manifestPath) return void 0;
10274
+ const filepath = resolve3(rootDir, manifestPath);
10275
+ if (!existsSync3(filepath)) return void 0;
10276
+ try {
10277
+ const manifest = JSON.parse(readFileSync3(filepath, "utf-8"));
10278
+ return typeof manifest.version === "string" ? manifest.version : void 0;
10279
+ } catch {
10280
+ return void 0;
10281
+ }
10282
+ }
10283
+ function resolveClaudeInstalledCachePath(pluginName, version) {
10284
+ if (!version) return void 0;
10285
+ const home = process.env.HOME ?? "~";
10286
+ return resolve3(home, ".claude/plugins/cache", getClaudeMarketplaceName(pluginName), pluginName, version);
10287
+ }
10288
+ function resolveExpectedInstalledConsumerPath(target, pluginName) {
10289
+ if (target.platform === "claude-code" && pluginName !== "") {
10290
+ const cachePath = resolveClaudeInstalledCachePath(
10291
+ pluginName,
10292
+ readBundleManifestVersion(target.sourceDir, "claude-code")
10293
+ );
10294
+ if (cachePath) return cachePath;
10295
+ }
10296
+ return target.pluginDir;
10297
+ }
9938
10298
  function resolveInstalledConsumerPath(target, pluginName) {
9939
10299
  if (target.platform === "claude-code" && pluginName !== "") {
9940
- return target.pluginDir;
10300
+ const expectedPath = resolveExpectedInstalledConsumerPath(target, pluginName);
10301
+ if (existsSync3(expectedPath)) return expectedPath;
10302
+ if (existsSync3(target.pluginDir)) return target.pluginDir;
10303
+ return expectedPath;
9941
10304
  }
9942
10305
  return target.pluginDir;
9943
10306
  }
@@ -10013,7 +10376,8 @@ function findInstalledBundleIntegrityIssues(rootDir, platform) {
10013
10376
  if (!manifestPath) {
10014
10377
  return {
10015
10378
  missingManifestPaths: [],
10016
- missingHookTargets: []
10379
+ missingHookTargets: [],
10380
+ invalidRuntimeScripts: []
10017
10381
  };
10018
10382
  }
10019
10383
  const manifestFile = resolve3(rootDir, manifestPath);
@@ -10021,7 +10385,8 @@ function findInstalledBundleIntegrityIssues(rootDir, platform) {
10021
10385
  return {
10022
10386
  manifestIssue: `missing plugin manifest at ${manifestPath}`,
10023
10387
  missingManifestPaths: [],
10024
- missingHookTargets: []
10388
+ missingHookTargets: [],
10389
+ invalidRuntimeScripts: []
10025
10390
  };
10026
10391
  }
10027
10392
  let manifest;
@@ -10031,7 +10396,8 @@ function findInstalledBundleIntegrityIssues(rootDir, platform) {
10031
10396
  return {
10032
10397
  manifestIssue: `plugin manifest at ${manifestPath} is not parseable: ${error instanceof Error ? error.message : String(error)}`,
10033
10398
  missingManifestPaths: [],
10034
- missingHookTargets: []
10399
+ missingHookTargets: [],
10400
+ invalidRuntimeScripts: []
10035
10401
  };
10036
10402
  }
10037
10403
  const missingManifestPaths = readBundleManifestReferences(manifest).filter((value) => {
@@ -10042,14 +10408,16 @@ function findInstalledBundleIntegrityIssues(rootDir, platform) {
10042
10408
  if (!hooksReference) {
10043
10409
  return {
10044
10410
  missingManifestPaths,
10045
- missingHookTargets: []
10411
+ missingHookTargets: [],
10412
+ invalidRuntimeScripts: findInstalledRuntimeScriptIssues(rootDir, manifest)
10046
10413
  };
10047
10414
  }
10048
10415
  const hooksPath = resolveBundleReference(rootDir, hooksReference);
10049
10416
  if (!hooksPath || !existsSync3(hooksPath)) {
10050
10417
  return {
10051
10418
  missingManifestPaths,
10052
- missingHookTargets: []
10419
+ missingHookTargets: [],
10420
+ invalidRuntimeScripts: findInstalledRuntimeScriptIssues(rootDir, manifest)
10053
10421
  };
10054
10422
  }
10055
10423
  try {
@@ -10064,15 +10432,47 @@ function findInstalledBundleIntegrityIssues(rootDir, platform) {
10064
10432
  )].sort();
10065
10433
  return {
10066
10434
  missingManifestPaths,
10067
- missingHookTargets
10435
+ missingHookTargets,
10436
+ invalidRuntimeScripts: findInstalledRuntimeScriptIssues(rootDir, manifest)
10068
10437
  };
10069
10438
  } catch {
10070
10439
  return {
10071
10440
  missingManifestPaths,
10072
- missingHookTargets: []
10441
+ missingHookTargets: [],
10442
+ invalidRuntimeScripts: findInstalledRuntimeScriptIssues(rootDir, manifest)
10073
10443
  };
10074
10444
  }
10075
10445
  }
10446
+ function findInstalledRuntimeScriptIssues(rootDir, manifest) {
10447
+ const mcpReference = typeof manifest.mcpServers === "string" ? manifest.mcpServers : void 0;
10448
+ if (!mcpReference) return [];
10449
+ const mcpPath = resolveBundleReference(rootDir, mcpReference);
10450
+ if (!mcpPath || !existsSync3(mcpPath)) return [];
10451
+ try {
10452
+ const parsed = JSON.parse(readFileSync3(mcpPath, "utf-8"));
10453
+ const issues = /* @__PURE__ */ new Set();
10454
+ for (const [serverName, server] of Object.entries(parsed.mcpServers ?? {})) {
10455
+ if (!server || typeof server !== "object") continue;
10456
+ const serverRecord = server;
10457
+ const args = Array.isArray(serverRecord.args) ? serverRecord.args.filter((value) => typeof value === "string") : [];
10458
+ const commandTargets = [
10459
+ typeof serverRecord.command === "string" ? serverRecord.command : "",
10460
+ ...args
10461
+ ].flatMap(extractBundleCommandTargets);
10462
+ for (const target of commandTargets) {
10463
+ const resolved = resolveBundleReference(rootDir, target);
10464
+ if (!resolved || !existsSync3(resolved) || !resolved.endsWith(".sh")) continue;
10465
+ const content = readFileSync3(resolved, "utf-8");
10466
+ if (!content.includes("check-env.sh")) continue;
10467
+ const relativePath = resolved.startsWith(`${rootDir}/`) ? resolved.slice(rootDir.length + 1) : resolved;
10468
+ issues.add(`runtime script ${relativePath} for MCP server "${serverName}" still references installer-owned scripts/check-env.sh`);
10469
+ }
10470
+ }
10471
+ return [...issues].sort();
10472
+ } catch {
10473
+ return [];
10474
+ }
10475
+ }
10076
10476
  function planInstallPlugin(distDir, pluginName, platforms) {
10077
10477
  const targets = getInstallTargets(pluginName);
10078
10478
  const filtered = platforms ? targets.filter((t) => platforms.includes(t.platform)) : targets;
@@ -10224,6 +10624,10 @@ var MUTATING_PREFIX_PATTERN = new RegExp(`^(${MUTATING_PREFIXES.join("|")})\\b`,
10224
10624
  // src/cli/doctor.ts
10225
10625
  var MATERIALIZED_ENV_MARKER = "materialized required config";
10226
10626
  var MIN_NODE_MAJOR = 18;
10627
+ var STDIO_LAUNCH_SMOKE_TIMEOUT_MS = 1200;
10628
+ function renderInstalledPluginRoot(value, rootDir) {
10629
+ return value.replaceAll("${PLUGIN_ROOT}", rootDir).replaceAll("${CLAUDE_PLUGIN_ROOT}", rootDir).replaceAll("${CURSOR_PLUGIN_ROOT}", rootDir).replaceAll("${CODEX_PLUGIN_ROOT}", rootDir).replaceAll("${OPENCODE_PLUGIN_ROOT}", rootDir);
10630
+ }
10227
10631
  function addCheck(checks, check) {
10228
10632
  checks.push(check);
10229
10633
  }
@@ -10422,14 +10826,14 @@ function checkInstalledUserConfig(checks, rootDir) {
10422
10826
  }
10423
10827
  }
10424
10828
  function checkInstalledEnvValidation(checks, rootDir) {
10425
- const envScriptPath = "scripts/check-env.sh";
10829
+ const envScriptPath = INSTALLER_OWNED_CHECK_ENV_PATH;
10426
10830
  const resolvedPath = resolve4(rootDir, envScriptPath);
10427
10831
  if (!existsSync4(resolvedPath)) {
10428
10832
  addCheck(checks, {
10429
10833
  level: "info",
10430
10834
  code: "consumer-env-script-missing",
10431
10835
  title: "No install-time env validation script found",
10432
- detail: "This bundle does not ship a scripts/check-env.sh file.",
10836
+ detail: getConsumerEnvScriptMissingDetail(),
10433
10837
  fix: "No action needed unless this plugin is expected to validate runtime secrets on install.",
10434
10838
  path: envScriptPath
10435
10839
  });
@@ -10451,12 +10855,30 @@ function checkInstalledEnvValidation(checks, rootDir) {
10451
10855
  level: "warning",
10452
10856
  code: "consumer-env-script-active",
10453
10857
  title: "Install-time env validation is still active",
10454
- detail: "This bundle still runs scripts/check-env.sh, which usually means required config was not materialized into the installed plugin.",
10858
+ detail: getConsumerEnvScriptActiveDetail(),
10455
10859
  fix: "If authenticated tools fail, reinstall the plugin and provide the requested userConfig values or required env vars.",
10456
10860
  path: envScriptPath
10457
10861
  });
10458
10862
  }
10459
- function checkInstalledMcpConfig(checks, rootDir, layout) {
10863
+ function checkInstalledRuntimeScriptRoles(checks, rootDir) {
10864
+ const roleFiles = [
10865
+ "scripts/check-env.sh",
10866
+ "scripts/load-env.sh",
10867
+ "scripts/bootstrap-runtime.sh",
10868
+ "scripts/start-mcp.sh"
10869
+ ];
10870
+ const presentRoles = roleFiles.filter((relativePath) => existsSync4(resolve4(rootDir, relativePath))).map((relativePath) => getRuntimeScriptRoleForPath(relativePath)).filter((role) => role !== null);
10871
+ if (presentRoles.length === 0) return;
10872
+ addCheck(checks, {
10873
+ level: "info",
10874
+ code: "consumer-runtime-script-roles",
10875
+ title: "Known runtime script-role files detected",
10876
+ detail: getConsumerRuntimeScriptRolesDetail(presentRoles),
10877
+ fix: "No action needed unless the runtime startup chain is unexpectedly missing a script you rely on.",
10878
+ path: "scripts/"
10879
+ });
10880
+ }
10881
+ async function checkInstalledMcpConfig(checks, rootDir, layout) {
10460
10882
  if (!layout.mcpConfigPath) {
10461
10883
  addCheck(checks, {
10462
10884
  level: "info",
@@ -10504,8 +10926,9 @@ function checkInstalledMcpConfig(checks, rootDir, layout) {
10504
10926
  path: layout.mcpConfigPath
10505
10927
  });
10506
10928
  }
10507
- const remoteEntries = servers.filter((server) => "url" in server);
10508
- const stdioEntries = servers.filter((server) => "command" in server);
10929
+ const namedServers = Object.entries(payload.mcpServers ?? {}).map(([name, server]) => ({ name, ...server }));
10930
+ const remoteEntries = namedServers.filter((server) => "url" in server);
10931
+ const stdioEntries = namedServers.filter((server) => "command" in server);
10509
10932
  const inlineHeaderEntries = servers.filter((server) => {
10510
10933
  if ("headers" in server && server.headers && typeof server.headers === "object") return true;
10511
10934
  if ("http_headers" in server && server.http_headers && typeof server.http_headers === "object") return true;
@@ -10531,6 +10954,38 @@ function checkInstalledMcpConfig(checks, rootDir, layout) {
10531
10954
  path: layout.mcpConfigPath
10532
10955
  });
10533
10956
  }
10957
+ const leakedPluginRootVars = [...new Set(
10958
+ stdioEntries.flatMap((server) => {
10959
+ const serverRecord = server;
10960
+ const serverArgs = Array.isArray(serverRecord.args) ? serverRecord.args.filter((value) => typeof value === "string") : [];
10961
+ const values = [
10962
+ typeof server.command === "string" ? server.command : "",
10963
+ ...serverArgs
10964
+ ];
10965
+ return findLeakedPluginRootVars(layout.platform, values);
10966
+ })
10967
+ )].sort();
10968
+ if (leakedPluginRootVars.length > 0) {
10969
+ addCheck(checks, {
10970
+ level: "warning",
10971
+ code: "consumer-mcp-stdio-host-root-leak",
10972
+ title: "Installed stdio MCP config contains the wrong host root contract",
10973
+ detail: `This installed ${layout.platform} MCP config still contains plugin root variable${leakedPluginRootVars.length === 1 ? "" : "s"} that do not belong in this host bundle: ${leakedPluginRootVars.map((pluginRootVar) => `\${${pluginRootVar}}`).join(", ")}.`,
10974
+ fix: "Author global stdio MCP paths as `./...` or `${PLUGIN_ROOT}/...`, then rebuild and reinstall so Pluxx can normalize the correct host-specific path.",
10975
+ path: layout.mcpConfigPath
10976
+ });
10977
+ }
10978
+ const launchResults = await smokeCheckInstalledStdioServers(rootDir, stdioEntries);
10979
+ for (const result of launchResults) {
10980
+ addCheck(checks, {
10981
+ level: result.ok ? "success" : "error",
10982
+ code: result.ok ? "consumer-mcp-stdio-launch-valid" : "consumer-mcp-stdio-launch-failed",
10983
+ title: result.ok ? `Installed stdio MCP server launches for ${result.serverName}` : `Installed stdio MCP server failed to stay up for ${result.serverName}`,
10984
+ detail: `${result.command || "(empty command)"} \u2014 ${result.detail}`,
10985
+ fix: result.ok ? "No action needed." : "Launch the installed command directly from the plugin directory, fix any missing runtime files/dependencies, then reinstall and rerun pluxx verify-install.",
10986
+ path: layout.mcpConfigPath
10987
+ });
10988
+ }
10534
10989
  }
10535
10990
  if (remoteEntries.length > 0 && inlineHeaderEntries.length > 0) {
10536
10991
  addCheck(checks, {
@@ -10577,6 +11032,9 @@ function checkInstalledBundleIntegrity(checks, rootDir, layout) {
10577
11032
  if (issues.missingHookTargets.length > 0) {
10578
11033
  details.push(`hook commands reference missing bundle target${issues.missingHookTargets.length === 1 ? "" : "s"}: ${issues.missingHookTargets.join(", ")}`);
10579
11034
  }
11035
+ if (issues.invalidRuntimeScripts.length > 0) {
11036
+ details.push(`runtime startup still depends on installer-owned validation: ${issues.invalidRuntimeScripts.join(", ")}`);
11037
+ }
10580
11038
  if (details.length === 0) {
10581
11039
  addCheck(checks, {
10582
11040
  level: "success",
@@ -10612,6 +11070,87 @@ function findMissingInstalledStdioRuntimePaths(rootDir, stdioEntries) {
10612
11070
  }
10613
11071
  return [...missing].sort();
10614
11072
  }
11073
+ async function smokeCheckInstalledStdioServers(rootDir, stdioEntries) {
11074
+ const results = [];
11075
+ for (const server of stdioEntries) {
11076
+ const serverName = typeof server.name === "string" ? server.name : "unknown";
11077
+ const command = typeof server.command === "string" ? renderInstalledPluginRoot(server.command, rootDir) : "";
11078
+ const args = Array.isArray(server.args) ? server.args.filter((value) => typeof value === "string").map((value) => renderInstalledPluginRoot(value, rootDir)) : [];
11079
+ const envRecord = server.env && typeof server.env === "object" ? Object.fromEntries(
11080
+ Object.entries(server.env).filter((entry) => typeof entry[1] === "string").map(([key, value]) => [key, renderInstalledPluginRoot(value, rootDir)])
11081
+ ) : {};
11082
+ if (command.trim().length === 0) {
11083
+ results.push({
11084
+ serverName,
11085
+ command: "",
11086
+ ok: false,
11087
+ detail: "stdio command is empty"
11088
+ });
11089
+ continue;
11090
+ }
11091
+ const renderedCommand = [command, ...args].join(" ");
11092
+ const stdoutChunks = [];
11093
+ const stderrChunks = [];
11094
+ const child = spawn(command, args, {
11095
+ cwd: rootDir,
11096
+ env: {
11097
+ ...process.env,
11098
+ ...envRecord
11099
+ },
11100
+ stdio: ["pipe", "pipe", "pipe"]
11101
+ });
11102
+ child.stdout?.on("data", (chunk) => {
11103
+ if (stdoutChunks.reduce((sum, entry) => sum + entry.length, 0) < 4096) {
11104
+ stdoutChunks.push(Buffer.from(chunk));
11105
+ }
11106
+ });
11107
+ child.stderr?.on("data", (chunk) => {
11108
+ if (stderrChunks.reduce((sum, entry) => sum + entry.length, 0) < 4096) {
11109
+ stderrChunks.push(Buffer.from(chunk));
11110
+ }
11111
+ });
11112
+ const exitResult = await new Promise((resolveResult) => {
11113
+ let settled = false;
11114
+ const settle = (result) => {
11115
+ if (settled) return;
11116
+ settled = true;
11117
+ clearTimeout(timeout);
11118
+ resolveResult(result);
11119
+ };
11120
+ const timeout = setTimeout(() => {
11121
+ child.stdin?.end();
11122
+ child.kill("SIGTERM");
11123
+ settle({
11124
+ serverName,
11125
+ command: renderedCommand,
11126
+ ok: true,
11127
+ detail: `process stayed alive for ${STDIO_LAUNCH_SMOKE_TIMEOUT_MS}ms before teardown`
11128
+ });
11129
+ }, STDIO_LAUNCH_SMOKE_TIMEOUT_MS);
11130
+ child.once("error", (error) => {
11131
+ settle({
11132
+ serverName,
11133
+ command: renderedCommand,
11134
+ ok: false,
11135
+ detail: `failed to spawn: ${error.message}`
11136
+ });
11137
+ });
11138
+ child.once("exit", (code, signal) => {
11139
+ const stdout = Buffer.concat(stdoutChunks).toString("utf-8").trim();
11140
+ const stderr = Buffer.concat(stderrChunks).toString("utf-8").trim();
11141
+ const output = [stderr, stdout].filter(Boolean).join(" | ");
11142
+ settle({
11143
+ serverName,
11144
+ command: renderedCommand,
11145
+ ok: false,
11146
+ detail: `process exited before ready window (code=${code ?? "null"}, signal=${signal ?? "null"})${output ? `: ${output}` : ""}`
11147
+ });
11148
+ });
11149
+ });
11150
+ results.push(exitResult);
11151
+ }
11152
+ return results;
11153
+ }
10615
11154
  function isLikelyLocalRuntimePath(value) {
10616
11155
  return value.startsWith("./") || value.startsWith("../") || value.startsWith(".\\") || value.startsWith("..\\");
10617
11156
  }
@@ -10785,7 +11324,8 @@ async function doctorConsumer(rootDir = process.cwd()) {
10785
11324
  checkInstalledBundleIntegrity(checks, rootDir, layout);
10786
11325
  checkInstalledUserConfig(checks, rootDir);
10787
11326
  checkInstalledEnvValidation(checks, rootDir);
10788
- checkInstalledMcpConfig(checks, rootDir, layout);
11327
+ checkInstalledRuntimeScriptRoles(checks, rootDir);
11328
+ await checkInstalledMcpConfig(checks, rootDir, layout);
10789
11329
  if (layout.platform === "opencode") {
10790
11330
  checkInstalledOpenCodeHostBridge(checks, rootDir);
10791
11331
  checkInstalledOpenCodeSkills(checks, rootDir);
@@ -10800,7 +11340,7 @@ function buildCheckFromReport(target, pluginName, report) {
10800
11340
  const stale = staleReason !== void 0;
10801
11341
  return {
10802
11342
  platform: target.platform,
10803
- installPath: target.pluginDir,
11343
+ installPath: consumerPath,
10804
11344
  consumerPath,
10805
11345
  built: target.built,
10806
11346
  installed: existsSync5(consumerPath),
@@ -10952,11 +11492,13 @@ function getVerifyInstallRecoveryActions(check) {
10952
11492
  }
10953
11493
  export {
10954
11494
  CORE_FOUR_PRIMITIVE_CAPABILITIES,
11495
+ INSTALLER_OWNED_CHECK_ENV_PATH,
10955
11496
  PLATFORM_LIMITS,
10956
11497
  PLATFORM_LIMIT_POLICIES,
10957
11498
  PLATFORM_VALIDATION_RULES,
10958
11499
  PLUXX_COMPILER_BUCKETS,
10959
11500
  PLUXX_COMPILER_INTENT_PATH,
11501
+ PORTABLE_RUNTIME_SCRIPT_ROLES,
10960
11502
  PluginConfigSchema,
10961
11503
  buildGeneratedPermissionHookScript,
10962
11504
  buildOpenCodePermissionMap,
@@ -10964,15 +11506,25 @@ export {
10964
11506
  definePlugin,
10965
11507
  formatPublishPlan,
10966
11508
  getConfiguredCompilerBuckets,
11509
+ getConsumerEnvScriptActiveDetail,
11510
+ getConsumerEnvScriptMissingDetail,
10967
11511
  getCoreFourPrimitiveCapabilities,
11512
+ getEnabledRuntimeReadinessBindings,
11513
+ getInstallerOwnedCheckEnvHookMessage,
11514
+ getInstallerOwnedCheckEnvRuntimeMessage,
10968
11515
  getPlatformCompatibilityMatrix,
10969
11516
  getPlatformRules,
10970
11517
  getPluginCompilerBuckets,
11518
+ getPortableRuntimeScriptRoleGuidance,
11519
+ getRuntimeReadinessCapability,
11520
+ getRuntimeReadinessExternalConfigNote,
11521
+ getRuntimeReadinessNamedPromptTargetNote,
10971
11522
  parsePermissionRule,
10972
11523
  permissionRulesNeedToolLevelDowngrade,
10973
11524
  planPublish,
10974
11525
  printVerifyInstallResult,
10975
11526
  readCompilerIntent,
11527
+ referencesInstallerOwnedCheckEnv,
10976
11528
  renderCompatibilityMatrixMarkdown,
10977
11529
  runPublish,
10978
11530
  verifyInstall