@orchid-labs/pluxx 0.1.6 → 0.1.7

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/cli/index.js CHANGED
@@ -85,7 +85,7 @@ var require_src = __commonJS({
85
85
  });
86
86
 
87
87
  // src/cli/index.ts
88
- import { readFileSync as readFileSync16 } from "fs";
88
+ import { readFileSync as readFileSync17 } from "fs";
89
89
 
90
90
  // src/config/load.ts
91
91
  import { resolve, extname, dirname } from "path";
@@ -7084,14 +7084,14 @@ async function build(config, rootDir, options = {}) {
7084
7084
  }
7085
7085
 
7086
7086
  // src/cli/agent.ts
7087
- import { existsSync as existsSync21, readdirSync as readdirSync7, readFileSync as readFileSync9, statSync as statSync4 } from "fs";
7087
+ import { existsSync as existsSync22, readdirSync as readdirSync7, readFileSync as readFileSync9, statSync as statSync4 } from "fs";
7088
7088
  import { chmod, copyFile, mkdir as mkdir3, mkdtemp, readFile as readFile3, rm as rm2 } from "fs/promises";
7089
7089
  import { homedir, tmpdir as tmpdir2 } from "os";
7090
- import { relative as relative8, resolve as resolve14 } from "path";
7090
+ import { relative as relative9, resolve as resolve14 } from "path";
7091
7091
  import { spawn as spawn2 } from "child_process";
7092
7092
 
7093
7093
  // src/cli/lint.ts
7094
- import { existsSync as existsSync17, readdirSync as readdirSync5, readFileSync as readFileSync6 } from "fs";
7094
+ import { existsSync as existsSync17, lstatSync, readdirSync as readdirSync5, readFileSync as readFileSync6 } from "fs";
7095
7095
  import { resolve as resolve9, relative as relative6, basename as basename4, dirname as dirname3 } from "path";
7096
7096
 
7097
7097
  // src/validation/platform-rules.ts
@@ -8452,10 +8452,11 @@ function lintMcpUrls(config, issues) {
8452
8452
  }
8453
8453
  }
8454
8454
  }
