@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.
- package/dist/branding-completeness.d.ts +10 -0
- package/dist/branding-completeness.d.ts.map +1 -0
- package/dist/cli/agent.d.ts.map +1 -1
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/index.js +2141 -952
- package/dist/cli/install.d.ts +1 -0
- package/dist/cli/install.d.ts.map +1 -1
- package/dist/cli/lint.d.ts.map +1 -1
- package/dist/cli/migrate.d.ts.map +1 -1
- package/dist/commands.d.ts +1 -0
- package/dist/commands.d.ts.map +1 -1
- package/dist/compiler-intent.d.ts +28 -28
- package/dist/generators/base.d.ts.map +1 -1
- package/dist/generators/claude-code/index.d.ts.map +1 -1
- package/dist/generators/codex/index.d.ts +1 -0
- package/dist/generators/codex/index.d.ts.map +1 -1
- package/dist/generators/cursor/index.d.ts.map +1 -1
- package/dist/generators/hooks-warning.d.ts.map +1 -1
- package/dist/generators/opencode/index.d.ts +1 -0
- package/dist/generators/opencode/index.d.ts.map +1 -1
- package/dist/generators/shared/claude-family.d.ts.map +1 -1
- package/dist/hook-translation-registry.d.ts +15 -0
- package/dist/hook-translation-registry.d.ts.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +587 -35
- package/dist/mcp-stdio-paths.d.ts +9 -0
- package/dist/mcp-stdio-paths.d.ts.map +1 -0
- package/dist/readiness.d.ts +13 -0
- package/dist/readiness.d.ts.map +1 -0
- package/dist/runtime-readiness-registry.d.ts +26 -0
- package/dist/runtime-readiness-registry.d.ts.map +1 -0
- package/dist/runtime-script-contract.d.ts +20 -0
- package/dist/runtime-script-contract.d.ts.map +1 -0
- package/dist/schema.d.ts +1444 -706
- package/dist/schema.d.ts.map +1 -1
- package/dist/skills.d.ts +27 -0
- package/dist/skills.d.ts.map +1 -0
- package/dist/validation/platform-rules.d.ts.map +1 -1
- 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:
|
|
7640
|
-
|
|
7641
|
-
|
|
7642
|
-
|
|
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
|
-
|
|
7647
|
-
|
|
7648
|
-
|
|
7649
|
-
|
|
7650
|
-
|
|
7651
|
-
|
|
7652
|
-
|
|
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:
|
|
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
|
-
|
|
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 =
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
10508
|
-
const
|
|
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
|
-
|
|
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:
|
|
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
|