8455
- function lintMcpRuntimeState(config, issues) {
8455
+ function lintMcpRuntimeState(rootDir, config, issues) {
8456
8456
  if (!config.mcp) return;
8457
8457
  const claudeUsesPlatformAuth = config.targets.includes("claude-code") && config.platforms?.["claude-code"]?.mcpAuth === "platform";
8458
8458
  const cursorUsesPlatformAuth = config.targets.includes("cursor") && config.platforms?.cursor?.mcpAuth === "platform";
8459
+ const passthroughDirs = (config.passthrough ?? []).map((entry) => resolveBundledPassthroughDir(rootDir, entry)).filter((entry) => Boolean(entry));
8459
8460
  for (const [serverName, server] of Object.entries(config.mcp)) {
8460
8461
  if (server.transport === "stdio") {
8461
8462
  pushIssue(issues, {
@@ -8465,6 +8466,17 @@ function lintMcpRuntimeState(config, issues) {
8465
8466
  file: "pluxx.config.ts",
8466
8467
  platform: "MCP"
8467
8468
  });
8469
+ for (const runtimePath of findLocalStdioRuntimePaths(rootDir, server)) {
8470
+ if (passthroughDirs.some((dir) => runtimePath === dir || runtimePath.startsWith(`${dir}/`))) continue;
8471
+ const relativeDir = `./${relative6(rootDir, runtimePath).replace(/\\/g, "/")}/`;
8472
+ pushIssue(issues, {
8473
+ level: "warning",
8474
+ code: "mcp-stdio-runtime-unbundled",
8475
+ message: `MCP server "${serverName}" references a project-local stdio runtime under ${relativeDir}, but that directory is not included in passthrough. Installed bundles may ship the MCP config without the executable payload.`,
8476
+ file: "pluxx.config.ts",
8477
+ platform: "MCP"
8478
+ });
8479
+ }
8468
8480
  }
8469
8481
  const runtimeAuthTargets = [];
8470
8482
  if (server.auth?.type === "platform") {
@@ -8488,6 +8500,37 @@ function lintMcpRuntimeState(config, issues) {
8488
8500
  }
8489
8501
  }
8490
8502
  }
8503
+ function resolveBundledPassthroughDir(rootDir, entry) {
8504
+ const resolvedPath = resolve9(rootDir, entry);
8505
+ if (!existsSync17(resolvedPath)) return null;
8506
+ try {
8507
+ const stats = lstatSync(resolvedPath);
8508
+ if (!stats.isDirectory()) return null;
8509
+ return resolvedPath.replace(/\/+$/, "");
8510
+ } catch {
8511
+ return null;
8512
+ }
8513
+ }
8514
+ function findLocalStdioRuntimePaths(rootDir, server) {
8515
+ if (server.transport !== "stdio") return [];
8516
+ const runtimeDirs = /* @__PURE__ */ new Set();
8517
+ const candidates = [server.command, ...server.args ?? []];
8518
+ for (const candidate of candidates) {
8519
+ if (!isLikelyLocalRuntimePath(candidate)) continue;
8520
+ const resolvedPath = resolve9(rootDir, candidate);
8521
+ if (!existsSync17(resolvedPath)) continue;
8522
+ try {
8523
+ const stats = lstatSync(resolvedPath);
8524
+ const runtimeDir = stats.isDirectory() ? resolvedPath : dirname3(resolvedPath);
8525
+ runtimeDirs.add(runtimeDir.replace(/\/+$/, ""));
8526
+ } catch {
8527
+ }
8528
+ }
8529
+ return [...runtimeDirs].sort();
8530
+ }
8531
+ function isLikelyLocalRuntimePath(value) {
8532
+ return value.startsWith("./") || value.startsWith("../") || value.startsWith(".\\") || value.startsWith("..\\");
8533
+ }
8491
8534
  function lintCodexHookCompatibility(config, issues) {
8492
8535
  if (!isCodexTargetEnabled(config) || !config.hooks) return;
8493
8536
  for (const hookEvent of Object.keys(config.hooks)) {
@@ -8721,15 +8764,19 @@ function lintOpenCodeAgentFrontmatter(dir, config, issues) {
8721
8764
  const agents = readCanonicalAgentFiles(resolve9(dir, config.agents));
8722
8765
  for (const agent of agents) {
8723
8766
  if (!("tools" in agent.frontmatter)) continue;
8767
+ if (hasCanonicalAgentPermission(agent.frontmatter.permission)) continue;
8724
8768
  pushIssue(issues, {
8725
8769
  level: "warning",
8726
8770
  code: "opencode-agent-tools-deprecated",
8727
- message: "OpenCode agent `tools` is deprecated. Pluxx will translate legacy agent tools into permission-first OpenCode output where possible, but canonical agents should prefer `permission`.",
8771
+ message: "OpenCode agent `tools` is deprecated. Add canonical `permission` frontmatter so Pluxx can keep the emitted OpenCode agent permission-first even when shared cross-host authoring still carries legacy tool hints.",
8728
8772
  file: relative6(dir, agent.filePath).replace(/\\/g, "/"),
8729
8773
  platform: "OpenCode"
8730
8774
  });
8731
8775
  }
8732
8776
  }
8777
+ function hasCanonicalAgentPermission(value) {
8778
+ return !!value && typeof value === "object" && !Array.isArray(value);
8779
+ }
8733
8780
  function lintAbsolutePaths(config, issues) {
8734
8781
  const absolutePathPattern = /^\/[a-zA-Z]|^[A-Z]:\\/;
8735
8782
  if (config.hooks) {
@@ -9172,7 +9219,7 @@ async function lintProject(dir = process.cwd(), options = {}) {
9172
9219
  lintAgentIsolation(agentFiles, issues, frontmatterCache);
9173
9220
  lintOpenCodeAgentFrontmatter(dir, { ...lintConfig, agents: lintConfig.agents ?? "./agents/" }, issues);
9174
9221
  lintMcpUrls(lintConfig, issues);
9175
- lintMcpRuntimeState(lintConfig, issues);
9222
+ lintMcpRuntimeState(dir, lintConfig, issues);
9176
9223
  lintBrandMetadata(lintConfig, issues);
9177
9224
  lintCodexOverrides(lintConfig, issues);
9178
9225
  lintCodexHookCompatibility(lintConfig, issues);
@@ -9256,16 +9303,17 @@ async function runLint(dir = process.cwd()) {
9256
9303
  }
9257
9304
 
9258
9305
  // src/cli/test.ts
9259
- import { existsSync as existsSync19 } from "fs";
9306
+ import { existsSync as existsSync20 } from "fs";
9260
9307
  import { resolve as resolve12 } from "path";
9261
9308
 
9262
9309
  // src/cli/eval.ts
9263
- import { existsSync as existsSync18, readFileSync as readFileSync7 } from "fs";
9310
+ import { existsSync as existsSync19, readFileSync as readFileSync7 } from "fs";
9264
9311
  import { resolve as resolve11 } from "path";
9265
9312
 
9266
9313
  // src/cli/init-from-mcp.ts
9314
+ import { existsSync as existsSync18, lstatSync as lstatSync2 } from "fs";
9267
9315
  import { mkdir as mkdir2 } from "fs/promises";
9268
- import { basename as basename5, resolve as resolve10 } from "path";
9316
+ import { basename as basename5, dirname as dirname4, relative as relative7, resolve as resolve10 } from "path";
9269
9317
 
9270
9318
  // src/user-config.ts
9271
9319
  var ENV_VAR_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
@@ -9548,6 +9596,7 @@ async function planMcpScaffold(options) {
9548
9596
  });
9549
9597
  const serverName = options.serverName ?? toKebabCase(options.introspection.serverInfo.name) ?? pluginName;
9550
9598
  const permissions = options.permissions ?? (options.approveMcpTools ? { allow: [`MCP(${serverName}.*)`] } : void 0);
9599
+ const passthroughPaths = inferLocalRuntimePassthroughPaths(options.rootDir, options.source);
9551
9600
  const runtimeAuthMode = options.runtimeAuthMode ?? (options.source.transport !== "stdio" && options.source.auth?.type === "platform" ? "platform" : "inline");
9552
9601
  const instructionsPath = resolve10(options.rootDir, "INSTRUCTIONS.md");
9553
9602
  const skillRoot = resolve10(options.rootDir, "skills");
@@ -9589,6 +9638,7 @@ async function planMcpScaffold(options) {
9589
9638
  userConfig,
9590
9639
  hooks: generatedHooks.hookEntries,
9591
9640
  scriptsPath: generatedHooks.scriptsPath,
9641
+ passthroughPaths,
9592
9642
  runtimeAuthMode,
9593
9643
  permissions,
9594
9644
  commandsPath: "./commands/"
@@ -9986,6 +10036,8 @@ function buildConfigTemplate(input) {
9986
10036
  userConfig: ${serializeUserConfig(input.userConfig)},
9987
10037
  ` : "";
9988
10038
  const scriptsBlock = input.scriptsPath ? ` scripts: ${JSON.stringify(input.scriptsPath)},
10039
+ ` : "";
10040
+ const passthroughBlock = input.passthroughPaths && input.passthroughPaths.length > 0 ? ` passthrough: ${JSON.stringify(input.passthroughPaths)},
9989
10041
  ` : "";
9990
10042
  const commandsBlock = input.commandsPath ? ` commands: ${JSON.stringify(input.commandsPath)},
9991
10043
  ` : "";
@@ -10021,6 +10073,7 @@ ${commandsBlock}
10021
10073
  instructions: './INSTRUCTIONS.md',
10022
10074
  ${userConfigBlock}
10023
10075
  ${scriptsBlock}
10076
+ ${passthroughBlock}
10024
10077
 
10025
10078
  mcp: {
10026
10079
  ${mcpBlock}
@@ -10037,6 +10090,29 @@ ${platformsBlock}
10037
10090
  })
10038
10091
  `;
10039
10092
  }
10093
+ function inferLocalRuntimePassthroughPaths(rootDir, source) {
10094
+ if (source.transport !== "stdio") return [];
10095
+ const passthrough = /* @__PURE__ */ new Set();
10096
+ const candidates = [source.command, ...source.args ?? []];
10097
+ for (const candidate of candidates) {
10098
+ if (!isLikelyLocalRuntimePath2(candidate)) continue;
10099
+ const resolvedPath = resolve10(rootDir, candidate);
10100
+ if (!existsSync18(resolvedPath)) continue;
10101
+ const stats = lstatSync2(resolvedPath);
10102
+ const runtimeDir = stats.isDirectory() ? resolvedPath : dirname4(resolvedPath);
10103
+ const normalized = normalizePassthroughDir(rootDir, runtimeDir);
10104
+ if (normalized) passthrough.add(normalized);
10105
+ }
10106
+ return [...passthrough].sort();
10107
+ }
10108
+ function isLikelyLocalRuntimePath2(value) {
10109
+ return value.startsWith("./") || value.startsWith("../") || value.startsWith(".\\") || value.startsWith("..\\");
10110
+ }
10111
+ function normalizePassthroughDir(rootDir, dirPath) {
10112
+ const relativePath = relative7(rootDir, dirPath).replace(/\\/g, "/").replace(/\/+$/, "");
10113
+ if (!relativePath || relativePath === ".") return null;
10114
+ return `./${relativePath}/`;
10115
+ }
10040
10116
  function buildMcpBlock(serverName, source) {
10041
10117
  if (source.transport === "stdio") {
10042
10118
  const argsLine = source.args && source.args.length > 0 ? `,
@@ -11076,7 +11152,7 @@ function summarizeChecks(checks) {
11076
11152
  }
11077
11153
  async function loadMcpScaffoldMetadata(rootDir) {
11078
11154
  const metadataPath = resolve11(rootDir, MCP_SCAFFOLD_METADATA_PATH);
11079
- if (!existsSync18(metadataPath)) {
11155
+ if (!existsSync19(metadataPath)) {
11080
11156
  return null;
11081
11157
  }
11082
11158
  const parsed = JSON.parse(readFileSync7(metadataPath, "utf-8"));
@@ -11107,7 +11183,7 @@ function collectSkillPromptLabels(skill) {
11107
11183
  function evaluateInstructions(rootDir, metadata, checks) {
11108
11184
  const relativePath = "INSTRUCTIONS.md";
11109
11185
  const filePath = resolve11(rootDir, relativePath);
11110
- if (!existsSync18(filePath)) {
11186
+ if (!existsSync19(filePath)) {
11111
11187
  addCheck(checks, {
11112
11188
  level: "error",
11113
11189
  code: "instructions-missing",
@@ -11153,7 +11229,7 @@ function evaluateSkills(rootDir, metadata, checks) {
11153
11229
  for (const skill of metadata.skills) {
11154
11230
  const relativePath = `skills/${skill.dirName}/SKILL.md`;
11155
11231
  const filePath = resolve11(rootDir, relativePath);
11156
- if (!existsSync18(filePath)) {
11232
+ if (!existsSync19(filePath)) {
11157
11233
  failures.push({ path: relativePath, missing: ["skill file"] });
11158
11234
  continue;
11159
11235
  }
@@ -11211,7 +11287,7 @@ function evaluateCommands(rootDir, metadata, checks) {
11211
11287
  for (const skill of metadata.skills) {
11212
11288
  const relativePath = `commands/${skill.dirName}.md`;
11213
11289
  const filePath = resolve11(rootDir, relativePath);
11214
- if (!existsSync18(filePath)) {
11290
+ if (!existsSync19(filePath)) {
11215
11291
  failures.push({ path: relativePath, missing: ["command file"] });
11216
11292
  continue;
11217
11293
  }
@@ -11463,7 +11539,7 @@ async function runTestSuite(options = {}) {
11463
11539
  const evalReport = await runEvalSuite({ rootDir });
11464
11540
  const checks = targets.map((platform) => {
11465
11541
  const requiredPath = SMOKE_PATHS[platform];
11466
- const ok = existsSync19(resolve12(rootDir, config.outDir, platform, requiredPath));
11542
+ const ok = existsSync20(resolve12(rootDir, config.outDir, platform, requiredPath));
11467
11543
  return { platform, requiredPath, ok };
11468
11544
  });
11469
11545
  return {
@@ -11520,8 +11596,8 @@ function printTestResult(result) {
11520
11596
  }
11521
11597
 
11522
11598
  // src/cli/sync-from-mcp.ts
11523
- import { cpSync as cpSync2, existsSync as existsSync20, mkdtempSync, readFileSync as readFileSync8, rmSync as rmSync2, readdirSync as readdirSync6, rmdirSync, writeFileSync as writeFileSync3 } from "fs";
11524
- import { dirname as dirname4, isAbsolute, relative as relative7, resolve as resolve13 } from "path";
11599
+ import { cpSync as cpSync2, existsSync as existsSync21, mkdtempSync, readFileSync as readFileSync8, rmSync as rmSync2, readdirSync as readdirSync6, rmdirSync, writeFileSync as writeFileSync3 } from "fs";
11600
+ import { dirname as dirname5, isAbsolute, relative as relative8, resolve as resolve13 } from "path";
11525
11601
  import { tmpdir } from "os";
11526
11602
 
11527
11603
  // src/mcp/introspect.ts
@@ -11803,10 +11879,10 @@ async function createSseClient(server) {
11803
11879
  let resolveEndpoint;
11804
11880
  let rejectEndpoint;
11805
11881
  let endpointSettled = false;
11806
- const endpointReady = new Promise((resolve24, reject) => {
11882
+ const endpointReady = new Promise((resolve25, reject) => {
11807
11883
  resolveEndpoint = (value) => {
11808
11884
  endpointSettled = true;
11809
- resolve24(value);
11885
+ resolve25(value);
11810
11886
  };
11811
11887
  rejectEndpoint = (error) => {
11812
11888
  endpointSettled = true;
@@ -11943,7 +12019,7 @@ async function createSseClient(server) {
11943
12019
  async request(method, params) {
11944
12020
  const requestId = nextRequestId();
11945
12021
  const endpoint = endpointUrl ?? await endpointReady;
11946
- const resultPromise = new Promise((resolve24, reject) => {
12022
+ const resultPromise = new Promise((resolve25, reject) => {
11947
12023
  const timeout = setTimeout(() => {
11948
12024
  pending.delete(requestId);
11949
12025
  reject(new McpIntrospectionError(`Timed out waiting for MCP SSE response to ${method}.`));
@@ -11951,7 +12027,7 @@ async function createSseClient(server) {
11951
12027
  pending.set(requestId, {
11952
12028
  resolve: (value) => {
11953
12029
  clearTimeout(timeout);
11954
- resolve24(value);
12030
+ resolve25(value);
11955
12031
  },
11956
12032
  reject: (error) => {
11957
12033
  clearTimeout(timeout);
@@ -12104,7 +12180,7 @@ async function createStdioClient(server) {
12104
12180
  method,
12105
12181
  ...params ? { params } : {}
12106
12182
  });
12107
- return new Promise((resolve24, reject) => {
12183
+ return new Promise((resolve25, reject) => {
12108
12184
  const timeout = setTimeout(() => {
12109
12185
  pending.delete(id);
12110
12186
  reject(new McpIntrospectionError(`Timed out waiting for MCP stdio response to ${method}.`));
@@ -12112,7 +12188,7 @@ async function createStdioClient(server) {
12112
12188
  pending.set(id, {
12113
12189
  resolve: (value) => {
12114
12190
  clearTimeout(timeout);
12115
- resolve24(value);
12191
+ resolve25(value);
12116
12192
  },
12117
12193
  reject: (error) => {
12118
12194
  clearTimeout(timeout);
@@ -12290,7 +12366,7 @@ function nextRequestId() {
12290
12366
  // src/cli/sync-from-mcp.ts
12291
12367
  async function readMcpScaffoldMetadata(rootDir) {
12292
12368
  const filepath = resolveWithinRoot(rootDir, MCP_SCAFFOLD_METADATA_PATH);
12293
- if (!existsSync20(filepath)) {
12369
+ if (!existsSync21(filepath)) {
12294
12370
  throw new Error(
12295
12371
  `No MCP scaffold metadata found at ${MCP_SCAFFOLD_METADATA_PATH}. Run "pluxx init --from-mcp" first.`
12296
12372
  );
@@ -12325,14 +12401,14 @@ async function syncFromMcp(options) {
12325
12401
  const skillRenames = detectSkillRenames(metadata.skills, newMetadata.skills, toolRenames);
12326
12402
  for (const [oldSkillDir, newSkillDir] of skillRenames) {
12327
12403
  const oldSkillPath = resolveWithinRoot(options.rootDir, `skills/${oldSkillDir}/SKILL.md`);
12328
- if (!existsSync20(oldSkillPath)) continue;
12404
+ if (!existsSync21(oldSkillPath)) continue;
12329
12405
  const oldContent = readFileSync8(oldSkillPath, "utf-8");
12330
12406
  const extracted = extractMixedMarkdownContent(oldContent, "");
12331
12407
  if (!hasMeaningfulCustomContent(oldContent)) continue;
12332
12408
  const newSkill = newMetadata.skills.find((s) => s.dirName === newSkillDir);
12333
12409
  if (!newSkill) continue;
12334
12410
  const newSkillPath = resolveWithinRoot(options.rootDir, `skills/${newSkill.dirName}/SKILL.md`);
12335
- if (!existsSync20(newSkillPath)) continue;
12411
+ if (!existsSync21(newSkillPath)) continue;
12336
12412
  const currentContent = readFileSync8(newSkillPath, "utf-8");
12337
12413
  const updatedContent = injectCustomContent(currentContent, extracted.customContent);
12338
12414
  writeFileSync3(newSkillPath, updatedContent, "utf-8");
@@ -12375,7 +12451,7 @@ async function syncFromMcp(options) {
12375
12451
  if (!beforeManaged.has(file)) return false;
12376
12452
  const before = beforeContents.get(file);
12377
12453
  const currentPath = resolveWithinRoot(options.rootDir, file);
12378
- if (!existsSync20(currentPath)) return false;
12454
+ if (!existsSync21(currentPath)) return false;
12379
12455
  const after = readFileSync8(currentPath, "utf-8");
12380
12456
  return before !== after;
12381
12457
  });
@@ -12464,7 +12540,7 @@ async function planSyncFromMcp(options) {
12464
12540
  }
12465
12541
  function readPersistedSkills(rootDir, metadata) {
12466
12542
  const taxonomyPath = resolveWithinRoot(rootDir, MCP_TAXONOMY_PATH);
12467
- if (existsSync20(taxonomyPath)) {
12543
+ if (existsSync21(taxonomyPath)) {
12468
12544
  return JSON.parse(readFileSync8(taxonomyPath, "utf-8"));
12469
12545
  }
12470
12546
  return metadata.skills.map((skill) => ({
@@ -12477,12 +12553,12 @@ function readPersistedSkills(rootDir, metadata) {
12477
12553
  function preserveCustomContentForRenames(rootDir, renames, pathForName) {
12478
12554
  for (const [oldName, newName] of renames) {
12479
12555
  const oldPath = resolveWithinRoot(rootDir, pathForName(oldName));
12480
- if (!existsSync20(oldPath)) continue;
12556
+ if (!existsSync21(oldPath)) continue;
12481
12557
  const oldContent = readFileSync8(oldPath, "utf-8");
12482
12558
  const extracted = extractMixedMarkdownContent(oldContent, "");
12483
12559
  if (!hasMeaningfulCustomContent(oldContent)) continue;
12484
12560
  const newPath = resolveWithinRoot(rootDir, pathForName(newName));
12485
- if (!existsSync20(newPath)) continue;
12561
+ if (!existsSync21(newPath)) continue;
12486
12562
  const currentContent = readFileSync8(newPath, "utf-8");
12487
12563
  const updatedContent = injectCustomContent(currentContent, extracted.customContent);
12488
12564
  writeFileSync3(newPath, updatedContent, "utf-8");
@@ -12492,21 +12568,21 @@ function snapshotManagedFiles(rootDir, files) {
12492
12568
  const contents = /* @__PURE__ */ new Map();
12493
12569
  for (const file of files) {
12494
12570
  const filepath = resolveWithinRoot(rootDir, file);
12495
- if (!existsSync20(filepath)) continue;
12571
+ if (!existsSync21(filepath)) continue;
12496
12572
  contents.set(file, readFileSync8(filepath, "utf-8"));
12497
12573
  }
12498
12574
  return contents;
12499
12575
  }
12500
12576
  function removeManagedFile(rootDir, relativePath) {
12501
12577
  const filepath = resolveWithinRoot(rootDir, relativePath);
12502
- if (!existsSync20(filepath)) return;
12578
+ if (!existsSync21(filepath)) return;
12503
12579
  rmSync2(filepath, { force: true });
12504
- pruneEmptyDirectories(rootDir, dirname4(filepath));
12580
+ pruneEmptyDirectories(rootDir, dirname5(filepath));
12505
12581
  }
12506
12582
  function shouldPreserveManagedFile(rootDir, relativePath) {
12507
12583
  if (!relativePath.endsWith(".md")) return false;
12508
12584
  const filepath = resolveWithinRoot(rootDir, relativePath);
12509
- if (!existsSync20(filepath)) return false;
12585
+ if (!existsSync21(filepath)) return false;
12510
12586
  return hasMeaningfulCustomContent(readFileSync8(filepath, "utf-8"));
12511
12587
  }
12512
12588
  function pruneEmptyDirectories(rootDir, startDir) {
@@ -12516,7 +12592,7 @@ function pruneEmptyDirectories(rootDir, startDir) {
12516
12592
  const entries = readdirSync6(current);
12517
12593
  if (entries.length > 0) return;
12518
12594
  rmdirSync(current);
12519
- current = dirname4(current);
12595
+ current = dirname5(current);
12520
12596
  }
12521
12597
  }
12522
12598
  var AGENT_PACK_FILES = [
@@ -12680,7 +12756,7 @@ function computeSkillRenameScore(oldSkill, newSkill, toolRenames) {
12680
12756
  function resolveWithinRoot(rootDir, relativePath) {
12681
12757
  const rootPath = resolve13(rootDir);
12682
12758
  const filepath = resolve13(rootPath, relativePath);
12683
- const relativePathFromRoot = relative7(rootPath, filepath);
12759
+ const relativePathFromRoot = relative8(rootPath, filepath);
12684
12760
  if (relativePathFromRoot === "" || !relativePathFromRoot.startsWith("..") && !isAbsolute(relativePathFromRoot)) {
12685
12761
  return filepath;
12686
12762
  }
@@ -12696,13 +12772,13 @@ function formatSyncSummary(result, rootDir) {
12696
12772
  result.renamedFiles.forEach((rename) => lines.push(` \u2192 ${rename.from} \u2192 ${rename.to}`));
12697
12773
  }
12698
12774
  lines.push(`Added: ${result.addedFiles.length}`);
12699
- result.addedFiles.forEach((file) => lines.push(` + ${relative7(rootDir, resolve13(rootDir, file))}`));
12775
+ result.addedFiles.forEach((file) => lines.push(` + ${relative8(rootDir, resolve13(rootDir, file))}`));
12700
12776
  lines.push(`Updated: ${result.updatedFiles.length}`);
12701
- result.updatedFiles.forEach((file) => lines.push(` ~ ${relative7(rootDir, resolve13(rootDir, file))}`));
12777
+ result.updatedFiles.forEach((file) => lines.push(` ~ ${relative8(rootDir, resolve13(rootDir, file))}`));
12702
12778
  lines.push(`Removed: ${result.removedFiles.length}`);
12703
- result.removedFiles.forEach((file) => lines.push(` - ${relative7(rootDir, resolve13(rootDir, file))}`));
12779
+ result.removedFiles.forEach((file) => lines.push(` - ${relative8(rootDir, resolve13(rootDir, file))}`));
12704
12780
  lines.push(`Preserved: ${result.preservedFiles.length}`);
12705
- result.preservedFiles.forEach((file) => lines.push(` ! ${relative7(rootDir, resolve13(rootDir, file))}`));
12781
+ result.preservedFiles.forEach((file) => lines.push(` ! ${relative8(rootDir, resolve13(rootDir, file))}`));
12706
12782
  return lines;
12707
12783
  }
12708
12784
 
@@ -12791,7 +12867,7 @@ async function planAgentPrompt(rootDir, kind, options = {}) {
12791
12867
  const project = await loadAgentProjectModel(rootDir, config);
12792
12868
  const overrides = await loadAgentOverrides(rootDir);
12793
12869
  const contextPath = resolve14(rootDir, AGENT_CONTEXT_PATH);
12794
- if (!options.allowMissingContext && !existsSync21(contextPath)) {
12870
+ if (!options.allowMissingContext && !existsSync22(contextPath)) {
12795
12871
  throw new Error(`No agent context found at ${AGENT_CONTEXT_PATH}. Run "pluxx agent prepare" first.`);
12796
12872
  }
12797
12873
  if (project.sourceKind !== "mcp-derived" && kind !== "review") {
@@ -12803,7 +12879,7 @@ async function planAgentPrompt(rootDir, kind, options = {}) {
12803
12879
  displayName: project.displayName,
12804
12880
  skillPaths: project.skills.map((skill) => skill.path),
12805
12881
  commandPaths: project.commands.map((command2) => command2.path),
12806
- extraContextPaths: [AGENT_SOURCES_PATH, AGENT_DOCS_CONTEXT_PATH].filter((path) => existsSync21(resolve14(rootDir, path))),
12882
+ extraContextPaths: [AGENT_SOURCES_PATH, AGENT_DOCS_CONTEXT_PATH].filter((path) => existsSync22(resolve14(rootDir, path))),
12807
12883
  sourceKind: project.sourceKind,
12808
12884
  taxonomyPath: project.taxonomyPath,
12809
12885
  overrides
@@ -12971,7 +13047,7 @@ function buildProtectedFiles() {
12971
13047
  }
12972
13048
  async function loadMcpScaffoldMetadata2(rootDir) {
12973
13049
  const metadataPath = resolve14(rootDir, MCP_SCAFFOLD_METADATA_PATH);
12974
- if (!existsSync21(metadataPath)) {
13050
+ if (!existsSync22(metadataPath)) {
12975
13051
  throw new Error(`No MCP scaffold metadata found at ${MCP_SCAFFOLD_METADATA_PATH}. Run "pluxx init --from-mcp" first.`);
12976
13052
  }
12977
13053
  try {
@@ -12985,7 +13061,7 @@ async function loadMcpScaffoldMetadata2(rootDir) {
12985
13061
  }
12986
13062
  async function loadAgentProjectModel(rootDir, config) {
12987
13063
  const metadataPath = resolve14(rootDir, MCP_SCAFFOLD_METADATA_PATH);
12988
- if (existsSync21(metadataPath)) {
13064
+ if (existsSync22(metadataPath)) {
12989
13065
  const metadata = await loadMcpScaffoldMetadata2(rootDir);
12990
13066
  const serverEntry = Object.entries(config.mcp ?? {})[0];
12991
13067
  const [serverName, server] = serverEntry ?? ["unknown", metadata.source];
@@ -13023,7 +13099,7 @@ function loadManualAgentProjectModel(rootDir, config) {
13023
13099
  const commandsDir = config.commands ? resolve14(rootDir, config.commands) : void 0;
13024
13100
  const skills = readCanonicalSkillFiles(rootDir, skillsDir);
13025
13101
  const commands = readCanonicalCommandFiles(commandsDir).map((command2) => ({
13026
- path: normalizeRelativePath(relative8(rootDir, command2.filePath)),
13102
+ path: normalizeRelativePath(relative9(rootDir, command2.filePath)),
13027
13103
  title: command2.title,
13028
13104
  description: command2.description
13029
13105
  }));
@@ -13354,7 +13430,7 @@ async function collectAgentContextPackInternal(rootDir, options, overrides) {
13354
13430
  if (seenFilePaths.has(relativePath)) continue;
13355
13431
  seenFilePaths.add(relativePath);
13356
13432
  const filePath = resolve14(rootDir, relativePath);
13357
- if (!existsSync21(filePath)) {
13433
+ if (!existsSync22(filePath)) {
13358
13434
  const source2 = {
13359
13435
  label: relativePath,
13360
13436
  kind: "file",
@@ -14524,18 +14600,18 @@ function normalizeRelativePath(path) {
14524
14600
  return path.replace(/\\/g, "/").replace(/^\.\//, "");
14525
14601
  }
14526
14602
  function readCanonicalSkillFiles(rootDir, skillsDir) {
14527
- if (!skillsDir || !existsSync21(skillsDir)) return [];
14603
+ if (!skillsDir || !existsSync22(skillsDir)) return [];
14528
14604
  return walkSkillMarkdownFiles(skillsDir).sort((a, b) => a.localeCompare(b)).map((filePath) => {
14529
14605
  const content = readFileSync9(filePath, "utf-8");
14530
14606
  const { frontmatterLines, body } = splitSkillMarkdownFrontmatter(content);
14531
- const dirName = normalizeRelativePath(relative8(skillsDir, filePath).replace(/\/SKILL\.md$/i, ""));
14607
+ const dirName = normalizeRelativePath(relative9(skillsDir, filePath).replace(/\/SKILL\.md$/i, ""));
14532
14608
  const title = firstMarkdownHeading(body) ?? dirName;
14533
14609
  return {
14534
14610
  dirName,
14535
14611
  title,
14536
14612
  description: parseYamlDescription(frontmatterLines),
14537
14613
  toolNames: [],
14538
- path: normalizeRelativePath(relative8(rootDir, filePath))
14614
+ path: normalizeRelativePath(relative9(rootDir, filePath))
14539
14615
  };
14540
14616
  });
14541
14617
  }
@@ -14845,7 +14921,7 @@ async function executeCommand(command2, cwd, options = {}) {
14845
14921
  let claudeTurnCompleted = false;
14846
14922
  let claudeTurnFailed = false;
14847
14923
  const sentinelInterval = codexLastMessagePath || isClaudeStreamJson ? setInterval(() => {
14848
- const sawCompletionSignal = codexTurnCompleted || codexTurnFailed || claudeTurnCompleted || claudeTurnFailed || (codexLastMessagePath ? existsSync21(codexLastMessagePath) : false);
14924
+ const sawCompletionSignal = codexTurnCompleted || codexTurnFailed || claudeTurnCompleted || claudeTurnFailed || (codexLastMessagePath ? existsSync22(codexLastMessagePath) : false);
14849
14925
  if (!sawCompletionSignal) return;
14850
14926
  if (sawFinalMessageAt == null) {
14851
14927
  sawFinalMessageAt = Date.now();
@@ -14971,15 +15047,15 @@ exec ${shellQuote(cursorBinary)} "$@"
14971
15047
  await mkdir3(resolve14(isolatedCodexHome, "memories"), { recursive: true });
14972
15048
  for (const relativePath of ["auth.json", "config.toml", "hooks.json", "installation_id"]) {
14973
15049
  const sourcePath = resolve14(currentCodexHome, relativePath);
14974
- if (!existsSync21(sourcePath)) continue;
15050
+ if (!existsSync22(sourcePath)) continue;
14975
15051
  await copyFile(sourcePath, resolve14(isolatedCodexHome, relativePath));
14976
15052
  }
14977
15053
  const rulesSourceDir = resolve14(currentCodexHome, "rules");
14978
- if (existsSync21(rulesSourceDir)) {
15054
+ if (existsSync22(rulesSourceDir)) {
14979
15055
  const rulesTargetDir = resolve14(isolatedCodexHome, "rules");
14980
15056
  await mkdir3(rulesTargetDir, { recursive: true });
14981
15057
  const defaultRulesPath = resolve14(rulesSourceDir, "default.rules");
14982
- if (existsSync21(defaultRulesPath)) {
15058
+ if (existsSync22(defaultRulesPath)) {
14983
15059
  await copyFile(defaultRulesPath, resolve14(rulesTargetDir, "default.rules"));
14984
15060
  }
14985
15061
  }
@@ -15017,7 +15093,7 @@ function titleCase(value) {
15017
15093
  }
15018
15094
  async function loadAgentOverrides(rootDir) {
15019
15095
  const overridesPath = resolve14(rootDir, AGENT_OVERRIDES_PATH);
15020
- if (!existsSync21(overridesPath)) {
15096
+ if (!existsSync22(overridesPath)) {
15021
15097
  return null;
15022
15098
  }
15023
15099
  const content = await readTextFile(overridesPath);
@@ -15119,12 +15195,12 @@ ${additions.map((block) => `- ${block.replace(/\n/g, "\n ")}`).join("\n")}
15119
15195
  }
15120
15196
 
15121
15197
  // src/cli/doctor.ts
15122
- import { accessSync, constants, existsSync as existsSync23, lstatSync, readFileSync as readFileSync11, readdirSync as readdirSync9 } from "fs";
15123
- import { basename as basename6, dirname as dirname6, resolve as resolve16 } from "path";
15198
+ import { accessSync, constants, existsSync as existsSync24, lstatSync as lstatSync3, readFileSync as readFileSync11, readdirSync as readdirSync9 } from "fs";
15199
+ import { basename as basename6, dirname as dirname7, resolve as resolve16 } from "path";
15124
15200
 
15125
15201
  // src/cli/install.ts
15126
- import { resolve as resolve15, dirname as dirname5 } from "path";
15127
- import { existsSync as existsSync22, symlinkSync, mkdirSync as mkdirSync4, rmSync as rmSync3, readFileSync as readFileSync10, writeFileSync as writeFileSync4, cpSync as cpSync3, readdirSync as readdirSync8 } from "fs";
15202
+ import { resolve as resolve15, dirname as dirname6 } from "path";
15203
+ import { existsSync as existsSync23, symlinkSync, mkdirSync as mkdirSync4, rmSync as rmSync3, readFileSync as readFileSync10, writeFileSync as writeFileSync4, cpSync as cpSync3, readdirSync as readdirSync8 } from "fs";
15128
15204
  import { spawnSync } from "child_process";
15129
15205
  import * as readline2 from "readline";
15130
15206
  function listHookCommands(hooks) {
@@ -15393,12 +15469,12 @@ ${content.slice(frontmatterMatch[0].length)}`;
15393
15469
  }
15394
15470
  function syncOpenCodeSkills(pluginDir, pluginName) {
15395
15471
  const sourceSkillsDir = resolve15(pluginDir, "skills");
15396
- if (!existsSync22(sourceSkillsDir)) return;
15472
+ if (!existsSync23(sourceSkillsDir)) return;
15397
15473
  mkdirSync4(getOpenCodeSkillRoot(), { recursive: true });
15398
15474
  for (const entry of readdirSync8(sourceSkillsDir, { withFileTypes: true })) {
15399
15475
  if (!entry.isDirectory()) continue;
15400
15476
  const skillSourceDir = resolve15(sourceSkillsDir, entry.name);
15401
- if (!existsSync22(resolve15(skillSourceDir, "SKILL.md"))) continue;
15477
+ if (!existsSync23(resolve15(skillSourceDir, "SKILL.md"))) continue;
15402
15478
  const installedSkillDir = getOpenCodeInstalledSkillDir(pluginName, entry.name);
15403
15479
  rmSync3(installedSkillDir, { recursive: true, force: true });
15404
15480
  cpSync3(skillSourceDir, installedSkillDir, { recursive: true });
@@ -15411,7 +15487,7 @@ function syncOpenCodeSkills(pluginDir, pluginName) {
15411
15487
  }
15412
15488
  function verifyOpenCodeInstall(pluginDir, pluginName) {
15413
15489
  const entryPath = getOpenCodeEntryPath(pluginDir);
15414
- if (!existsSync22(entryPath)) {
15490
+ if (!existsSync23(entryPath)) {
15415
15491
  throw new Error(`OpenCode install is incomplete: missing host entry file at ${entryPath}`);
15416
15492
  }
15417
15493
  const entryContent = readFileSync10(entryPath, "utf-8");
@@ -15424,13 +15500,13 @@ function verifyOpenCodeInstall(pluginDir, pluginName) {
15424
15500
  throw new Error(`OpenCode install is incomplete: ${entryPath} does not preserve the plugin root bridge`);
15425
15501
  }
15426
15502
  const sourceSkillsDir = resolve15(pluginDir, "skills");
15427
- if (!existsSync22(sourceSkillsDir)) return;
15503
+ if (!existsSync23(sourceSkillsDir)) return;
15428
15504
  for (const entry of readdirSync8(sourceSkillsDir, { withFileTypes: true })) {
15429
15505
  if (!entry.isDirectory()) continue;
15430
15506
  const sourceSkillPath = resolve15(sourceSkillsDir, entry.name, "SKILL.md");
15431
- if (!existsSync22(sourceSkillPath)) continue;
15507
+ if (!existsSync23(sourceSkillPath)) continue;
15432
15508
  const installedSkillPath = resolve15(getOpenCodeInstalledSkillDir(pluginName, entry.name), "SKILL.md");
15433
- if (!existsSync22(installedSkillPath)) {
15509
+ if (!existsSync23(installedSkillPath)) {
15434
15510
  throw new Error(`OpenCode install is incomplete: missing synced skill at ${installedSkillPath}`);
15435
15511
  }
15436
15512
  const installedSkillContent = readFileSync10(installedSkillPath, "utf-8");
@@ -15441,7 +15517,7 @@ function verifyOpenCodeInstall(pluginDir, pluginName) {
15441
15517
  }
15442
15518
  function removeOpenCodeSkills(pluginName) {
15443
15519
  const root = getOpenCodeSkillRoot();
15444
- if (!existsSync22(root)) return false;
15520
+ if (!existsSync23(root)) return false;
15445
15521
  let removed = false;
15446
15522
  for (const entry of readdirSync8(root, { withFileTypes: true })) {
15447
15523
  if (!entry.name.startsWith(`${pluginName}-`)) continue;
@@ -15477,7 +15553,7 @@ function runCommandDefault(command2, args2) {
15477
15553
  function createSymlinkInstall(target) {
15478
15554
  const parentDir = resolve15(target.pluginDir, "..");
15479
15555
  mkdirSync4(parentDir, { recursive: true });
15480
- if (existsSync22(target.pluginDir)) {
15556
+ if (existsSync23(target.pluginDir)) {
15481
15557
  rmSync3(target.pluginDir, { recursive: true, force: true });
15482
15558
  }
15483
15559
  symlinkSync(target.sourceDir, target.pluginDir);
@@ -15495,7 +15571,7 @@ function getCodexMarketplacePluginPath(pluginName) {
15495
15571
  return `./.codex/plugins/${pluginName}`;
15496
15572
  }
15497
15573
  function readCodexMarketplace(filepath) {
15498
- if (!existsSync22(filepath)) {
15574
+ if (!existsSync23(filepath)) {
15499
15575
  return {
15500
15576
  name: "pluxx-local",
15501
15577
  interface: {
@@ -15514,7 +15590,7 @@ function readCodexMarketplace(filepath) {
15514
15590
  }
15515
15591
  function ensureCodexMarketplace(pluginName) {
15516
15592
  const filepath = getCodexMarketplacePath();
15517
- mkdirSync4(dirname5(filepath), { recursive: true });
15593
+ mkdirSync4(dirname6(filepath), { recursive: true });
15518
15594
  const marketplace = readCodexMarketplace(filepath);
15519
15595
  const nextPlugins = (marketplace.plugins ?? []).filter((plugin) => plugin.name !== pluginName);
15520
15596
  nextPlugins.push({
@@ -15540,7 +15616,7 @@ function ensureCodexMarketplace(pluginName) {
15540
15616
  }
15541
15617
  function removeCodexMarketplacePlugin(pluginName) {
15542
15618
  const filepath = getCodexMarketplacePath();
15543
- if (!existsSync22(filepath)) return;
15619
+ if (!existsSync23(filepath)) return;
15544
15620
  const marketplace = readCodexMarketplace(filepath);
15545
15621
  const nextPlugins = (marketplace.plugins ?? []).filter((plugin) => plugin.name !== pluginName);
15546
15622
  if (nextPlugins.length === (marketplace.plugins ?? []).length) {
@@ -15562,7 +15638,7 @@ function removeCodexMarketplacePlugin(pluginName) {
15562
15638
  function createCopiedInstall(target) {
15563
15639
  const parentDir = resolve15(target.pluginDir, "..");
15564
15640
  mkdirSync4(parentDir, { recursive: true });
15565
- if (existsSync22(target.pluginDir)) {
15641
+ if (existsSync23(target.pluginDir)) {
15566
15642
  rmSync3(target.pluginDir, { recursive: true, force: true });
15567
15643
  }
15568
15644
  cpSync3(target.sourceDir, target.pluginDir, { recursive: true });
@@ -15587,7 +15663,7 @@ function patchInstalledMcpConfig(pluginDir, platform, config, entries) {
15587
15663
  const env = buildUserConfigEnvMap(entries);
15588
15664
  if (platform === "claude-code" || platform === "cursor") {
15589
15665
  const filepath = resolve15(pluginDir, platform === "claude-code" ? ".mcp.json" : "mcp.json");
15590
- if (!existsSync22(filepath)) return;
15666
+ if (!existsSync23(filepath)) return;
15591
15667
  const mcpServers = {};
15592
15668
  const usesPlatformManagedAuth = platform === "claude-code" ? config.platforms?.["claude-code"]?.mcpAuth === "platform" : config.platforms?.cursor?.mcpAuth === "platform";
15593
15669
  for (const [name, server] of Object.entries(config.mcp)) {
@@ -15619,7 +15695,7 @@ function patchInstalledMcpConfig(pluginDir, platform, config, entries) {
15619
15695
  }
15620
15696
  if (platform === "codex") {
15621
15697
  const filepath = resolve15(pluginDir, ".mcp.json");
15622
- if (!existsSync22(filepath)) return;
15698
+ if (!existsSync23(filepath)) return;
15623
15699
  const mcpServers = {};
15624
15700
  for (const [name, server] of Object.entries(config.mcp)) {
15625
15701
  if (server.transport === "stdio") {
@@ -15659,7 +15735,7 @@ function writeInstalledUserConfig(pluginDir, entries) {
15659
15735
  function disableInstalledEnvValidation(pluginDir, entries) {
15660
15736
  if (entries.length === 0) return;
15661
15737
  const filepath = resolve15(pluginDir, "scripts/check-env.sh");
15662
- if (!existsSync22(filepath)) return;
15738
+ if (!existsSync23(filepath)) return;
15663
15739
  writeFileSync4(
15664
15740
  filepath,
15665
15741
  "#!/usr/bin/env bash\nset -euo pipefail\n# pluxx install materialized required config for this local plugin install.\nexit 0\n"
@@ -15741,7 +15817,7 @@ function ensureClaudeMarketplaceRegistered(pluginName, sourceDir, runCommand, ma
15741
15817
  }
15742
15818
  function installClaudePlugin(target, pluginName, runCommand, materialized) {
15743
15819
  const marketplaceName = ensureClaudeMarketplaceRegistered(pluginName, target.sourceDir, runCommand, materialized);
15744
- if (existsSync22(target.pluginDir)) {
15820
+ if (existsSync23(target.pluginDir)) {
15745
15821
  rmSync3(target.pluginDir, { recursive: true, force: true });
15746
15822
  }
15747
15823
  runCommand("claude", ["plugin", "uninstall", `${pluginName}@${marketplaceName}`]);
@@ -15760,9 +15836,9 @@ function uninstallClaudePlugin(target, pluginName, runCommand, options = {}) {
15760
15836
  }
15761
15837
  }
15762
15838
  const marketplaceRoot = getClaudeMarketplaceRoot(pluginName);
15763
- const hadMarketplaceRoot = existsSync22(marketplaceRoot);
15839
+ const hadMarketplaceRoot = existsSync23(marketplaceRoot);
15764
15840
  rmSync3(marketplaceRoot, { recursive: true, force: true });
15765
- const hadLegacyPluginDir = existsSync22(target.pluginDir);
15841
+ const hadLegacyPluginDir = existsSync23(target.pluginDir);
15766
15842
  if (hadLegacyPluginDir) {
15767
15843
  rmSync3(target.pluginDir, { recursive: true, force: true });
15768
15844
  }
@@ -15776,8 +15852,8 @@ function planInstallPlugin(distDir, pluginName, platforms) {
15776
15852
  return {
15777
15853
  ...target,
15778
15854
  sourceDir,
15779
- built: existsSync22(sourceDir),
15780
- existing: existsSync22(target.pluginDir)
15855
+ built: existsSync23(sourceDir),
15856
+ existing: existsSync23(target.pluginDir)
15781
15857
  };
15782
15858
  });
15783
15859
  }
@@ -15853,13 +15929,13 @@ async function uninstallPlugin(pluginName, platforms, options = {}) {
15853
15929
  continue;
15854
15930
  }
15855
15931
  let removedTarget = false;
15856
- if (existsSync22(target.pluginDir)) {
15932
+ if (existsSync23(target.pluginDir)) {
15857
15933
  rmSync3(target.pluginDir, { recursive: true, force: true });
15858
15934
  removedTarget = true;
15859
15935
  }
15860
15936
  if (target.platform === "opencode") {
15861
15937
  const entryPath = getOpenCodeEntryPath(target.pluginDir);
15862
- if (existsSync22(entryPath)) {
15938
+ if (existsSync23(entryPath)) {
15863
15939
  rmSync3(entryPath, { force: true });
15864
15940
  removedTarget = true;
15865
15941
  }
@@ -15969,7 +16045,7 @@ function checkReadablePath(checks, rootDir, label, configuredPath, required) {
15969
16045
  return;
15970
16046
  }
15971
16047
  const resolvedPath = resolve16(rootDir, configuredPath);
15972
- if (!existsSync23(resolvedPath)) {
16048
+ if (!existsSync24(resolvedPath)) {
15973
16049
  addCheck2(checks, {
15974
16050
  level: "error",
15975
16051
  code: "path-not-found",
@@ -16335,7 +16411,7 @@ function checkCompilerIntent(checks, rootDir) {
16335
16411
  }
16336
16412
  function checkScaffoldMetadata(checks, rootDir, config) {
16337
16413
  const metadataPath = resolve16(rootDir, MCP_SCAFFOLD_METADATA_PATH);
16338
- if (!existsSync23(metadataPath)) {
16414
+ if (!existsSync24(metadataPath)) {
16339
16415
  addCheck2(checks, {
16340
16416
  level: "info",
16341
16417
  code: "mcp-metadata-missing",
@@ -16400,7 +16476,7 @@ function checkScaffoldMetadata(checks, rootDir, config) {
16400
16476
  }
16401
16477
  }
16402
16478
  function detectConsumerLayout(rootDir) {
16403
- if (existsSync23(resolve16(rootDir, ".claude-plugin/plugin.json"))) {
16479
+ if (existsSync24(resolve16(rootDir, ".claude-plugin/plugin.json"))) {
16404
16480
  return {
16405
16481
  kind: "installed-platform",
16406
16482
  platform: "claude-code",
@@ -16408,7 +16484,7 @@ function detectConsumerLayout(rootDir) {
16408
16484
  mcpConfigPath: ".mcp.json"
16409
16485
  };
16410
16486
  }
16411
- if (existsSync23(resolve16(rootDir, ".cursor-plugin/plugin.json"))) {
16487
+ if (existsSync24(resolve16(rootDir, ".cursor-plugin/plugin.json"))) {
16412
16488
  return {
16413
16489
  kind: "installed-platform",
16414
16490
  platform: "cursor",
@@ -16416,7 +16492,7 @@ function detectConsumerLayout(rootDir) {
16416
16492
  mcpConfigPath: "mcp.json"
16417
16493
  };
16418
16494
  }
16419
- if (existsSync23(resolve16(rootDir, ".codex-plugin/plugin.json"))) {
16495
+ if (existsSync24(resolve16(rootDir, ".codex-plugin/plugin.json"))) {
16420
16496
  return {
16421
16497
  kind: "installed-platform",
16422
16498
  platform: "codex",
@@ -16426,7 +16502,7 @@ function detectConsumerLayout(rootDir) {
16426
16502
  }
16427
16503
  const packagePath = resolve16(rootDir, "package.json");
16428
16504
  const indexPath = resolve16(rootDir, "index.ts");
16429
- if (existsSync23(packagePath) && existsSync23(indexPath)) {
16505
+ if (existsSync24(packagePath) && existsSync24(indexPath)) {
16430
16506
  try {
16431
16507
  const pkg = JSON.parse(readFileSync11(packagePath, "utf-8"));
16432
16508
  if (pkg.peerDependencies?.["@opencode-ai/plugin"] || pkg.keywords?.includes("opencode-plugin")) {
@@ -16444,10 +16520,10 @@ function detectConsumerLayout(rootDir) {
16444
16520
  };
16445
16521
  }
16446
16522
  }
16447
- if (CONFIG_FILES.some((filename) => existsSync23(resolve16(rootDir, filename)))) {
16523
+ if (CONFIG_FILES.some((filename) => existsSync24(resolve16(rootDir, filename)))) {
16448
16524
  return { kind: "source-project" };
16449
16525
  }
16450
- if (["claude-code", "cursor", "codex", "opencode"].some((dir) => existsSync23(resolve16(rootDir, dir)))) {
16526
+ if (["claude-code", "cursor", "codex", "opencode"].some((dir) => existsSync24(resolve16(rootDir, dir)))) {
16451
16527
  return { kind: "multi-target-dist" };
16452
16528
  }
16453
16529
  return { kind: "unknown" };
@@ -16458,7 +16534,7 @@ function readJsonFile2(rootDir, relativePath) {
16458
16534
  function checkConsumerBundlePath(checks, rootDir) {
16459
16535
  try {
16460
16536
  accessSync(rootDir, constants.R_OK);
16461
- const details = lstatSync(rootDir);
16537
+ const details = lstatSync3(rootDir);
16462
16538
  addCheck2(checks, {
16463
16539
  level: "success",
16464
16540
  code: "consumer-path-readable",
@@ -16505,7 +16581,7 @@ function checkConsumerManifest(checks, rootDir, layout) {
16505
16581
  function checkInstalledUserConfig(checks, rootDir) {
16506
16582
  const userConfigPath = ".pluxx-user.json";
16507
16583
  const resolvedPath = resolve16(rootDir, userConfigPath);
16508
- if (!existsSync23(resolvedPath)) {
16584
+ if (!existsSync24(resolvedPath)) {
16509
16585
  addCheck2(checks, {
16510
16586
  level: "info",
16511
16587
  code: "consumer-user-config-missing",
@@ -16542,7 +16618,7 @@ function checkInstalledUserConfig(checks, rootDir) {
16542
16618
  function checkInstalledEnvValidation(checks, rootDir) {
16543
16619
  const envScriptPath = "scripts/check-env.sh";
16544
16620
  const resolvedPath = resolve16(rootDir, envScriptPath);
16545
- if (!existsSync23(resolvedPath)) {
16621
+ if (!existsSync24(resolvedPath)) {
16546
16622
  addCheck2(checks, {
16547
16623
  level: "info",
16548
16624
  code: "consumer-env-script-missing",
@@ -16587,7 +16663,7 @@ function checkInstalledMcpConfig(checks, rootDir, layout) {
16587
16663
  return;
16588
16664
  }
16589
16665
  const resolvedPath = resolve16(rootDir, layout.mcpConfigPath);
16590
- if (!existsSync23(resolvedPath)) {
16666
+ if (!existsSync24(resolvedPath)) {
16591
16667
  addCheck2(checks, {
16592
16668
  level: "info",
16593
16669
  code: "consumer-mcp-config-missing",
@@ -16628,6 +16704,17 @@ function checkInstalledMcpConfig(checks, rootDir, layout) {
16628
16704
  fix: "If tools fail, verify the bundled command or its runtime dependencies on this machine.",
16629
16705
  path: layout.mcpConfigPath
16630
16706
  });
16707
+ const missingRuntimePaths = findMissingInstalledStdioRuntimePaths(rootDir, stdioEntries);
16708
+ if (missingRuntimePaths.length > 0) {
16709
+ addCheck2(checks, {
16710
+ level: "warning",
16711
+ code: "consumer-mcp-stdio-runtime-missing",
16712
+ title: "Bundled stdio MCP runtime files are missing",
16713
+ detail: `This installed MCP config references local runtime path${missingRuntimePaths.length === 1 ? "" : "s"} that do not exist in the bundle: ${missingRuntimePaths.join(", ")}.`,
16714
+ fix: "Rebuild the plugin with the MCP runtime directory included in passthrough, then reinstall the host bundle.",
16715
+ path: layout.mcpConfigPath
16716
+ });
16717
+ }
16631
16718
  }
16632
16719
  if (remoteEntries.length > 0 && inlineHeaderEntries.length > 0) {
16633
16720
  addCheck2(checks, {
@@ -16662,9 +16749,27 @@ function checkInstalledMcpConfig(checks, rootDir, layout) {
16662
16749
  });
16663
16750
  }
16664
16751
  }
16752
+ function findMissingInstalledStdioRuntimePaths(rootDir, stdioEntries) {
16753
+ const missing = /* @__PURE__ */ new Set();
16754
+ for (const server of stdioEntries) {
16755
+ const command2 = typeof server.command === "string" ? server.command : void 0;
16756
+ const args2 = Array.isArray(server.args) ? server.args.filter((value) => typeof value === "string") : [];
16757
+ for (const candidate of [command2, ...args2]) {
16758
+ if (!candidate || !isLikelyLocalRuntimePath3(candidate)) continue;
16759
+ const resolvedPath = resolve16(rootDir, candidate);
16760
+ if (!existsSync24(resolvedPath)) {
16761
+ missing.add(candidate);
16762
+ }
16763
+ }
16764
+ }
16765
+ return [...missing].sort();
16766
+ }
16767
+ function isLikelyLocalRuntimePath3(value) {
16768
+ return value.startsWith("./") || value.startsWith("../") || value.startsWith(".\\") || value.startsWith("..\\");
16769
+ }
16665
16770
  function isLikelyOpenCodeInstallPath(rootDir) {
16666
- const parent = dirname6(rootDir);
16667
- const grandparent = dirname6(parent);
16771
+ const parent = dirname7(rootDir);
16772
+ const grandparent = dirname7(parent);
16668
16773
  return basename6(parent) === "plugins" && basename6(grandparent) === "opencode";
16669
16774
  }
16670
16775
  function checkInstalledOpenCodeHostBridge(checks, rootDir) {
@@ -16682,7 +16787,7 @@ function checkInstalledOpenCodeHostBridge(checks, rootDir) {
16682
16787
  const pluginName = basename6(rootDir);
16683
16788
  const entryPath = `${rootDir}.ts`;
16684
16789
  const entryRelativePath = `${pluginName}.ts`;
16685
- if (!existsSync23(entryPath)) {
16790
+ if (!existsSync24(entryPath)) {
16686
16791
  addCheck2(checks, {
16687
16792
  level: "error",
16688
16793
  code: "consumer-opencode-entry-missing",
@@ -16722,7 +16827,7 @@ function checkInstalledOpenCodeSkills(checks, rootDir) {
16722
16827
  }
16723
16828
  const pluginName = basename6(rootDir);
16724
16829
  const sourceSkillsDir = resolve16(rootDir, "skills");
16725
- if (!existsSync23(sourceSkillsDir)) {
16830
+ if (!existsSync24(sourceSkillsDir)) {
16726
16831
  addCheck2(checks, {
16727
16832
  level: "info",
16728
16833
  code: "consumer-opencode-skill-sync-not-applicable",
@@ -16733,17 +16838,17 @@ function checkInstalledOpenCodeSkills(checks, rootDir) {
16733
16838
  });
16734
16839
  return;
16735
16840
  }
16736
- const skillRoot = resolve16(dirname6(dirname6(rootDir)), "skills");
16841
+ const skillRoot = resolve16(dirname7(dirname7(rootDir)), "skills");
16737
16842
  const missingSkills = [];
16738
16843
  const malformedSkills = [];
16739
16844
  let expectedSkillCount = 0;
16740
16845
  for (const entry of readdirSync9(sourceSkillsDir, { withFileTypes: true })) {
16741
16846
  if (!entry.isDirectory()) continue;
16742
16847
  const sourceSkillPath = resolve16(sourceSkillsDir, entry.name, "SKILL.md");
16743
- if (!existsSync23(sourceSkillPath)) continue;
16848
+ if (!existsSync24(sourceSkillPath)) continue;
16744
16849
  expectedSkillCount++;
16745
16850
  const installedSkillPath = resolve16(skillRoot, `${pluginName}-${entry.name}`, "SKILL.md");
16746
- if (!existsSync23(installedSkillPath)) {
16851
+ if (!existsSync24(installedSkillPath)) {
16747
16852
  missingSkills.push(`${pluginName}-${entry.name}`);
16748
16853
  continue;
16749
16854
  }
@@ -16840,7 +16945,7 @@ async function doctorConsumer(rootDir = process.cwd()) {
16840
16945
  }
16841
16946
  async function doctorProject(rootDir = process.cwd()) {
16842
16947
  const checks = [];
16843
- const configPath = CONFIG_FILES.find((filename) => existsSync23(resolve16(rootDir, filename)));
16948
+ const configPath = CONFIG_FILES.find((filename) => existsSync24(resolve16(rootDir, filename)));
16844
16949
  addRuntimeChecks(checks, "project");
16845
16950
  if (!configPath) {
16846
16951
  addCheck2(checks, {
@@ -16939,7 +17044,7 @@ function printDoctorReport(report) {
16939
17044
 
16940
17045
  // src/cli/dev.ts
16941
17046
  import { watch } from "fs";
16942
- import { relative as relative9, resolve as resolve17 } from "path";
17047
+ import { relative as relative10, resolve as resolve17 } from "path";
16943
17048
  var WATCH_PATTERNS = [
16944
17049
  /^pluxx\.config\.(ts|js|json)$/,
16945
17050
  /^skills\//,
@@ -16965,7 +17070,7 @@ async function runDev(args2) {
16965
17070
  let pendingFile = null;
16966
17071
  const watcher = watch(rootDir, { recursive: true }, (_event, filename) => {
16967
17072
  if (!filename) return;
16968
- const rel = relative9(rootDir, resolve17(rootDir, filename));
17073
+ const rel = relative10(rootDir, resolve17(rootDir, filename));
16969
17074
  if (rel.startsWith("dist/") || rel.startsWith(".") || rel.includes("node_modules")) {
16970
17075
  return;
16971
17076
  }
@@ -17020,8 +17125,8 @@ async function runBuild(rootDir, targets) {
17020
17125
  }
17021
17126
 
17022
17127
  // src/cli/migrate.ts
17023
- import { basename as basename7, relative as relative10, resolve as resolve18 } from "path";
17024
- import { existsSync as existsSync24, readdirSync as readdirSync10, mkdirSync as mkdirSync5, cpSync as cpSync4, readFileSync as readFileSync12, writeFileSync as writeFileSync5 } from "fs";
17128
+ import { basename as basename7, relative as relative11, resolve as resolve18 } from "path";
17129
+ import { existsSync as existsSync25, readdirSync as readdirSync10, mkdirSync as mkdirSync5, cpSync as cpSync4, readFileSync as readFileSync12, writeFileSync as writeFileSync5 } from "fs";
17025
17130
  function detectPlatform(pluginDir) {
17026
17131
  const checks = [
17027
17132
  { dir: ".claude-plugin", platform: "claude-code" },
@@ -17030,12 +17135,12 @@ function detectPlatform(pluginDir) {
17030
17135
  ];
17031
17136
  for (const check of checks) {
17032
17137
  const manifestPath = resolve18(pluginDir, check.dir, "plugin.json");
17033
- if (existsSync24(manifestPath)) {
17138
+ if (existsSync25(manifestPath)) {
17034
17139
  return { platform: check.platform, manifestPath };
17035
17140
  }
17036
17141
  }
17037
17142
  const pkgPath = resolve18(pluginDir, "package.json");
17038
- if (existsSync24(pkgPath)) {
17143
+ if (existsSync25(pkgPath)) {
17039
17144
  try {
17040
17145
  const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
17041
17146
  const deps = {
@@ -17088,7 +17193,7 @@ function parseMcp(pluginDir, detection) {
17088
17193
  } catch {
17089
17194
  }
17090
17195
  for (const mcpPath of mcpPaths) {
17091
- if (!existsSync24(mcpPath)) continue;
17196
+ if (!existsSync25(mcpPath)) continue;
17092
17197
  try {
17093
17198
  const raw = JSON.parse(readFileSync12(mcpPath, "utf-8"));
17094
17199
  const servers = raw.mcpServers ?? raw;
@@ -17182,7 +17287,7 @@ function parseHooks(pluginDir, detection) {
17182
17287
  } catch {
17183
17288
  }
17184
17289
  for (const hooksPath of hooksPaths) {
17185
- if (!existsSync24(hooksPath)) continue;
17290
+ if (!existsSync25(hooksPath)) continue;
17186
17291
  try {
17187
17292
  const raw = JSON.parse(readFileSync12(hooksPath, "utf-8"));
17188
17293
  const hooksObj = raw.hooks ?? raw;
@@ -17231,7 +17336,7 @@ function findInstructions(pluginDir) {
17231
17336
  ];
17232
17337
  for (const candidate of candidates) {
17233
17338
  const filePath = resolve18(pluginDir, candidate);
17234
- if (existsSync24(filePath)) {
17339
+ if (existsSync25(filePath)) {
17235
17340
  return `./${candidate}`;
17236
17341
  }
17237
17342
  }
@@ -17256,7 +17361,7 @@ function detectCanonicalSourcePaths(pluginDir) {
17256
17361
  for (const bucket of Object.keys(CANONICAL_SOURCE_CANDIDATES)) {
17257
17362
  for (const candidate of CANONICAL_SOURCE_CANDIDATES[bucket]) {
17258
17363
  const normalized = stripRelativePrefix(candidate);
17259
- if (!existsSync24(resolve18(pluginDir, normalized))) continue;
17364
+ if (!existsSync25(resolve18(pluginDir, normalized))) continue;
17260
17365
  result[bucket] = normalizeRelativeDir(normalized);
17261
17366
  break;
17262
17367
  }
@@ -17272,7 +17377,7 @@ function detectPassthroughDirs(pluginDir, mcp) {
17272
17377
  if (!match?.[1]) continue;
17273
17378
  const dirName = match[1];
17274
17379
  const dirPath = resolve18(pluginDir, dirName);
17275
- if (existsSync24(dirPath)) {
17380
+ if (existsSync25(dirPath)) {
17276
17381
  passthrough.add(`./${dirName}/`);
17277
17382
  }
17278
17383
  }
@@ -17373,7 +17478,7 @@ function inferPermissionsFromMigratedSkills(pluginDir, sourcePaths) {
17373
17478
  for (const entry of entries) {
17374
17479
  if (!entry.isDirectory()) continue;
17375
17480
  const skillPath = resolve18(skillsDir, entry.name, "SKILL.md");
17376
- if (!existsSync24(skillPath)) continue;
17481
+ if (!existsSync25(skillPath)) continue;
17377
17482
  const content = readFileSync12(skillPath, "utf-8");
17378
17483
  const inferredRules = extractAllowedTools(content).map(normalizeMigratedAllowedTool).filter((rule) => Boolean(rule));
17379
17484
  if (inferredRules.length === 0) continue;
@@ -17418,7 +17523,7 @@ function readMigratedSkills(pluginDir, sourcePaths) {
17418
17523
  const skillPath = resolve18(skillsDir, dirName, "SKILL.md");
17419
17524
  let title = titleCaseFromDirName(dirName);
17420
17525
  let description;
17421
- if (existsSync24(skillPath)) {
17526
+ if (existsSync25(skillPath)) {
17422
17527
  const content = readFileSync12(skillPath, "utf-8");
17423
17528
  title = extractFrontmatterField(content, "name") ?? firstHeading3(content) ?? title;
17424
17529
  description = extractFrontmatterField(content, "description");
@@ -17452,13 +17557,13 @@ var HOST_NATIVE_SKILL_FRONTMATTER_KEYS = /* @__PURE__ */ new Set([
17452
17557
  "allowed-tools"
17453
17558
  ]);
17454
17559
  function sanitizeMigratedSkillFrontmatter(outputDir) {
17455
- if (!existsSync24(resolve18(outputDir, "skills"))) return;
17560
+ if (!existsSync25(resolve18(outputDir, "skills"))) return;
17456
17561
  const skillsDir = resolve18(outputDir, "skills");
17457
17562
  const entries = readdirSync10(skillsDir, { withFileTypes: true });
17458
17563
  for (const entry of entries) {
17459
17564
  if (!entry.isDirectory()) continue;
17460
17565
  const skillPath = resolve18(skillsDir, entry.name, "SKILL.md");
17461
- if (!existsSync24(skillPath)) continue;
17566
+ if (!existsSync25(skillPath)) continue;
17462
17567
  const content = readFileSync12(skillPath, "utf-8");
17463
17568
  const lines = content.split(/\r?\n/);
17464
17569
  if (lines[0]?.trim() !== "---") continue;
@@ -17621,11 +17726,11 @@ function walkMarkdownFiles3(dir) {
17621
17726
  return files;
17622
17727
  }
17623
17728
  function normalizeMigratedOpenCodeAgents(destDir) {
17624
- if (!existsSync24(destDir)) return [];
17729
+ if (!existsSync25(destDir)) return [];
17625
17730
  const normalized = [];
17626
17731
  for (const filePath of walkMarkdownFiles3(destDir)) {
17627
17732
  if (normalizeMigratedOpenCodeAgentFile(filePath)) {
17628
- normalized.push(relative10(destDir, filePath).replace(/\\/g, "/"));
17733
+ normalized.push(relative11(destDir, filePath).replace(/\\/g, "/"));
17629
17734
  }
17630
17735
  }
17631
17736
  return normalized.sort();
@@ -17710,7 +17815,7 @@ function buildMigratedScaffoldMetadata(result, outputDir) {
17710
17815
  if (!result.sourcePaths[dir]) return [];
17711
17816
  const baseDir = dir;
17712
17817
  const dirPath = resolve18(outputDir, baseDir);
17713
- if (!existsSync24(dirPath)) return [];
17818
+ if (!existsSync25(dirPath)) return [];
17714
17819
  const entries = readdirSync10(dirPath, { withFileTypes: true });
17715
17820
  const files = [];
17716
17821
  for (const entry of entries) {
@@ -17777,7 +17882,7 @@ function copyDirectories(pluginDir, outputDir, sourcePaths, passthrough) {
17777
17882
  const normalizedSource = stripRelativePrefix(sourcePath);
17778
17883
  const src = resolve18(pluginDir, normalizedSource);
17779
17884
  const dest = resolve18(outputDir, dir);
17780
- if (existsSync24(dest)) {
17885
+ if (existsSync25(dest)) {
17781
17886
  console.log(` skip ./${dir}/ (already exists)`);
17782
17887
  continue;
17783
17888
  }
@@ -17794,7 +17899,7 @@ function copyDirectories(pluginDir, outputDir, sourcePaths, passthrough) {
17794
17899
  const normalized = entry.replace(/^\.\//, "").replace(/\/$/, "");
17795
17900
  const src = resolve18(pluginDir, normalized);
17796
17901
  const dest = resolve18(outputDir, normalized);
17797
- if (existsSync24(dest)) {
17902
+ if (existsSync25(dest)) {
17798
17903
  console.log(` skip ./${normalized}/ (already exists)`);
17799
17904
  continue;
17800
17905
  }
@@ -17940,7 +18045,7 @@ function quote(s) {
17940
18045
  async function migrate(inputPath) {
17941
18046
  const pluginDir = resolve18(inputPath);
17942
18047
  const outputDir = process.cwd();
17943
- if (!existsSync24(pluginDir)) {
18048
+ if (!existsSync25(pluginDir)) {
17944
18049
  console.error(`Error: Path does not exist: ${pluginDir}`);
17945
18050
  process.exit(1);
17946
18051
  }
@@ -18007,7 +18112,7 @@ async function migrate(inputPath) {
18007
18112
  };
18008
18113
  const configContent = generateConfigTs(result);
18009
18114
  const configPath = resolve18(outputDir, "pluxx.config.ts");
18010
- if (existsSync24(configPath)) {
18115
+ if (existsSync25(configPath)) {
18011
18116
  console.error(`
18012
18117
  Error: pluxx.config.ts already exists in ${outputDir}`);
18013
18118
  console.error("Remove it first or run from a different directory.");
@@ -18024,7 +18129,7 @@ Generated pluxx.config.ts`);
18024
18129
  if (instructions) {
18025
18130
  const srcInstr = resolve18(pluginDir, instructions);
18026
18131
  const destInstr = resolve18(outputDir, instructions);
18027
- if (!existsSync24(destInstr)) {
18132
+ if (!existsSync25(destInstr)) {
18028
18133
  const content = readFileSync12(srcInstr, "utf-8");
18029
18134
  await writeTextFile(destInstr, content);
18030
18135
  console.log(`Copied: ${instructions}`);
@@ -18060,7 +18165,7 @@ Generated pluxx.config.ts`);
18060
18165
 
18061
18166
  // src/cli/mcp-proxy.ts
18062
18167
  import { mkdirSync as mkdirSync6, readFileSync as readFileSync13 } from "fs";
18063
- import { dirname as dirname7, resolve as resolve19 } from "path";
18168
+ import { dirname as dirname8, resolve as resolve19 } from "path";
18064
18169
  import * as readline3 from "readline";
18065
18170
  function usage() {
18066
18171
  return [
@@ -18137,7 +18242,7 @@ async function loadReplayTape(filepath) {
18137
18242
  }
18138
18243
  async function writeTape(filepath, tape) {
18139
18244
  const absolutePath = resolve19(process.cwd(), filepath);
18140
- mkdirSync6(dirname7(absolutePath), { recursive: true });
18245
+ mkdirSync6(dirname8(absolutePath), { recursive: true });
18141
18246
  await writeTextFile(absolutePath, `${JSON.stringify(tape, null, 2)}
18142
18247
  `);
18143
18248
  }
@@ -18308,7 +18413,7 @@ var PromptCancelledError = class extends Error {
18308
18413
  }
18309
18414
  };
18310
18415
  function ask(question) {
18311
- return new Promise((resolve24, reject) => {
18416
+ return new Promise((resolve25, reject) => {
18312
18417
  const rl = readline4.createInterface({
18313
18418
  input: process.stdin,
18314
18419
  output: process.stdout
@@ -18336,7 +18441,7 @@ function ask(question) {
18336
18441
  rl.once("close", onClose);
18337
18442
  rl.question(question, (answer) => {
18338
18443
  settle(() => {
18339
- resolve24(answer);
18444
+ resolve25(answer);
18340
18445
  rl.close();
18341
18446
  });
18342
18447
  });
@@ -19311,13 +19416,13 @@ ${c2}
19311
19416
  } }).prompt();
19312
19417
 
19313
19418
  // src/cli/index.ts
19314
- import { basename as basename8, resolve as resolve23 } from "path";
19419
+ import { basename as basename8, resolve as resolve24 } from "path";
19315
19420
  import { mkdir as mkdir4, mkdtemp as mkdtemp3, rm as rm4 } from "fs/promises";
19316
19421
  import { tmpdir as tmpdir5 } from "os";
19317
19422
  import { spawn as spawn4, spawnSync as spawnSync3 } from "child_process";
19318
19423
 
19319
19424
  // src/cli/publish.ts
19320
- import { chmodSync, existsSync as existsSync25, mkdtempSync as mkdtempSync2, readFileSync as readFileSync14, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "fs";
19425
+ import { chmodSync, existsSync as existsSync26, mkdtempSync as mkdtempSync2, readFileSync as readFileSync14, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "fs";
19321
19426
  import { createHash } from "crypto";
19322
19427
  import { resolve as resolve20 } from "path";
19323
19428
  import { spawnSync as spawnSync2 } from "child_process";
@@ -19352,7 +19457,7 @@ function resolveRequestedChannels(options) {
19352
19457
  }
19353
19458
  function getBuiltTargets(rootDir, config) {
19354
19459
  return config.targets.filter(
19355
- (platform) => existsSync25(resolve20(rootDir, config.outDir, platform))
19460
+ (platform) => existsSync26(resolve20(rootDir, config.outDir, platform))
19356
19461
  );
19357
19462
  }
19358
19463
  function getArchiveAssetName(pluginName, platform, version, variant) {
@@ -19411,7 +19516,7 @@ function buildReleaseAssets(rootDir, config, version, targets) {
19411
19516
  function readNpmPackageName(rootDir, config) {
19412
19517
  const packageDir = resolve20(rootDir, config.outDir, "opencode");
19413
19518
  const packageJsonPath = resolve20(packageDir, "package.json");
19414
- if (!existsSync25(packageJsonPath)) {
19519
+ if (!existsSync26(packageJsonPath)) {
19415
19520
  return {};
19416
19521
  }
19417
19522
  try {
@@ -19464,7 +19569,7 @@ function collectChecks(args2) {
19464
19569
  if (args2.npmEnabled) {
19465
19570
  checks.push({
19466
19571
  name: "npm-package-ready",
19467
- ok: Boolean(args2.packageDir && existsSync25(resolve20(args2.packageDir, "package.json")) && args2.packageName),
19572
+ ok: Boolean(args2.packageDir && existsSync26(resolve20(args2.packageDir, "package.json")) && args2.packageName),
19468
19573
  code: "npm-package-ready",
19469
19574
  detail: args2.packageDir ? `OpenCode package dir: ${args2.packageDir}` : "No npm-backed target package found."
19470
19575
  });
@@ -20271,7 +20376,7 @@ function printJson(value) {
20271
20376
  }
20272
20377
 
20273
20378
  // src/cli/verify-install.ts
20274
- import { existsSync as existsSync26 } from "fs";
20379
+ import { existsSync as existsSync27 } from "fs";
20275
20380
  import { resolve as resolve21 } from "path";
20276
20381
  function buildCheckFromReport(target, pluginName, report) {
20277
20382
  const consumerPath = resolveInstalledConsumerPath(target, pluginName);
@@ -20280,7 +20385,7 @@ function buildCheckFromReport(target, pluginName, report) {
20280
20385
  installPath: target.pluginDir,
20281
20386
  consumerPath,
20282
20387
  built: target.built,
20283
- installed: existsSync26(consumerPath),
20388
+ installed: existsSync27(consumerPath),
20284
20389
  ok: report.errors === 0,
20285
20390
  errors: report.errors,
20286
20391
  warnings: report.warnings,
@@ -20319,7 +20424,7 @@ function printVerifyInstallResult(result) {
20319
20424
  }
20320
20425
 
20321
20426
  // src/cli/behavioral.ts
20322
- import { existsSync as existsSync27, readFileSync as readFileSync15 } from "fs";
20427
+ import { existsSync as existsSync28, readFileSync as readFileSync15 } from "fs";
20323
20428
  import { mkdtemp as mkdtemp2, rm as rm3 } from "fs/promises";
20324
20429
  import { tmpdir as tmpdir4 } from "os";
20325
20430
  import { resolve as resolve22 } from "path";
@@ -20356,7 +20461,7 @@ function loadBehavioralCases(rootDir, targets, promptOverride) {
20356
20461
  }];
20357
20462
  }
20358
20463
  const filePath = resolve22(rootDir, BEHAVIORAL_CONFIG_PATH);
20359
- if (!existsSync27(filePath)) {
20464
+ if (!existsSync28(filePath)) {
20360
20465
  throw new Error(
20361
20466
  `No behavioral smoke config found at ${BEHAVIORAL_CONFIG_PATH}. Add that file or pass --behavioral-prompt to define a real example query.`
20362
20467
  );
@@ -20471,7 +20576,7 @@ async function executeBehavioralCommand(platform, command2, cwd) {
20471
20576
  child.on("close", (code) => {
20472
20577
  const stdout = Buffer.concat(stdoutChunks).toString("utf-8");
20473
20578
  const stderr = Buffer.concat(stderrChunks).toString("utf-8");
20474
- const codexMessage = codexLastMessagePath && existsSync27(codexLastMessagePath) ? readFileSync15(codexLastMessagePath, "utf-8") : "";
20579
+ const codexMessage = codexLastMessagePath && existsSync28(codexLastMessagePath) ? readFileSync15(codexLastMessagePath, "utf-8") : "";
20475
20580
  resolvePromise({
20476
20581
  exitCode: code ?? 1,
20477
20582
  response: codexMessage.trim() || stdout.trim() || stderr.trim()
@@ -20533,6 +20638,396 @@ function shellQuote2(value) {
20533
20638
  return `'${value.replace(/'/g, `'\\''`)}'`;
20534
20639
  }
20535
20640
 
20641
+ // src/cli/discover-installed-mcp.ts
20642
+ import { existsSync as existsSync29, readFileSync as readFileSync16 } from "fs";
20643
+ import { homedir as homedir2 } from "os";
20644
+ import { resolve as resolve23 } from "path";
20645
+ var INSTALLED_MCP_HOSTS = ["claude-code", "cursor", "codex", "opencode"];
20646
+ function discoverInstalledMcpServers(options = {}) {
20647
+ const rootDir = options.rootDir ?? process.cwd();
20648
+ const homeDir = options.homeDir ?? homedir2();
20649
+ const hostSet = new Set(options.hosts ?? INSTALLED_MCP_HOSTS);
20650
+ const results = [];
20651
+ const seen = /* @__PURE__ */ new Set();
20652
+ for (const candidate of installedMcpFileCandidates(rootDir, homeDir)) {
20653
+ if (!hostSet.has(candidate.host) || !existsSync29(candidate.path)) continue;
20654
+ const parsed = candidate.parser === "json" ? parseJsonMcpFile(candidate.path, candidate.host) : parseCodexTomlMcpFile(candidate.path);
20655
+ for (const discovered of parsed) {
20656
+ const key = `${discovered.host}:${discovered.serverName}:${discovered.sourcePath}`;
20657
+ if (seen.has(key)) continue;
20658
+ seen.add(key);
20659
+ results.push(discovered);
20660
+ }
20661
+ }
20662
+ return results.sort((a, b) => {
20663
+ const hostOrder = INSTALLED_MCP_HOSTS.indexOf(a.host) - INSTALLED_MCP_HOSTS.indexOf(b.host);
20664
+ if (hostOrder !== 0) return hostOrder;
20665
+ return a.serverName.localeCompare(b.serverName);
20666
+ });
20667
+ }
20668
+ function resolveInstalledMcpSelector(selector, candidates) {
20669
+ const trimmed = selector.trim();
20670
+ if (!trimmed) {
20671
+ throw new Error("Provide an installed MCP selector such as `exa` or `codex:exa`.");
20672
+ }
20673
+ const exactId = candidates.filter((candidate) => candidate.id === trimmed);
20674
+ if (exactId.length === 1) return exactId[0];
20675
+ const hostMatch = trimmed.match(/^([^:]+):(.+)$/);
20676
+ if (hostMatch) {
20677
+ const host = normalizeInstalledMcpHost(hostMatch[1]);
20678
+ const serverName = hostMatch[2];
20679
+ const matches = candidates.filter((candidate) => candidate.host === host && candidate.serverName === serverName);
20680
+ if (matches.length === 1) return matches[0];
20681
+ if (matches.length > 1) {
20682
+ throw new Error(`Installed MCP selector "${trimmed}" matched multiple config files. Use one of: ${matches.map((match) => match.id).join(", ")}`);
20683
+ }
20684
+ }
20685
+ const nameMatches = candidates.filter((candidate) => candidate.serverName === trimmed);
20686
+ if (nameMatches.length === 1) return nameMatches[0];
20687
+ if (nameMatches.length > 1) {
20688
+ throw new Error(`Installed MCP selector "${trimmed}" is ambiguous. Use one of: ${nameMatches.map((match) => match.id).join(", ")}`);
20689
+ }
20690
+ const available = candidates.map((candidate) => candidate.id).join(", ") || "none";
20691
+ throw new Error(`No installed MCP named "${trimmed}" was found. Available installed MCPs: ${available}`);
20692
+ }
20693
+ function normalizeInstalledMcpHost(value) {
20694
+ const normalized = value.trim().toLowerCase();
20695
+ const aliases = {
20696
+ claude: "claude-code",
20697
+ "claude-code": "claude-code",
20698
+ cursor: "cursor",
20699
+ codex: "codex",
20700
+ open: "opencode",
20701
+ opencode: "opencode"
20702
+ };
20703
+ const host = aliases[normalized];
20704
+ if (!host) {
20705
+ throw new Error(`Installed MCP host must be one of: ${INSTALLED_MCP_HOSTS.join(", ")}`);
20706
+ }
20707
+ return host;
20708
+ }
20709
+ function formatInstalledMcpSource(discovered) {
20710
+ const transport = discovered.server.transport;
20711
+ const endpoint = transport === "stdio" ? [discovered.server.command, ...discovered.server.args ?? []].join(" ") : discovered.server.url;
20712
+ return `${discovered.id} (${transport}: ${endpoint})`;
20713
+ }
20714
+ function installedMcpFileCandidates(rootDir, homeDir) {
20715
+ return [
20716
+ { host: "claude-code", path: resolve23(rootDir, ".mcp.json"), parser: "json" },
20717
+ { host: "claude-code", path: resolve23(rootDir, ".claude-plugin/plugin.json"), parser: "json" },
20718
+ { host: "claude-code", path: resolve23(rootDir, ".claude/settings.json"), parser: "json" },
20719
+ { host: "claude-code", path: resolve23(rootDir, ".claude/settings.local.json"), parser: "json" },
20720
+ { host: "claude-code", path: resolve23(homeDir, ".claude/settings.json"), parser: "json" },
20721
+ { host: "claude-code", path: resolve23(homeDir, ".claude/settings.local.json"), parser: "json" },
20722
+ { host: "claude-code", path: resolve23(homeDir, ".claude.json"), parser: "json" },
20723
+ { host: "cursor", path: resolve23(rootDir, "mcp.json"), parser: "json" },
20724
+ { host: "cursor", path: resolve23(rootDir, ".cursor/mcp.json"), parser: "json" },
20725
+ { host: "cursor", path: resolve23(homeDir, ".cursor/mcp.json"), parser: "json" },
20726
+ { host: "codex", path: resolve23(rootDir, ".mcp.json"), parser: "json" },
20727
+ { host: "codex", path: resolve23(rootDir, ".codex/config.toml"), parser: "toml" },
20728
+ { host: "codex", path: resolve23(homeDir, ".codex/config.toml"), parser: "toml" },
20729
+ { host: "opencode", path: resolve23(rootDir, "opencode.json"), parser: "json" },
20730
+ { host: "opencode", path: resolve23(rootDir, ".opencode.json"), parser: "json" },
20731
+ { host: "opencode", path: resolve23(homeDir, ".config/opencode/opencode.json"), parser: "json" }
20732
+ ];
20733
+ }
20734
+ function parseJsonMcpFile(path, host) {
20735
+ try {
20736
+ const raw = JSON.parse(readFileSync16(path, "utf-8"));
20737
+ const servers = extractJsonMcpServers(raw, path, host);
20738
+ return Object.entries(servers).flatMap(([serverName, config]) => {
20739
+ const normalized = normalizeCommonMcpServer(config, host);
20740
+ if (!normalized) return [];
20741
+ return [toDiscovered(host, serverName, path, normalized.server, normalized.warnings)];
20742
+ });
20743
+ } catch {
20744
+ return [];
20745
+ }
20746
+ }
20747
+ function extractJsonMcpServers(raw, path, host) {
20748
+ if (host === "opencode" && raw.mcp && typeof raw.mcp === "object") {
20749
+ return raw.mcp;
20750
+ }
20751
+ if (raw.mcpServers && typeof raw.mcpServers === "object") {
20752
+ return raw.mcpServers;
20753
+ }
20754
+ if (host === "claude-code" && raw.projects && typeof raw.projects === "object") {
20755
+ const projectServers = {};
20756
+ for (const projectConfig of Object.values(raw.projects)) {
20757
+ if (!projectConfig || typeof projectConfig !== "object") continue;
20758
+ const mcpServers = projectConfig.mcpServers;
20759
+ if (!mcpServers || typeof mcpServers !== "object") continue;
20760
+ Object.assign(projectServers, mcpServers);
20761
+ }
20762
+ return projectServers;
20763
+ }
20764
+ if (typeof raw.mcpServers === "string") return {};
20765
+ if (path.endsWith("mcp.json") || path.endsWith(".mcp.json")) {
20766
+ return raw;
20767
+ }
20768
+ return {};
20769
+ }
20770
+ function parseCodexTomlMcpFile(path) {
20771
+ const text = readFileSync16(path, "utf-8");
20772
+ const servers = {};
20773
+ let currentServer;
20774
+ let currentSubtable;
20775
+ for (const rawLine of text.split(/\r?\n/)) {
20776
+ const line = stripTomlComment(rawLine).trim();
20777
+ if (!line) continue;
20778
+ const section = line.match(/^\[mcp_servers\.([A-Za-z0-9_.-]+)(?:\.([A-Za-z0-9_.-]+))?\]$/);
20779
+ if (section) {
20780
+ currentServer = section[1];
20781
+ currentSubtable = section[2];
20782
+ servers[currentServer] ??= {};
20783
+ if (currentSubtable) {
20784
+ const existing = servers[currentServer][currentSubtable];
20785
+ if (!existing || typeof existing !== "object" || Array.isArray(existing)) {
20786
+ servers[currentServer][currentSubtable] = {};
20787
+ }
20788
+ }
20789
+ continue;
20790
+ }
20791
+ if (!currentServer) continue;
20792
+ const assignment = line.match(/^([A-Za-z0-9_-]+)\s*=\s*(.+)$/);
20793
+ if (!assignment) continue;
20794
+ const key = assignment[1];
20795
+ const value = parseTomlValue(assignment[2].trim());
20796
+ if (currentSubtable) {
20797
+ const table = servers[currentServer][currentSubtable];
20798
+ table[key] = value;
20799
+ } else {
20800
+ servers[currentServer][key] = value;
20801
+ }
20802
+ }
20803
+ return Object.entries(servers).flatMap(([serverName, config]) => {
20804
+ const normalized = normalizeCommonMcpServer(config, "codex");
20805
+ if (!normalized) return [];
20806
+ return [toDiscovered("codex", serverName, path, normalized.server, normalized.warnings)];
20807
+ });
20808
+ }
20809
+ function normalizeCommonMcpServer(config, host) {
20810
+ if (!config || typeof config !== "object") return null;
20811
+ const cfg = config;
20812
+ const warnings = [];
20813
+ const auth = inferAuth(cfg, warnings);
20814
+ const remoteUrl = firstString(cfg.url, cfg.endpoint);
20815
+ if (remoteUrl) {
20816
+ const server = cfg.type === "sse" || cfg.transport === "sse" ? {
20817
+ transport: "sse",
20818
+ url: remoteUrl,
20819
+ ...auth ? { auth } : {}
20820
+ } : {
20821
+ transport: "http",
20822
+ url: remoteUrl,
20823
+ ...auth ? { auth } : {}
20824
+ };
20825
+ return { server, warnings };
20826
+ }
20827
+ const command2 = normalizeCommand(cfg.command, host);
20828
+ if (!command2) return null;
20829
+ const args2 = normalizeArgs(cfg.command, cfg.args, host);
20830
+ const env = normalizeEnv(cfg.env, warnings);
20831
+ return {
20832
+ server: {
20833
+ transport: "stdio",
20834
+ command: command2,
20835
+ ...args2.length > 0 ? { args: args2 } : {},
20836
+ ...Object.keys(env).length > 0 ? { env } : {},
20837
+ ...auth ? { auth } : {}
20838
+ },
20839
+ warnings
20840
+ };
20841
+ }
20842
+ function normalizeCommand(value, host) {
20843
+ if (typeof value === "string") return value;
20844
+ if (host === "opencode" && Array.isArray(value) && typeof value[0] === "string") {
20845
+ return value[0];
20846
+ }
20847
+ return void 0;
20848
+ }
20849
+ function normalizeArgs(command2, args2, host) {
20850
+ if (host === "opencode" && Array.isArray(command2)) {
20851
+ return command2.slice(1).map(String);
20852
+ }
20853
+ return normalizeStringArray(args2);
20854
+ }
20855
+ function normalizeStringArray(value) {
20856
+ if (!Array.isArray(value)) return [];
20857
+ return value.map(String);
20858
+ }
20859
+ function normalizeEnv(value, warnings) {
20860
+ if (!value || typeof value !== "object" || Array.isArray(value)) return {};
20861
+ const env = {};
20862
+ for (const [key, rawValue] of Object.entries(value)) {
20863
+ if (typeof rawValue !== "string") continue;
20864
+ const placeholder = envPlaceholder(rawValue);
20865
+ if (placeholder) {
20866
+ env[key] = placeholder;
20867
+ continue;
20868
+ }
20869
+ if (looksSecretKey(key)) {
20870
+ env[key] = `\${${key}}`;
20871
+ warnings.push(`Literal secret-like env value for ${key} was replaced with \${${key}}.`);
20872
+ continue;
20873
+ }
20874
+ env[key] = rawValue;
20875
+ }
20876
+ return env;
20877
+ }
20878
+ function inferAuth(cfg, warnings) {
20879
+ const bearerTokenEnv = firstString(cfg.bearer_token_env_var, cfg.bearerTokenEnvVar);
20880
+ if (bearerTokenEnv) {
20881
+ return {
20882
+ type: "bearer",
20883
+ envVar: bearerTokenEnv,
20884
+ headerName: "Authorization",
20885
+ headerTemplate: "Bearer ${value}"
20886
+ };
20887
+ }
20888
+ const envHttpHeaders = cfg.env_http_headers ?? cfg.envHttpHeaders;
20889
+ if (envHttpHeaders && typeof envHttpHeaders === "object" && !Array.isArray(envHttpHeaders)) {
20890
+ const [headerName, envVar] = Object.entries(envHttpHeaders).find(([, value]) => typeof value === "string") ?? [];
20891
+ if (typeof headerName === "string" && typeof envVar === "string") {
20892
+ return {
20893
+ type: headerName.toLowerCase() === "authorization" ? "bearer" : "header",
20894
+ envVar,
20895
+ headerName,
20896
+ headerTemplate: headerName.toLowerCase() === "authorization" ? "Bearer ${value}" : "${value}"
20897
+ };
20898
+ }
20899
+ }
20900
+ const headers = cfg.headers ?? cfg.http_headers ?? cfg.httpHeaders;
20901
+ if (!headers || typeof headers !== "object" || Array.isArray(headers)) return void 0;
20902
+ const headerEntries = Object.entries(headers);
20903
+ for (const [headerName, rawValue] of headerEntries) {
20904
+ if (typeof rawValue !== "string") continue;
20905
+ const envVar = extractEnvVar(rawValue);
20906
+ if (envVar) {
20907
+ return {
20908
+ type: headerName.toLowerCase() === "authorization" ? "bearer" : "header",
20909
+ envVar,
20910
+ headerName,
20911
+ headerTemplate: rawValue.replace(envReferencePattern(envVar), "${value}")
20912
+ };
20913
+ }
20914
+ if (looksSecretValue(rawValue)) {
20915
+ warnings.push(`Literal ${headerName} header value was not copied. Re-run init with --auth-env if this server needs auth.`);
20916
+ }
20917
+ }
20918
+ return void 0;
20919
+ }
20920
+ function toDiscovered(host, serverName, sourcePath, server, warnings) {
20921
+ return {
20922
+ id: `${host}:${serverName}`,
20923
+ host,
20924
+ serverName,
20925
+ sourcePath,
20926
+ server,
20927
+ warnings
20928
+ };
20929
+ }
20930
+ function firstString(...values) {
20931
+ for (const value of values) {
20932
+ if (typeof value === "string" && value.trim()) return value.trim();
20933
+ }
20934
+ return void 0;
20935
+ }
20936
+ function envPlaceholder(value) {
20937
+ const envVar = extractEnvVar(value);
20938
+ return envVar ? `\${${envVar}}` : void 0;
20939
+ }
20940
+ function extractEnvVar(value) {
20941
+ const match = value.match(/\$\{(?:env:)?([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*)/);
20942
+ return match?.[1] ?? match?.[2];
20943
+ }
20944
+ function envReferencePattern(envVar) {
20945
+ return new RegExp(`\\$\\{(?:env:)?${escapeRegExp2(envVar)}\\}|\\$${escapeRegExp2(envVar)}`);
20946
+ }
20947
+ function escapeRegExp2(value) {
20948
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
20949
+ }
20950
+ function looksSecretKey(value) {
20951
+ return /(api|auth|secret|token|key|password|credential)/i.test(value);
20952
+ }
20953
+ function looksSecretValue(value) {
20954
+ return value.length >= 16 && !extractEnvVar(value);
20955
+ }
20956
+ function stripTomlComment(line) {
20957
+ let inString = false;
20958
+ let quote2 = "";
20959
+ for (let i = 0; i < line.length; i += 1) {
20960
+ const char = line[i];
20961
+ if ((char === '"' || char === "'") && line[i - 1] !== "\\") {
20962
+ if (!inString) {
20963
+ inString = true;
20964
+ quote2 = char;
20965
+ } else if (quote2 === char) {
20966
+ inString = false;
20967
+ quote2 = "";
20968
+ }
20969
+ continue;
20970
+ }
20971
+ if (char === "#" && !inString) return line.slice(0, i);
20972
+ }
20973
+ return line;
20974
+ }
20975
+ function parseTomlValue(value) {
20976
+ if (value.startsWith('"') && value.endsWith('"')) return unquoteTomlString(value);
20977
+ if (value.startsWith("'") && value.endsWith("'")) return value.slice(1, -1);
20978
+ if (value.startsWith("[") && value.endsWith("]")) {
20979
+ const inner = value.slice(1, -1).trim();
20980
+ if (!inner) return [];
20981
+ return splitTomlList(inner).map((part) => parseTomlValue(part.trim()));
20982
+ }
20983
+ if (value.startsWith("{") && value.endsWith("}")) {
20984
+ const inner = value.slice(1, -1).trim();
20985
+ const result = {};
20986
+ if (!inner) return result;
20987
+ for (const part of splitTomlList(inner)) {
20988
+ const assignment = part.match(/^([A-Za-z0-9_-]+)\s*=\s*(.+)$/);
20989
+ if (!assignment) continue;
20990
+ result[assignment[1]] = parseTomlValue(assignment[2].trim());
20991
+ }
20992
+ return result;
20993
+ }
20994
+ if (value === "true") return true;
20995
+ if (value === "false") return false;
20996
+ return value;
20997
+ }
20998
+ function splitTomlList(value) {
20999
+ const parts = [];
21000
+ let current = "";
21001
+ let inString = false;
21002
+ let quote2 = "";
21003
+ let braceDepth = 0;
21004
+ for (let i = 0; i < value.length; i += 1) {
21005
+ const char = value[i];
21006
+ if ((char === '"' || char === "'") && value[i - 1] !== "\\") {
21007
+ if (!inString) {
21008
+ inString = true;
21009
+ quote2 = char;
21010
+ } else if (quote2 === char) {
21011
+ inString = false;
21012
+ quote2 = "";
21013
+ }
21014
+ }
21015
+ if (!inString && char === "{") braceDepth += 1;
21016
+ if (!inString && char === "}") braceDepth -= 1;
21017
+ if (!inString && braceDepth === 0 && char === ",") {
21018
+ parts.push(current);
21019
+ current = "";
21020
+ continue;
21021
+ }
21022
+ current += char;
21023
+ }
21024
+ if (current.trim()) parts.push(current);
21025
+ return parts;
21026
+ }
21027
+ function unquoteTomlString(value) {
21028
+ return value.slice(1, -1).replace(/\\"/g, '"').replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\\\/g, "\\");
21029
+ }
21030
+
20536
21031
  // src/cli/index.ts
20537
21032
  var CLI_PACKAGE_NAME = "@orchid-labs/pluxx";
20538
21033
  var rawArgs = process.argv.slice(2);
@@ -20583,6 +21078,9 @@ async function main() {
20583
21078
  case "mcp":
20584
21079
  await runMcp();
20585
21080
  break;
21081
+ case "discover-mcp":
21082
+ await runDiscoverMcp();
21083
+ break;
20586
21084
  case "autopilot":
20587
21085
  await runAutopilot();
20588
21086
  break;
@@ -20596,6 +21094,10 @@ async function main() {
20596
21094
  await runVerifyInstall();
20597
21095
  break;
20598
21096
  case "publish":
21097
+ if (isHelpRequested(args.slice(1))) {
21098
+ printPublishHelp();
21099
+ break;
21100
+ }
20599
21101
  await runPublishCommand();
20600
21102
  break;
20601
21103
  case "uninstall":
@@ -20634,9 +21136,12 @@ function normalizeTopLevelArgs(input) {
20634
21136
  }
20635
21137
  return input;
20636
21138
  }
21139
+ function isHelpRequested(input) {
21140
+ return input.includes("--help") || input.includes("-h");
21141
+ }
20637
21142
  function getCliPackageVersion() {
20638
21143
  const packageJsonPath = new URL("../../package.json", import.meta.url);
20639
- const raw = JSON.parse(readFileSync16(packageJsonPath, "utf-8"));
21144
+ const raw = JSON.parse(readFileSync17(packageJsonPath, "utf-8"));
20640
21145
  if (typeof raw.version !== "string" || raw.version.trim() === "") {
20641
21146
  throw new Error("Unable to determine the installed pluxx version from package.json.");
20642
21147
  }
@@ -21296,7 +21801,7 @@ async function planInitContextArtifactFiles(rootDir, contextPack) {
21296
21801
  return plannedFiles;
21297
21802
  }
21298
21803
  async function planAuxiliaryFile(rootDir, relativePath, content) {
21299
- const filePath = resolve23(rootDir, relativePath);
21804
+ const filePath = resolve24(rootDir, relativePath);
21300
21805
  const action = await planTextFileAction(filePath, content);
21301
21806
  return {
21302
21807
  relativePath,
@@ -21326,6 +21831,7 @@ function formatMcpDiscoverySummary(introspection) {
21326
21831
  function parseInitFromMcpOptions(rawArgs2, initialName, initialSource) {
21327
21832
  return {
21328
21833
  source: initialSource ?? readOption2(rawArgs2, "--from-mcp"),
21834
+ installedMcp: readOption2(rawArgs2, "--from-installed-mcp"),
21329
21835
  assumeDefaults: rawArgs2.includes("--yes"),
21330
21836
  name: readOption2(rawArgs2, "--name") ?? initialName,
21331
21837
  author: readOption2(rawArgs2, "--author"),
@@ -21354,11 +21860,49 @@ function toKebabCase3(value) {
21354
21860
  function toTsString(value) {
21355
21861
  return JSON.stringify(value);
21356
21862
  }
21863
+ function parseInstalledMcpHosts(rawArgs2) {
21864
+ const hostValues = readMultiValueOption(rawArgs2, "--host") ?? readMultiValueOption(rawArgs2, "--hosts");
21865
+ if (!hostValues) return void 0;
21866
+ return hostValues.flatMap((value) => value.split(",")).map((value) => value.trim()).filter(Boolean).map(normalizeInstalledMcpHost);
21867
+ }
21868
+ function resolveInstalledMcpForInit(selector, hosts) {
21869
+ const discovered = discoverInstalledMcpServers({ hosts });
21870
+ return resolveInstalledMcpSelector(selector, discovered);
21871
+ }
21872
+ async function runDiscoverMcp() {
21873
+ const hosts = parseInstalledMcpHosts(args);
21874
+ const discovered = discoverInstalledMcpServers({ hosts });
21875
+ if (runtime.jsonOutput) {
21876
+ printJson({
21877
+ count: discovered.length,
21878
+ servers: discovered
21879
+ });
21880
+ return;
21881
+ }
21882
+ if (discovered.length === 0) {
21883
+ console.log("No installed MCP servers found.");
21884
+ console.log(`Searched hosts: ${(hosts ?? INSTALLED_MCP_HOSTS).join(", ")}`);
21885
+ return;
21886
+ }
21887
+ console.log("Installed MCP servers:");
21888
+ for (const server of discovered) {
21889
+ console.log(` ${server.id}`);
21890
+ console.log(` source: ${server.sourcePath}`);
21891
+ console.log(` server: ${formatInstalledMcpSource(server)}`);
21892
+ for (const warning of server.warnings) {
21893
+ console.log(` warning: ${warning}`);
21894
+ }
21895
+ }
21896
+ console.log("");
21897
+ console.log("Import one:");
21898
+ console.log(` pluxx init --from-installed-mcp ${discovered[0].id} --yes`);
21899
+ }
21357
21900
  async function runInit() {
21358
21901
  const positionalName = args[1] && !args[1].startsWith("-") ? args[1] : void 0;
21359
21902
  const fromMcpFlag = args.indexOf("--from-mcp");
21903
+ const fromInstalledMcpFlag = args.indexOf("--from-installed-mcp");
21360
21904
  const fromMcpInput = fromMcpFlag !== -1 && args[fromMcpFlag + 1] && !args[fromMcpFlag + 1].startsWith("-") ? args[fromMcpFlag + 1] : void 0;
21361
- if (fromMcpFlag !== -1) {
21905
+ if (fromMcpFlag !== -1 || fromInstalledMcpFlag !== -1) {
21362
21906
  await runInitFromMcp(positionalName, fromMcpInput);
21363
21907
  return;
21364
21908
  }
@@ -21442,7 +21986,7 @@ ${mcpBlock}${brandBlock}
21442
21986
  targets: [${targetsList}],
21443
21987
  })
21444
21988
  `;
21445
- await writeTextFile(resolve23(process.cwd(), "pluxx.config.ts"), template);
21989
+ await writeTextFile(resolve24(process.cwd(), "pluxx.config.ts"), template);
21446
21990
  const skillDir = `skills/${skillName}`;
21447
21991
  await mkdir4(skillDir, { recursive: true });
21448
21992
  const skillContent = `---
@@ -21464,7 +22008,7 @@ Describe how agents should use this skill.
21464
22008
  Example prompt or command here
21465
22009
  \`\`\`
21466
22010
  `;
21467
- await writeTextFile(resolve23(process.cwd(), `${skillDir}/SKILL.md`), skillContent);
22011
+ await writeTextFile(resolve24(process.cwd(), `${skillDir}/SKILL.md`), skillContent);
21468
22012
  console.log("");
21469
22013
  console.log(" Created:");
21470
22014
  console.log(" pluxx.config.ts");
@@ -21493,14 +22037,15 @@ async function runInitFromMcp(initialName, initialSource) {
21493
22037
  const contextPaths = options.contextPaths ?? [];
21494
22038
  const ingestProvider = options.ingestProvider ? parseChoiceOption(options.ingestProvider, AGENT_INGEST_PROVIDERS, "Ingestion provider") : void 0;
21495
22039
  if (!options.jsonOutput && !runtime.quiet) {
21496
- mt("pluxx init --from-mcp");
22040
+ mt(options.installedMcp ? "pluxx init --from-installed-mcp" : "pluxx init --from-mcp");
21497
22041
  }
21498
22042
  try {
21499
- const rawSource = options.source ?? (interactive ? await clackText("MCP server URL or local command") : "");
22043
+ const installedMcpSource = options.installedMcp ? resolveInstalledMcpForInit(options.installedMcp, parseInstalledMcpHosts(args)) : void 0;
22044
+ const rawSource = installedMcpSource ? formatInstalledMcpSource(installedMcpSource) : options.source ?? (interactive ? await clackText("MCP server URL or local command") : "");
21500
22045
  if (!rawSource) {
21501
- throw new Error("Provide an MCP server URL or local command. Example: pluxx init --from-mcp https://example.com/mcp");
22046
+ throw new Error("Provide an MCP server URL/local command or an installed MCP selector. Example: pluxx init --from-mcp https://example.com/mcp");
21502
22047
  }
21503
- let source = parseMcpSourceInput(rawSource, options.transport);
22048
+ let source = installedMcpSource?.server ?? parseMcpSourceInput(rawSource, options.transport);
21504
22049
  let introspectionSource = source;
21505
22050
  const configuredRemoteAuth = source.transport === "stdio" ? void 0 : buildRemoteAuthConfig(options);
21506
22051
  if (configuredRemoteAuth && !source.auth) {
@@ -21653,7 +22198,7 @@ ${formatAuthRequiredMessage("init", retryError, source)}`);
21653
22198
  if (!options.jsonOutput && !runtime.quiet) {
21654
22199
  O2.step("Step 2/4 \xB7 Plugin identity");
21655
22200
  }
21656
- const defaultPluginName = options.name ? toKebabCase3(options.name) : derivePluginName(introspection, source);
22201
+ const defaultPluginName = options.name ? toKebabCase3(options.name) : installedMcpSource?.serverName ? toKebabCase3(installedMcpSource.serverName) : derivePluginName(introspection, source);
21657
22202
  const pluginName = toKebabCase3(
21658
22203
  options.name ?? (interactive ? await clackText("Plugin name", defaultPluginName) : defaultPluginName)
21659
22204
  );
@@ -21687,6 +22232,7 @@ ${formatAuthRequiredMessage("init", retryError, source)}`);
21687
22232
  source,
21688
22233
  runtimeAuthMode,
21689
22234
  introspection,
22235
+ serverName: installedMcpSource?.serverName,
21690
22236
  displayName,
21691
22237
  description: sourcedContextPack?.docsContext?.shortDescription,
21692
22238
  websiteUrl,
@@ -21708,7 +22254,7 @@ ${formatAuthRequiredMessage("init", retryError, source)}`);
21708
22254
  if (!runtime.dryRun) {
21709
22255
  await applyMcpScaffoldPlan(process.cwd(), plan);
21710
22256
  for (const file of contextArtifactFiles) {
21711
- await writeTextFile(resolve23(process.cwd(), file.relativePath), file.content);
22257
+ await writeTextFile(resolve24(process.cwd(), file.relativePath), file.content);
21712
22258
  }
21713
22259
  }
21714
22260
  const lintResult = runtime.dryRun ? { errors: 0, warnings: 0, issues: [] } : await lintProject(process.cwd());
@@ -21778,6 +22324,11 @@ ${formatAuthRequiredMessage("init", retryError, source)}`);
21778
22324
  O2.info(n);
21779
22325
  }
21780
22326
  }
22327
+ if (installedMcpSource && installedMcpSource.warnings.length > 0) {
22328
+ for (const warning of installedMcpSource.warnings) {
22329
+ O2.warn(`Installed MCP import: ${warning}`);
22330
+ }
22331
+ }
21781
22332
  if (summary.quality.issues.length > 0) {
21782
22333
  for (const line of formatMcpQualityLines(summary.quality)) {
21783
22334
  O2.info(line);
@@ -21871,7 +22422,7 @@ async function runSync() {
21871
22422
  async function runDoctor() {
21872
22423
  const consumerMode = readFlag(args, "--consumer");
21873
22424
  const doctorPath = args.slice(1).find((value) => !value.startsWith("-"));
21874
- const rootDir = doctorPath ? resolve23(process.cwd(), doctorPath) : process.cwd();
22425
+ const rootDir = doctorPath ? resolve24(process.cwd(), doctorPath) : process.cwd();
21875
22426
  const report = consumerMode ? await doctorConsumer(rootDir) : await doctorProject(rootDir);
21876
22427
  if (runtime.jsonOutput) {
21877
22428
  printJson(report);
@@ -22828,7 +23379,7 @@ async function runVerifyInstall() {
22828
23379
  const targets = parseTargetFlagValues(args);
22829
23380
  const config = await loadConfig();
22830
23381
  if (runtime.dryRun) {
22831
- const distDir = resolve23(process.cwd(), config.outDir);
23382
+ const distDir = resolve24(process.cwd(), config.outDir);
22832
23383
  const plan = planInstallPlugin(distDir, config.name, targets ?? config.targets);
22833
23384
  const summary = {
22834
23385
  dryRun: true,
@@ -22915,9 +23466,10 @@ Usage:
22915
23466
  pluxx agent prepare Generate agent context + boundary files for host agents
22916
23467
  pluxx agent prompt <kind> Generate a prompt pack (taxonomy, instructions, review)
22917
23468
  pluxx agent run <kind> --runner <id> Execute a prompt pack via Claude, Cursor, Codex, or OpenCode headlessly
23469
+ pluxx discover-mcp [--host <hosts...>] List installed MCP servers from local host configs
22918
23470
  pluxx mcp proxy ... Run a local MCP proxy with optional record/replay tapes
22919
23471
  pluxx autopilot --from-mcp ... Run import + agent refinement + verification in one command
22920
- pluxx init [name] [--from-mcp <source>] Create a new pluxx.config.ts
23472
+ pluxx init [name] [--from-mcp <source>|--from-installed-mcp <selector>] Create a new pluxx.config.ts
22921
23473
  pluxx sync [--from-mcp <source>] Refresh MCP-derived scaffold files
22922
23474
  pluxx migrate <path> Import an existing plugin into pluxx
22923
23475
  pluxx test [--target <platforms...>] [--install] [--behavioral] Run config, lint, eval, build, and smoke checks
@@ -22952,6 +23504,9 @@ Examples:
22952
23504
  pluxx init my-plugin Scaffold a new plugin config
22953
23505
  pluxx init --from-mcp https://example.com/mcp Scaffold from a remote MCP server
22954
23506
  pluxx init --from-mcp "npx -y -p @acme/mcp acme-mcp" Scaffold from a local MCP command
23507
+ pluxx discover-mcp List installed MCP servers in Claude, Cursor, Codex, and OpenCode config
23508
+ pluxx discover-mcp --host codex opencode --json
23509
+ pluxx init --from-installed-mcp codex:acme --yes Scaffold from an already installed MCP config
22955
23510
  pluxx init --from-mcp https://example.com/mcp --yes --name acme --display-name "Acme" --author "Acme" --targets claude-code,codex --grouping workflow --hooks safe --json
22956
23511
  pluxx init --from-mcp https://example.com/mcp --yes --auth-env API_KEY --auth-type header --auth-header X-API-Key --auth-template "\${value}"
22957
23512
  pluxx init --from-mcp https://example.com/mcp --yes --auth-type platform --runtime-auth platform
@@ -22992,6 +23547,39 @@ Examples:
22992
23547
  pluxx verify-install --target codex Verify the installed Codex bundle in its native local path
22993
23548
  pluxx install --dry-run Preview local install paths and trust implications
22994
23549
  pluxx install --trust Install without hook trust confirmation
23550
+ pluxx publish --dry-run Preview npm/GitHub release publish checks
23551
+ pluxx publish --github-release --version 1.0.0 Create release installers and a GitHub release
23552
+ pluxx publish --npm --tag next Publish the npm package under a non-latest dist-tag
23553
+ `);
23554
+ }
23555
+ function printPublishHelp() {
23556
+ console.log(`
23557
+ pluxx publish \u2014 package and release a built plugin or CLI
23558
+
23559
+ Usage:
23560
+ pluxx publish [--npm] [--github-release] [--allow-dirty] [--dry-run] [--json] [--tag latest] [--version x.y.z]
23561
+
23562
+ Behavior:
23563
+ - loads the current pluxx.config.* project
23564
+ - plans npm publish and/or GitHub release work
23565
+ - runs clean-working-tree checks unless --allow-dirty is passed
23566
+ - writes release assets such as install scripts and release-manifest.json when applicable
23567
+
23568
+ Flags:
23569
+ --npm Publish to npm using the configured package metadata
23570
+ --github-release Create or update GitHub release assets for the requested version
23571
+ --version x.y.z Override the release version for the publish plan
23572
+ --tag <name> Set the npm dist-tag (default: latest)
23573
+ --allow-dirty Skip the clean-working-tree check
23574
+ --dry-run Show the publish plan without writing or uploading
23575
+ --json Print the publish plan or result as JSON
23576
+ --help, -h Show this command help
23577
+
23578
+ Examples:
23579
+ pluxx publish --dry-run
23580
+ pluxx publish --github-release --version 1.0.0
23581
+ pluxx publish --npm --version 0.1.7
23582
+ pluxx publish --npm --github-release --version 1.0.0
22995
23583
  `);
22996
23584
  }
22997
23585
  if (import.meta.main) {