@orchid-labs/pluxx 0.1.5 → 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
@@ -84,6 +84,9 @@ var require_src = __commonJS({
84
84
  }
85
85
  });
86
86
 
87
+ // src/cli/index.ts
88
+ import { readFileSync as readFileSync17 } from "fs";
89
+
87
90
  // src/config/load.ts
88
91
  import { resolve, extname, dirname } from "path";
89
92
  import { existsSync } from "fs";
@@ -7081,14 +7084,14 @@ async function build(config, rootDir, options = {}) {
7081
7084
  }
7082
7085
 
7083
7086
  // src/cli/agent.ts
7084
- 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";
7085
7088
  import { chmod, copyFile, mkdir as mkdir3, mkdtemp, readFile as readFile3, rm as rm2 } from "fs/promises";
7086
7089
  import { homedir, tmpdir as tmpdir2 } from "os";
7087
- import { relative as relative8, resolve as resolve14 } from "path";
7090
+ import { relative as relative9, resolve as resolve14 } from "path";
7088
7091
  import { spawn as spawn2 } from "child_process";
7089
7092
 
7090
7093
  // src/cli/lint.ts
7091
- 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";
7092
7095
  import { resolve as resolve9, relative as relative6, basename as basename4, dirname as dirname3 } from "path";
7093
7096
 
7094
7097
  // src/validation/platform-rules.ts
@@ -8449,10 +8452,11 @@ function lintMcpUrls(config, issues) {
8449
8452
  }
8450
8453
  }
8451
8454
  }
8452
- function lintMcpRuntimeState(config, issues) {
8455
+ function lintMcpRuntimeState(rootDir, config, issues) {
8453
8456
  if (!config.mcp) return;
8454
8457
  const claudeUsesPlatformAuth = config.targets.includes("claude-code") && config.platforms?.["claude-code"]?.mcpAuth === "platform";
8455
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));
8456
8460
  for (const [serverName, server] of Object.entries(config.mcp)) {
8457
8461
  if (server.transport === "stdio") {
8458
8462
  pushIssue(issues, {
@@ -8462,6 +8466,17 @@ function lintMcpRuntimeState(config, issues) {
8462
8466
  file: "pluxx.config.ts",
8463
8467
  platform: "MCP"
8464
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
+ }
8465
8480
  }
8466
8481
  const runtimeAuthTargets = [];
8467
8482
  if (server.auth?.type === "platform") {
@@ -8485,6 +8500,37 @@ function lintMcpRuntimeState(config, issues) {
8485
8500
  }
8486
8501
  }
8487
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
+ }
8488
8534
  function lintCodexHookCompatibility(config, issues) {
8489
8535
  if (!isCodexTargetEnabled(config) || !config.hooks) return;
8490
8536
  for (const hookEvent of Object.keys(config.hooks)) {
@@ -8718,15 +8764,19 @@ function lintOpenCodeAgentFrontmatter(dir, config, issues) {
8718
8764
  const agents = readCanonicalAgentFiles(resolve9(dir, config.agents));
8719
8765
  for (const agent of agents) {
8720
8766
  if (!("tools" in agent.frontmatter)) continue;
8767
+ if (hasCanonicalAgentPermission(agent.frontmatter.permission)) continue;
8721
8768
  pushIssue(issues, {
8722
8769
  level: "warning",
8723
8770
  code: "opencode-agent-tools-deprecated",
8724
- 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.",
8725
8772
  file: relative6(dir, agent.filePath).replace(/\\/g, "/"),
8726
8773
  platform: "OpenCode"
8727
8774
  });
8728
8775
  }
8729
8776
  }
8777
+ function hasCanonicalAgentPermission(value) {
8778
+ return !!value && typeof value === "object" && !Array.isArray(value);
8779
+ }
8730
8780
  function lintAbsolutePaths(config, issues) {
8731
8781
  const absolutePathPattern = /^\/[a-zA-Z]|^[A-Z]:\\/;
8732
8782
  if (config.hooks) {
@@ -9169,7 +9219,7 @@ async function lintProject(dir = process.cwd(), options = {}) {
9169
9219
  lintAgentIsolation(agentFiles, issues, frontmatterCache);
9170
9220
  lintOpenCodeAgentFrontmatter(dir, { ...lintConfig, agents: lintConfig.agents ?? "./agents/" }, issues);
9171
9221
  lintMcpUrls(lintConfig, issues);
9172
- lintMcpRuntimeState(lintConfig, issues);
9222
+ lintMcpRuntimeState(dir, lintConfig, issues);
9173
9223
  lintBrandMetadata(lintConfig, issues);
9174
9224
  lintCodexOverrides(lintConfig, issues);
9175
9225
  lintCodexHookCompatibility(lintConfig, issues);
@@ -9253,16 +9303,17 @@ async function runLint(dir = process.cwd()) {
9253
9303
  }
9254
9304
 
9255
9305
  // src/cli/test.ts
9256
- import { existsSync as existsSync19 } from "fs";
9306
+ import { existsSync as existsSync20 } from "fs";
9257
9307
  import { resolve as resolve12 } from "path";
9258
9308
 
9259
9309
  // src/cli/eval.ts
9260
- import { existsSync as existsSync18, readFileSync as readFileSync7 } from "fs";
9310
+ import { existsSync as existsSync19, readFileSync as readFileSync7 } from "fs";
9261
9311
  import { resolve as resolve11 } from "path";
9262
9312
 
9263
9313
  // src/cli/init-from-mcp.ts
9314
+ import { existsSync as existsSync18, lstatSync as lstatSync2 } from "fs";
9264
9315
  import { mkdir as mkdir2 } from "fs/promises";
9265
- 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";
9266
9317
 
9267
9318
  // src/user-config.ts
9268
9319
  var ENV_VAR_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
@@ -9545,6 +9596,7 @@ async function planMcpScaffold(options) {
9545
9596
  });
9546
9597
  const serverName = options.serverName ?? toKebabCase(options.introspection.serverInfo.name) ?? pluginName;
9547
9598
  const permissions = options.permissions ?? (options.approveMcpTools ? { allow: [`MCP(${serverName}.*)`] } : void 0);
9599
+ const passthroughPaths = inferLocalRuntimePassthroughPaths(options.rootDir, options.source);
9548
9600
  const runtimeAuthMode = options.runtimeAuthMode ?? (options.source.transport !== "stdio" && options.source.auth?.type === "platform" ? "platform" : "inline");
9549
9601
  const instructionsPath = resolve10(options.rootDir, "INSTRUCTIONS.md");
9550
9602
  const skillRoot = resolve10(options.rootDir, "skills");
@@ -9586,6 +9638,7 @@ async function planMcpScaffold(options) {
9586
9638
  userConfig,
9587
9639
  hooks: generatedHooks.hookEntries,
9588
9640
  scriptsPath: generatedHooks.scriptsPath,
9641
+ passthroughPaths,
9589
9642
  runtimeAuthMode,
9590
9643
  permissions,
9591
9644
  commandsPath: "./commands/"
@@ -9983,6 +10036,8 @@ function buildConfigTemplate(input) {
9983
10036
  userConfig: ${serializeUserConfig(input.userConfig)},
9984
10037
  ` : "";
9985
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)},
9986
10041
  ` : "";
9987
10042
  const commandsBlock = input.commandsPath ? ` commands: ${JSON.stringify(input.commandsPath)},
9988
10043
  ` : "";
@@ -10018,6 +10073,7 @@ ${commandsBlock}
10018
10073
  instructions: './INSTRUCTIONS.md',
10019
10074
  ${userConfigBlock}
10020
10075
  ${scriptsBlock}
10076
+ ${passthroughBlock}
10021
10077
 
10022
10078
  mcp: {
10023
10079
  ${mcpBlock}
@@ -10034,6 +10090,29 @@ ${platformsBlock}
10034
10090
  })
10035
10091
  `;
10036
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
+ }
10037
10116
  function buildMcpBlock(serverName, source) {
10038
10117
  if (source.transport === "stdio") {
10039
10118
  const argsLine = source.args && source.args.length > 0 ? `,
@@ -11073,7 +11152,7 @@ function summarizeChecks(checks) {
11073
11152
  }
11074
11153
  async function loadMcpScaffoldMetadata(rootDir) {
11075
11154
  const metadataPath = resolve11(rootDir, MCP_SCAFFOLD_METADATA_PATH);
11076
- if (!existsSync18(metadataPath)) {
11155
+ if (!existsSync19(metadataPath)) {
11077
11156
  return null;
11078
11157
  }
11079
11158
  const parsed = JSON.parse(readFileSync7(metadataPath, "utf-8"));
@@ -11104,7 +11183,7 @@ function collectSkillPromptLabels(skill) {
11104
11183
  function evaluateInstructions(rootDir, metadata, checks) {
11105
11184
  const relativePath = "INSTRUCTIONS.md";
11106
11185
  const filePath = resolve11(rootDir, relativePath);
11107
- if (!existsSync18(filePath)) {
11186
+ if (!existsSync19(filePath)) {
11108
11187
  addCheck(checks, {
11109
11188
  level: "error",
11110
11189
  code: "instructions-missing",
@@ -11150,7 +11229,7 @@ function evaluateSkills(rootDir, metadata, checks) {
11150
11229
  for (const skill of metadata.skills) {
11151
11230
  const relativePath = `skills/${skill.dirName}/SKILL.md`;
11152
11231
  const filePath = resolve11(rootDir, relativePath);
11153
- if (!existsSync18(filePath)) {
11232
+ if (!existsSync19(filePath)) {
11154
11233
  failures.push({ path: relativePath, missing: ["skill file"] });
11155
11234
  continue;
11156
11235
  }
@@ -11208,7 +11287,7 @@ function evaluateCommands(rootDir, metadata, checks) {
11208
11287
  for (const skill of metadata.skills) {
11209
11288
  const relativePath = `commands/${skill.dirName}.md`;
11210
11289
  const filePath = resolve11(rootDir, relativePath);
11211
- if (!existsSync18(filePath)) {
11290
+ if (!existsSync19(filePath)) {
11212
11291
  failures.push({ path: relativePath, missing: ["command file"] });
11213
11292
  continue;
11214
11293
  }
@@ -11460,7 +11539,7 @@ async function runTestSuite(options = {}) {
11460
11539
  const evalReport = await runEvalSuite({ rootDir });
11461
11540
  const checks = targets.map((platform) => {
11462
11541
  const requiredPath = SMOKE_PATHS[platform];
11463
- const ok = existsSync19(resolve12(rootDir, config.outDir, platform, requiredPath));
11542
+ const ok = existsSync20(resolve12(rootDir, config.outDir, platform, requiredPath));
11464
11543
  return { platform, requiredPath, ok };
11465
11544
  });
11466
11545
  return {
@@ -11517,8 +11596,8 @@ function printTestResult(result) {
11517
11596
  }
11518
11597
 
11519
11598
  // src/cli/sync-from-mcp.ts
11520
- import { cpSync as cpSync2, existsSync as existsSync20, mkdtempSync, readFileSync as readFileSync8, rmSync as rmSync2, readdirSync as readdirSync6, rmdirSync, writeFileSync as writeFileSync3 } from "fs";
11521
- 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";
11522
11601
  import { tmpdir } from "os";
11523
11602
 
11524
11603
  // src/mcp/introspect.ts
@@ -11800,10 +11879,10 @@ async function createSseClient(server) {
11800
11879
  let resolveEndpoint;
11801
11880
  let rejectEndpoint;
11802
11881
  let endpointSettled = false;
11803
- const endpointReady = new Promise((resolve24, reject) => {
11882
+ const endpointReady = new Promise((resolve25, reject) => {
11804
11883
  resolveEndpoint = (value) => {
11805
11884
  endpointSettled = true;
11806
- resolve24(value);
11885
+ resolve25(value);
11807
11886
  };
11808
11887
  rejectEndpoint = (error) => {
11809
11888
  endpointSettled = true;
@@ -11940,7 +12019,7 @@ async function createSseClient(server) {
11940
12019
  async request(method, params) {
11941
12020
  const requestId = nextRequestId();
11942
12021
  const endpoint = endpointUrl ?? await endpointReady;
11943
- const resultPromise = new Promise((resolve24, reject) => {
12022
+ const resultPromise = new Promise((resolve25, reject) => {
11944
12023
  const timeout = setTimeout(() => {
11945
12024
  pending.delete(requestId);
11946
12025
  reject(new McpIntrospectionError(`Timed out waiting for MCP SSE response to ${method}.`));
@@ -11948,7 +12027,7 @@ async function createSseClient(server) {
11948
12027
  pending.set(requestId, {
11949
12028
  resolve: (value) => {
11950
12029
  clearTimeout(timeout);
11951
- resolve24(value);
12030
+ resolve25(value);
11952
12031
  },
11953
12032
  reject: (error) => {
11954
12033
  clearTimeout(timeout);
@@ -12101,7 +12180,7 @@ async function createStdioClient(server) {
12101
12180
  method,
12102
12181
  ...params ? { params } : {}
12103
12182
  });
12104
- return new Promise((resolve24, reject) => {
12183
+ return new Promise((resolve25, reject) => {
12105
12184
  const timeout = setTimeout(() => {
12106
12185
  pending.delete(id);
12107
12186
  reject(new McpIntrospectionError(`Timed out waiting for MCP stdio response to ${method}.`));
@@ -12109,7 +12188,7 @@ async function createStdioClient(server) {
12109
12188
  pending.set(id, {
12110
12189
  resolve: (value) => {
12111
12190
  clearTimeout(timeout);
12112
- resolve24(value);
12191
+ resolve25(value);
12113
12192
  },
12114
12193
  reject: (error) => {
12115
12194
  clearTimeout(timeout);
@@ -12287,7 +12366,7 @@ function nextRequestId() {
12287
12366
  // src/cli/sync-from-mcp.ts
12288
12367
  async function readMcpScaffoldMetadata(rootDir) {
12289
12368
  const filepath = resolveWithinRoot(rootDir, MCP_SCAFFOLD_METADATA_PATH);
12290
- if (!existsSync20(filepath)) {
12369
+ if (!existsSync21(filepath)) {
12291
12370
  throw new Error(
12292
12371
  `No MCP scaffold metadata found at ${MCP_SCAFFOLD_METADATA_PATH}. Run "pluxx init --from-mcp" first.`
12293
12372
  );
@@ -12322,14 +12401,14 @@ async function syncFromMcp(options) {
12322
12401
  const skillRenames = detectSkillRenames(metadata.skills, newMetadata.skills, toolRenames);
12323
12402
  for (const [oldSkillDir, newSkillDir] of skillRenames) {
12324
12403
  const oldSkillPath = resolveWithinRoot(options.rootDir, `skills/${oldSkillDir}/SKILL.md`);
12325
- if (!existsSync20(oldSkillPath)) continue;
12404
+ if (!existsSync21(oldSkillPath)) continue;
12326
12405
  const oldContent = readFileSync8(oldSkillPath, "utf-8");
12327
12406
  const extracted = extractMixedMarkdownContent(oldContent, "");
12328
12407
  if (!hasMeaningfulCustomContent(oldContent)) continue;
12329
12408
  const newSkill = newMetadata.skills.find((s) => s.dirName === newSkillDir);
12330
12409
  if (!newSkill) continue;
12331
12410
  const newSkillPath = resolveWithinRoot(options.rootDir, `skills/${newSkill.dirName}/SKILL.md`);
12332
- if (!existsSync20(newSkillPath)) continue;
12411
+ if (!existsSync21(newSkillPath)) continue;
12333
12412
  const currentContent = readFileSync8(newSkillPath, "utf-8");
12334
12413
  const updatedContent = injectCustomContent(currentContent, extracted.customContent);
12335
12414
  writeFileSync3(newSkillPath, updatedContent, "utf-8");
@@ -12372,7 +12451,7 @@ async function syncFromMcp(options) {
12372
12451
  if (!beforeManaged.has(file)) return false;
12373
12452
  const before = beforeContents.get(file);
12374
12453
  const currentPath = resolveWithinRoot(options.rootDir, file);
12375
- if (!existsSync20(currentPath)) return false;
12454
+ if (!existsSync21(currentPath)) return false;
12376
12455
  const after = readFileSync8(currentPath, "utf-8");
12377
12456
  return before !== after;
12378
12457
  });
@@ -12461,7 +12540,7 @@ async function planSyncFromMcp(options) {
12461
12540
  }
12462
12541
  function readPersistedSkills(rootDir, metadata) {
12463
12542
  const taxonomyPath = resolveWithinRoot(rootDir, MCP_TAXONOMY_PATH);
12464
- if (existsSync20(taxonomyPath)) {
12543
+ if (existsSync21(taxonomyPath)) {
12465
12544
  return JSON.parse(readFileSync8(taxonomyPath, "utf-8"));
12466
12545
  }
12467
12546
  return metadata.skills.map((skill) => ({
@@ -12474,12 +12553,12 @@ function readPersistedSkills(rootDir, metadata) {
12474
12553
  function preserveCustomContentForRenames(rootDir, renames, pathForName) {
12475
12554
  for (const [oldName, newName] of renames) {
12476
12555
  const oldPath = resolveWithinRoot(rootDir, pathForName(oldName));
12477
- if (!existsSync20(oldPath)) continue;
12556
+ if (!existsSync21(oldPath)) continue;
12478
12557
  const oldContent = readFileSync8(oldPath, "utf-8");
12479
12558
  const extracted = extractMixedMarkdownContent(oldContent, "");
12480
12559
  if (!hasMeaningfulCustomContent(oldContent)) continue;
12481
12560
  const newPath = resolveWithinRoot(rootDir, pathForName(newName));
12482
- if (!existsSync20(newPath)) continue;
12561
+ if (!existsSync21(newPath)) continue;
12483
12562
  const currentContent = readFileSync8(newPath, "utf-8");
12484
12563
  const updatedContent = injectCustomContent(currentContent, extracted.customContent);
12485
12564
  writeFileSync3(newPath, updatedContent, "utf-8");
@@ -12489,21 +12568,21 @@ function snapshotManagedFiles(rootDir, files) {
12489
12568
  const contents = /* @__PURE__ */ new Map();
12490
12569
  for (const file of files) {
12491
12570
  const filepath = resolveWithinRoot(rootDir, file);
12492
- if (!existsSync20(filepath)) continue;
12571
+ if (!existsSync21(filepath)) continue;
12493
12572
  contents.set(file, readFileSync8(filepath, "utf-8"));
12494
12573
  }
12495
12574
  return contents;
12496
12575
  }
12497
12576
  function removeManagedFile(rootDir, relativePath) {
12498
12577
  const filepath = resolveWithinRoot(rootDir, relativePath);
12499
- if (!existsSync20(filepath)) return;
12578
+ if (!existsSync21(filepath)) return;
12500
12579
  rmSync2(filepath, { force: true });
12501
- pruneEmptyDirectories(rootDir, dirname4(filepath));
12580
+ pruneEmptyDirectories(rootDir, dirname5(filepath));
12502
12581
  }
12503
12582
  function shouldPreserveManagedFile(rootDir, relativePath) {
12504
12583
  if (!relativePath.endsWith(".md")) return false;
12505
12584
  const filepath = resolveWithinRoot(rootDir, relativePath);
12506
- if (!existsSync20(filepath)) return false;
12585
+ if (!existsSync21(filepath)) return false;
12507
12586
  return hasMeaningfulCustomContent(readFileSync8(filepath, "utf-8"));
12508
12587
  }
12509
12588
  function pruneEmptyDirectories(rootDir, startDir) {
@@ -12513,7 +12592,7 @@ function pruneEmptyDirectories(rootDir, startDir) {
12513
12592
  const entries = readdirSync6(current);
12514
12593
  if (entries.length > 0) return;
12515
12594
  rmdirSync(current);
12516
- current = dirname4(current);
12595
+ current = dirname5(current);
12517
12596
  }
12518
12597
  }
12519
12598
  var AGENT_PACK_FILES = [
@@ -12677,7 +12756,7 @@ function computeSkillRenameScore(oldSkill, newSkill, toolRenames) {
12677
12756
  function resolveWithinRoot(rootDir, relativePath) {
12678
12757
  const rootPath = resolve13(rootDir);
12679
12758
  const filepath = resolve13(rootPath, relativePath);
12680
- const relativePathFromRoot = relative7(rootPath, filepath);
12759
+ const relativePathFromRoot = relative8(rootPath, filepath);
12681
12760
  if (relativePathFromRoot === "" || !relativePathFromRoot.startsWith("..") && !isAbsolute(relativePathFromRoot)) {
12682
12761
  return filepath;
12683
12762
  }
@@ -12693,13 +12772,13 @@ function formatSyncSummary(result, rootDir) {
12693
12772
  result.renamedFiles.forEach((rename) => lines.push(` \u2192 ${rename.from} \u2192 ${rename.to}`));
12694
12773
  }
12695
12774
  lines.push(`Added: ${result.addedFiles.length}`);
12696
- result.addedFiles.forEach((file) => lines.push(` + ${relative7(rootDir, resolve13(rootDir, file))}`));
12775
+ result.addedFiles.forEach((file) => lines.push(` + ${relative8(rootDir, resolve13(rootDir, file))}`));
12697
12776
  lines.push(`Updated: ${result.updatedFiles.length}`);
12698
- result.updatedFiles.forEach((file) => lines.push(` ~ ${relative7(rootDir, resolve13(rootDir, file))}`));
12777
+ result.updatedFiles.forEach((file) => lines.push(` ~ ${relative8(rootDir, resolve13(rootDir, file))}`));
12699
12778
  lines.push(`Removed: ${result.removedFiles.length}`);
12700
- result.removedFiles.forEach((file) => lines.push(` - ${relative7(rootDir, resolve13(rootDir, file))}`));
12779
+ result.removedFiles.forEach((file) => lines.push(` - ${relative8(rootDir, resolve13(rootDir, file))}`));
12701
12780
  lines.push(`Preserved: ${result.preservedFiles.length}`);
12702
- result.preservedFiles.forEach((file) => lines.push(` ! ${relative7(rootDir, resolve13(rootDir, file))}`));
12781
+ result.preservedFiles.forEach((file) => lines.push(` ! ${relative8(rootDir, resolve13(rootDir, file))}`));
12703
12782
  return lines;
12704
12783
  }
12705
12784
 
@@ -12788,7 +12867,7 @@ async function planAgentPrompt(rootDir, kind, options = {}) {
12788
12867
  const project = await loadAgentProjectModel(rootDir, config);
12789
12868
  const overrides = await loadAgentOverrides(rootDir);
12790
12869
  const contextPath = resolve14(rootDir, AGENT_CONTEXT_PATH);
12791
- if (!options.allowMissingContext && !existsSync21(contextPath)) {
12870
+ if (!options.allowMissingContext && !existsSync22(contextPath)) {
12792
12871
  throw new Error(`No agent context found at ${AGENT_CONTEXT_PATH}. Run "pluxx agent prepare" first.`);
12793
12872
  }
12794
12873
  if (project.sourceKind !== "mcp-derived" && kind !== "review") {
@@ -12800,7 +12879,7 @@ async function planAgentPrompt(rootDir, kind, options = {}) {
12800
12879
  displayName: project.displayName,
12801
12880
  skillPaths: project.skills.map((skill) => skill.path),
12802
12881
  commandPaths: project.commands.map((command2) => command2.path),
12803
- 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))),
12804
12883
  sourceKind: project.sourceKind,
12805
12884
  taxonomyPath: project.taxonomyPath,
12806
12885
  overrides
@@ -12968,7 +13047,7 @@ function buildProtectedFiles() {
12968
13047
  }
12969
13048
  async function loadMcpScaffoldMetadata2(rootDir) {
12970
13049
  const metadataPath = resolve14(rootDir, MCP_SCAFFOLD_METADATA_PATH);
12971
- if (!existsSync21(metadataPath)) {
13050
+ if (!existsSync22(metadataPath)) {
12972
13051
  throw new Error(`No MCP scaffold metadata found at ${MCP_SCAFFOLD_METADATA_PATH}. Run "pluxx init --from-mcp" first.`);
12973
13052
  }
12974
13053
  try {
@@ -12982,7 +13061,7 @@ async function loadMcpScaffoldMetadata2(rootDir) {
12982
13061
  }
12983
13062
  async function loadAgentProjectModel(rootDir, config) {
12984
13063
  const metadataPath = resolve14(rootDir, MCP_SCAFFOLD_METADATA_PATH);
12985
- if (existsSync21(metadataPath)) {
13064
+ if (existsSync22(metadataPath)) {
12986
13065
  const metadata = await loadMcpScaffoldMetadata2(rootDir);
12987
13066
  const serverEntry = Object.entries(config.mcp ?? {})[0];
12988
13067
  const [serverName, server] = serverEntry ?? ["unknown", metadata.source];
@@ -13020,7 +13099,7 @@ function loadManualAgentProjectModel(rootDir, config) {
13020
13099
  const commandsDir = config.commands ? resolve14(rootDir, config.commands) : void 0;
13021
13100
  const skills = readCanonicalSkillFiles(rootDir, skillsDir);
13022
13101
  const commands = readCanonicalCommandFiles(commandsDir).map((command2) => ({
13023
- path: normalizeRelativePath(relative8(rootDir, command2.filePath)),
13102
+ path: normalizeRelativePath(relative9(rootDir, command2.filePath)),
13024
13103
  title: command2.title,
13025
13104
  description: command2.description
13026
13105
  }));
@@ -13351,7 +13430,7 @@ async function collectAgentContextPackInternal(rootDir, options, overrides) {
13351
13430
  if (seenFilePaths.has(relativePath)) continue;
13352
13431
  seenFilePaths.add(relativePath);
13353
13432
  const filePath = resolve14(rootDir, relativePath);
13354
- if (!existsSync21(filePath)) {
13433
+ if (!existsSync22(filePath)) {
13355
13434
  const source2 = {
13356
13435
  label: relativePath,
13357
13436
  kind: "file",
@@ -14521,18 +14600,18 @@ function normalizeRelativePath(path) {
14521
14600
  return path.replace(/\\/g, "/").replace(/^\.\//, "");
14522
14601
  }
14523
14602
  function readCanonicalSkillFiles(rootDir, skillsDir) {
14524
- if (!skillsDir || !existsSync21(skillsDir)) return [];
14603
+ if (!skillsDir || !existsSync22(skillsDir)) return [];
14525
14604
  return walkSkillMarkdownFiles(skillsDir).sort((a, b) => a.localeCompare(b)).map((filePath) => {
14526
14605
  const content = readFileSync9(filePath, "utf-8");
14527
14606
  const { frontmatterLines, body } = splitSkillMarkdownFrontmatter(content);
14528
- const dirName = normalizeRelativePath(relative8(skillsDir, filePath).replace(/\/SKILL\.md$/i, ""));
14607
+ const dirName = normalizeRelativePath(relative9(skillsDir, filePath).replace(/\/SKILL\.md$/i, ""));
14529
14608
  const title = firstMarkdownHeading(body) ?? dirName;
14530
14609
  return {
14531
14610
  dirName,
14532
14611
  title,
14533
14612
  description: parseYamlDescription(frontmatterLines),
14534
14613
  toolNames: [],
14535
- path: normalizeRelativePath(relative8(rootDir, filePath))
14614
+ path: normalizeRelativePath(relative9(rootDir, filePath))
14536
14615
  };
14537
14616
  });
14538
14617
  }
@@ -14842,7 +14921,7 @@ async function executeCommand(command2, cwd, options = {}) {
14842
14921
  let claudeTurnCompleted = false;
14843
14922
  let claudeTurnFailed = false;
14844
14923
  const sentinelInterval = codexLastMessagePath || isClaudeStreamJson ? setInterval(() => {
14845
- const sawCompletionSignal = codexTurnCompleted || codexTurnFailed || claudeTurnCompleted || claudeTurnFailed || (codexLastMessagePath ? existsSync21(codexLastMessagePath) : false);
14924
+ const sawCompletionSignal = codexTurnCompleted || codexTurnFailed || claudeTurnCompleted || claudeTurnFailed || (codexLastMessagePath ? existsSync22(codexLastMessagePath) : false);
14846
14925
  if (!sawCompletionSignal) return;
14847
14926
  if (sawFinalMessageAt == null) {
14848
14927
  sawFinalMessageAt = Date.now();
@@ -14968,15 +15047,15 @@ exec ${shellQuote(cursorBinary)} "$@"
14968
15047
  await mkdir3(resolve14(isolatedCodexHome, "memories"), { recursive: true });
14969
15048
  for (const relativePath of ["auth.json", "config.toml", "hooks.json", "installation_id"]) {
14970
15049
  const sourcePath = resolve14(currentCodexHome, relativePath);
14971
- if (!existsSync21(sourcePath)) continue;
15050
+ if (!existsSync22(sourcePath)) continue;
14972
15051
  await copyFile(sourcePath, resolve14(isolatedCodexHome, relativePath));
14973
15052
  }
14974
15053
  const rulesSourceDir = resolve14(currentCodexHome, "rules");
14975
- if (existsSync21(rulesSourceDir)) {
15054
+ if (existsSync22(rulesSourceDir)) {
14976
15055
  const rulesTargetDir = resolve14(isolatedCodexHome, "rules");
14977
15056
  await mkdir3(rulesTargetDir, { recursive: true });
14978
15057
  const defaultRulesPath = resolve14(rulesSourceDir, "default.rules");
14979
- if (existsSync21(defaultRulesPath)) {
15058
+ if (existsSync22(defaultRulesPath)) {
14980
15059
  await copyFile(defaultRulesPath, resolve14(rulesTargetDir, "default.rules"));
14981
15060
  }
14982
15061
  }
@@ -15014,7 +15093,7 @@ function titleCase(value) {
15014
15093
  }
15015
15094
  async function loadAgentOverrides(rootDir) {
15016
15095
  const overridesPath = resolve14(rootDir, AGENT_OVERRIDES_PATH);
15017
- if (!existsSync21(overridesPath)) {
15096
+ if (!existsSync22(overridesPath)) {
15018
15097
  return null;
15019
15098
  }
15020
15099
  const content = await readTextFile(overridesPath);
@@ -15116,12 +15195,12 @@ ${additions.map((block) => `- ${block.replace(/\n/g, "\n ")}`).join("\n")}
15116
15195
  }
15117
15196
 
15118
15197
  // src/cli/doctor.ts
15119
- import { accessSync, constants, existsSync as existsSync23, lstatSync, readFileSync as readFileSync11, readdirSync as readdirSync9 } from "fs";
15120
- 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";
15121
15200
 
15122
15201
  // src/cli/install.ts
15123
- import { resolve as resolve15, dirname as dirname5 } from "path";
15124
- 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";
15125
15204
  import { spawnSync } from "child_process";
15126
15205
  import * as readline2 from "readline";
15127
15206
  function listHookCommands(hooks) {
@@ -15390,12 +15469,12 @@ ${content.slice(frontmatterMatch[0].length)}`;
15390
15469
  }
15391
15470
  function syncOpenCodeSkills(pluginDir, pluginName) {
15392
15471
  const sourceSkillsDir = resolve15(pluginDir, "skills");
15393
- if (!existsSync22(sourceSkillsDir)) return;
15472
+ if (!existsSync23(sourceSkillsDir)) return;
15394
15473
  mkdirSync4(getOpenCodeSkillRoot(), { recursive: true });
15395
15474
  for (const entry of readdirSync8(sourceSkillsDir, { withFileTypes: true })) {
15396
15475
  if (!entry.isDirectory()) continue;
15397
15476
  const skillSourceDir = resolve15(sourceSkillsDir, entry.name);
15398
- if (!existsSync22(resolve15(skillSourceDir, "SKILL.md"))) continue;
15477
+ if (!existsSync23(resolve15(skillSourceDir, "SKILL.md"))) continue;
15399
15478
  const installedSkillDir = getOpenCodeInstalledSkillDir(pluginName, entry.name);
15400
15479
  rmSync3(installedSkillDir, { recursive: true, force: true });
15401
15480
  cpSync3(skillSourceDir, installedSkillDir, { recursive: true });
@@ -15408,7 +15487,7 @@ function syncOpenCodeSkills(pluginDir, pluginName) {
15408
15487
  }
15409
15488
  function verifyOpenCodeInstall(pluginDir, pluginName) {
15410
15489
  const entryPath = getOpenCodeEntryPath(pluginDir);
15411
- if (!existsSync22(entryPath)) {
15490
+ if (!existsSync23(entryPath)) {
15412
15491
  throw new Error(`OpenCode install is incomplete: missing host entry file at ${entryPath}`);
15413
15492
  }
15414
15493
  const entryContent = readFileSync10(entryPath, "utf-8");
@@ -15421,13 +15500,13 @@ function verifyOpenCodeInstall(pluginDir, pluginName) {
15421
15500
  throw new Error(`OpenCode install is incomplete: ${entryPath} does not preserve the plugin root bridge`);
15422
15501
  }
15423
15502
  const sourceSkillsDir = resolve15(pluginDir, "skills");
15424
- if (!existsSync22(sourceSkillsDir)) return;
15503
+ if (!existsSync23(sourceSkillsDir)) return;
15425
15504
  for (const entry of readdirSync8(sourceSkillsDir, { withFileTypes: true })) {
15426
15505
  if (!entry.isDirectory()) continue;
15427
15506
  const sourceSkillPath = resolve15(sourceSkillsDir, entry.name, "SKILL.md");
15428
- if (!existsSync22(sourceSkillPath)) continue;
15507
+ if (!existsSync23(sourceSkillPath)) continue;
15429
15508
  const installedSkillPath = resolve15(getOpenCodeInstalledSkillDir(pluginName, entry.name), "SKILL.md");
15430
- if (!existsSync22(installedSkillPath)) {
15509
+ if (!existsSync23(installedSkillPath)) {
15431
15510
  throw new Error(`OpenCode install is incomplete: missing synced skill at ${installedSkillPath}`);
15432
15511
  }
15433
15512
  const installedSkillContent = readFileSync10(installedSkillPath, "utf-8");
@@ -15438,7 +15517,7 @@ function verifyOpenCodeInstall(pluginDir, pluginName) {
15438
15517
  }
15439
15518
  function removeOpenCodeSkills(pluginName) {
15440
15519
  const root = getOpenCodeSkillRoot();
15441
- if (!existsSync22(root)) return false;
15520
+ if (!existsSync23(root)) return false;
15442
15521
  let removed = false;
15443
15522
  for (const entry of readdirSync8(root, { withFileTypes: true })) {
15444
15523
  if (!entry.name.startsWith(`${pluginName}-`)) continue;
@@ -15474,7 +15553,7 @@ function runCommandDefault(command2, args2) {
15474
15553
  function createSymlinkInstall(target) {
15475
15554
  const parentDir = resolve15(target.pluginDir, "..");
15476
15555
  mkdirSync4(parentDir, { recursive: true });
15477
- if (existsSync22(target.pluginDir)) {
15556
+ if (existsSync23(target.pluginDir)) {
15478
15557
  rmSync3(target.pluginDir, { recursive: true, force: true });
15479
15558
  }
15480
15559
  symlinkSync(target.sourceDir, target.pluginDir);
@@ -15492,7 +15571,7 @@ function getCodexMarketplacePluginPath(pluginName) {
15492
15571
  return `./.codex/plugins/${pluginName}`;
15493
15572
  }
15494
15573
  function readCodexMarketplace(filepath) {
15495
- if (!existsSync22(filepath)) {
15574
+ if (!existsSync23(filepath)) {
15496
15575
  return {
15497
15576
  name: "pluxx-local",
15498
15577
  interface: {
@@ -15511,7 +15590,7 @@ function readCodexMarketplace(filepath) {
15511
15590
  }
15512
15591
  function ensureCodexMarketplace(pluginName) {
15513
15592
  const filepath = getCodexMarketplacePath();
15514
- mkdirSync4(dirname5(filepath), { recursive: true });
15593
+ mkdirSync4(dirname6(filepath), { recursive: true });
15515
15594
  const marketplace = readCodexMarketplace(filepath);
15516
15595
  const nextPlugins = (marketplace.plugins ?? []).filter((plugin) => plugin.name !== pluginName);
15517
15596
  nextPlugins.push({
@@ -15537,7 +15616,7 @@ function ensureCodexMarketplace(pluginName) {
15537
15616
  }
15538
15617
  function removeCodexMarketplacePlugin(pluginName) {
15539
15618
  const filepath = getCodexMarketplacePath();
15540
- if (!existsSync22(filepath)) return;
15619
+ if (!existsSync23(filepath)) return;
15541
15620
  const marketplace = readCodexMarketplace(filepath);
15542
15621
  const nextPlugins = (marketplace.plugins ?? []).filter((plugin) => plugin.name !== pluginName);
15543
15622
  if (nextPlugins.length === (marketplace.plugins ?? []).length) {
@@ -15559,7 +15638,7 @@ function removeCodexMarketplacePlugin(pluginName) {
15559
15638
  function createCopiedInstall(target) {
15560
15639
  const parentDir = resolve15(target.pluginDir, "..");
15561
15640
  mkdirSync4(parentDir, { recursive: true });
15562
- if (existsSync22(target.pluginDir)) {
15641
+ if (existsSync23(target.pluginDir)) {
15563
15642
  rmSync3(target.pluginDir, { recursive: true, force: true });
15564
15643
  }
15565
15644
  cpSync3(target.sourceDir, target.pluginDir, { recursive: true });
@@ -15584,7 +15663,7 @@ function patchInstalledMcpConfig(pluginDir, platform, config, entries) {
15584
15663
  const env = buildUserConfigEnvMap(entries);
15585
15664
  if (platform === "claude-code" || platform === "cursor") {
15586
15665
  const filepath = resolve15(pluginDir, platform === "claude-code" ? ".mcp.json" : "mcp.json");
15587
- if (!existsSync22(filepath)) return;
15666
+ if (!existsSync23(filepath)) return;
15588
15667
  const mcpServers = {};
15589
15668
  const usesPlatformManagedAuth = platform === "claude-code" ? config.platforms?.["claude-code"]?.mcpAuth === "platform" : config.platforms?.cursor?.mcpAuth === "platform";
15590
15669
  for (const [name, server] of Object.entries(config.mcp)) {
@@ -15616,7 +15695,7 @@ function patchInstalledMcpConfig(pluginDir, platform, config, entries) {
15616
15695
  }
15617
15696
  if (platform === "codex") {
15618
15697
  const filepath = resolve15(pluginDir, ".mcp.json");
15619
- if (!existsSync22(filepath)) return;
15698
+ if (!existsSync23(filepath)) return;
15620
15699
  const mcpServers = {};
15621
15700
  for (const [name, server] of Object.entries(config.mcp)) {
15622
15701
  if (server.transport === "stdio") {
@@ -15656,7 +15735,7 @@ function writeInstalledUserConfig(pluginDir, entries) {
15656
15735
  function disableInstalledEnvValidation(pluginDir, entries) {
15657
15736
  if (entries.length === 0) return;
15658
15737
  const filepath = resolve15(pluginDir, "scripts/check-env.sh");
15659
- if (!existsSync22(filepath)) return;
15738
+ if (!existsSync23(filepath)) return;
15660
15739
  writeFileSync4(
15661
15740
  filepath,
15662
15741
  "#!/usr/bin/env bash\nset -euo pipefail\n# pluxx install materialized required config for this local plugin install.\nexit 0\n"
@@ -15738,7 +15817,7 @@ function ensureClaudeMarketplaceRegistered(pluginName, sourceDir, runCommand, ma
15738
15817
  }
15739
15818
  function installClaudePlugin(target, pluginName, runCommand, materialized) {
15740
15819
  const marketplaceName = ensureClaudeMarketplaceRegistered(pluginName, target.sourceDir, runCommand, materialized);
15741
- if (existsSync22(target.pluginDir)) {
15820
+ if (existsSync23(target.pluginDir)) {
15742
15821
  rmSync3(target.pluginDir, { recursive: true, force: true });
15743
15822
  }
15744
15823
  runCommand("claude", ["plugin", "uninstall", `${pluginName}@${marketplaceName}`]);
@@ -15757,9 +15836,9 @@ function uninstallClaudePlugin(target, pluginName, runCommand, options = {}) {
15757
15836
  }
15758
15837
  }
15759
15838
  const marketplaceRoot = getClaudeMarketplaceRoot(pluginName);
15760
- const hadMarketplaceRoot = existsSync22(marketplaceRoot);
15839
+ const hadMarketplaceRoot = existsSync23(marketplaceRoot);
15761
15840
  rmSync3(marketplaceRoot, { recursive: true, force: true });
15762
- const hadLegacyPluginDir = existsSync22(target.pluginDir);
15841
+ const hadLegacyPluginDir = existsSync23(target.pluginDir);
15763
15842
  if (hadLegacyPluginDir) {
15764
15843
  rmSync3(target.pluginDir, { recursive: true, force: true });
15765
15844
  }
@@ -15773,8 +15852,8 @@ function planInstallPlugin(distDir, pluginName, platforms) {
15773
15852
  return {
15774
15853
  ...target,
15775
15854
  sourceDir,
15776
- built: existsSync22(sourceDir),
15777
- existing: existsSync22(target.pluginDir)
15855
+ built: existsSync23(sourceDir),
15856
+ existing: existsSync23(target.pluginDir)
15778
15857
  };
15779
15858
  });
15780
15859
  }
@@ -15850,13 +15929,13 @@ async function uninstallPlugin(pluginName, platforms, options = {}) {
15850
15929
  continue;
15851
15930
  }
15852
15931
  let removedTarget = false;
15853
- if (existsSync22(target.pluginDir)) {
15932
+ if (existsSync23(target.pluginDir)) {
15854
15933
  rmSync3(target.pluginDir, { recursive: true, force: true });
15855
15934
  removedTarget = true;
15856
15935
  }
15857
15936
  if (target.platform === "opencode") {
15858
15937
  const entryPath = getOpenCodeEntryPath(target.pluginDir);
15859
- if (existsSync22(entryPath)) {
15938
+ if (existsSync23(entryPath)) {
15860
15939
  rmSync3(entryPath, { force: true });
15861
15940
  removedTarget = true;
15862
15941
  }
@@ -15966,7 +16045,7 @@ function checkReadablePath(checks, rootDir, label, configuredPath, required) {
15966
16045
  return;
15967
16046
  }
15968
16047
  const resolvedPath = resolve16(rootDir, configuredPath);
15969
- if (!existsSync23(resolvedPath)) {
16048
+ if (!existsSync24(resolvedPath)) {
15970
16049
  addCheck2(checks, {
15971
16050
  level: "error",
15972
16051
  code: "path-not-found",
@@ -16332,7 +16411,7 @@ function checkCompilerIntent(checks, rootDir) {
16332
16411
  }
16333
16412
  function checkScaffoldMetadata(checks, rootDir, config) {
16334
16413
  const metadataPath = resolve16(rootDir, MCP_SCAFFOLD_METADATA_PATH);
16335
- if (!existsSync23(metadataPath)) {
16414
+ if (!existsSync24(metadataPath)) {
16336
16415
  addCheck2(checks, {
16337
16416
  level: "info",
16338
16417
  code: "mcp-metadata-missing",
@@ -16397,7 +16476,7 @@ function checkScaffoldMetadata(checks, rootDir, config) {
16397
16476
  }
16398
16477
  }
16399
16478
  function detectConsumerLayout(rootDir) {
16400
- if (existsSync23(resolve16(rootDir, ".claude-plugin/plugin.json"))) {
16479
+ if (existsSync24(resolve16(rootDir, ".claude-plugin/plugin.json"))) {
16401
16480
  return {
16402
16481
  kind: "installed-platform",
16403
16482
  platform: "claude-code",
@@ -16405,7 +16484,7 @@ function detectConsumerLayout(rootDir) {
16405
16484
  mcpConfigPath: ".mcp.json"
16406
16485
  };
16407
16486
  }
16408
- if (existsSync23(resolve16(rootDir, ".cursor-plugin/plugin.json"))) {
16487
+ if (existsSync24(resolve16(rootDir, ".cursor-plugin/plugin.json"))) {
16409
16488
  return {
16410
16489
  kind: "installed-platform",
16411
16490
  platform: "cursor",
@@ -16413,7 +16492,7 @@ function detectConsumerLayout(rootDir) {
16413
16492
  mcpConfigPath: "mcp.json"
16414
16493
  };
16415
16494
  }
16416
- if (existsSync23(resolve16(rootDir, ".codex-plugin/plugin.json"))) {
16495
+ if (existsSync24(resolve16(rootDir, ".codex-plugin/plugin.json"))) {
16417
16496
  return {
16418
16497
  kind: "installed-platform",
16419
16498
  platform: "codex",
@@ -16423,7 +16502,7 @@ function detectConsumerLayout(rootDir) {
16423
16502
  }
16424
16503
  const packagePath = resolve16(rootDir, "package.json");
16425
16504
  const indexPath = resolve16(rootDir, "index.ts");
16426
- if (existsSync23(packagePath) && existsSync23(indexPath)) {
16505
+ if (existsSync24(packagePath) && existsSync24(indexPath)) {
16427
16506
  try {
16428
16507
  const pkg = JSON.parse(readFileSync11(packagePath, "utf-8"));
16429
16508
  if (pkg.peerDependencies?.["@opencode-ai/plugin"] || pkg.keywords?.includes("opencode-plugin")) {
@@ -16441,10 +16520,10 @@ function detectConsumerLayout(rootDir) {
16441
16520
  };
16442
16521
  }
16443
16522
  }
16444
- if (CONFIG_FILES.some((filename) => existsSync23(resolve16(rootDir, filename)))) {
16523
+ if (CONFIG_FILES.some((filename) => existsSync24(resolve16(rootDir, filename)))) {
16445
16524
  return { kind: "source-project" };
16446
16525
  }
16447
- 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)))) {
16448
16527
  return { kind: "multi-target-dist" };
16449
16528
  }
16450
16529
  return { kind: "unknown" };
@@ -16455,7 +16534,7 @@ function readJsonFile2(rootDir, relativePath) {
16455
16534
  function checkConsumerBundlePath(checks, rootDir) {
16456
16535
  try {
16457
16536
  accessSync(rootDir, constants.R_OK);
16458
- const details = lstatSync(rootDir);
16537
+ const details = lstatSync3(rootDir);
16459
16538
  addCheck2(checks, {
16460
16539
  level: "success",
16461
16540
  code: "consumer-path-readable",
@@ -16502,7 +16581,7 @@ function checkConsumerManifest(checks, rootDir, layout) {
16502
16581
  function checkInstalledUserConfig(checks, rootDir) {
16503
16582
  const userConfigPath = ".pluxx-user.json";
16504
16583
  const resolvedPath = resolve16(rootDir, userConfigPath);
16505
- if (!existsSync23(resolvedPath)) {
16584
+ if (!existsSync24(resolvedPath)) {
16506
16585
  addCheck2(checks, {
16507
16586
  level: "info",
16508
16587
  code: "consumer-user-config-missing",
@@ -16539,7 +16618,7 @@ function checkInstalledUserConfig(checks, rootDir) {
16539
16618
  function checkInstalledEnvValidation(checks, rootDir) {
16540
16619
  const envScriptPath = "scripts/check-env.sh";
16541
16620
  const resolvedPath = resolve16(rootDir, envScriptPath);
16542
- if (!existsSync23(resolvedPath)) {
16621
+ if (!existsSync24(resolvedPath)) {
16543
16622
  addCheck2(checks, {
16544
16623
  level: "info",
16545
16624
  code: "consumer-env-script-missing",
@@ -16584,7 +16663,7 @@ function checkInstalledMcpConfig(checks, rootDir, layout) {
16584
16663
  return;
16585
16664
  }
16586
16665
  const resolvedPath = resolve16(rootDir, layout.mcpConfigPath);
16587
- if (!existsSync23(resolvedPath)) {
16666
+ if (!existsSync24(resolvedPath)) {
16588
16667
  addCheck2(checks, {
16589
16668
  level: "info",
16590
16669
  code: "consumer-mcp-config-missing",
@@ -16625,6 +16704,17 @@ function checkInstalledMcpConfig(checks, rootDir, layout) {
16625
16704
  fix: "If tools fail, verify the bundled command or its runtime dependencies on this machine.",
16626
16705
  path: layout.mcpConfigPath
16627
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
+ }
16628
16718
  }
16629
16719
  if (remoteEntries.length > 0 && inlineHeaderEntries.length > 0) {
16630
16720
  addCheck2(checks, {
@@ -16659,9 +16749,27 @@ function checkInstalledMcpConfig(checks, rootDir, layout) {
16659
16749
  });
16660
16750
  }
16661
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
+ }
16662
16770
  function isLikelyOpenCodeInstallPath(rootDir) {
16663
- const parent = dirname6(rootDir);
16664
- const grandparent = dirname6(parent);
16771
+ const parent = dirname7(rootDir);
16772
+ const grandparent = dirname7(parent);
16665
16773
  return basename6(parent) === "plugins" && basename6(grandparent) === "opencode";
16666
16774
  }
16667
16775
  function checkInstalledOpenCodeHostBridge(checks, rootDir) {
@@ -16679,7 +16787,7 @@ function checkInstalledOpenCodeHostBridge(checks, rootDir) {
16679
16787
  const pluginName = basename6(rootDir);
16680
16788
  const entryPath = `${rootDir}.ts`;
16681
16789
  const entryRelativePath = `${pluginName}.ts`;
16682
- if (!existsSync23(entryPath)) {
16790
+ if (!existsSync24(entryPath)) {
16683
16791
  addCheck2(checks, {
16684
16792
  level: "error",
16685
16793
  code: "consumer-opencode-entry-missing",
@@ -16719,7 +16827,7 @@ function checkInstalledOpenCodeSkills(checks, rootDir) {
16719
16827
  }
16720
16828
  const pluginName = basename6(rootDir);
16721
16829
  const sourceSkillsDir = resolve16(rootDir, "skills");
16722
- if (!existsSync23(sourceSkillsDir)) {
16830
+ if (!existsSync24(sourceSkillsDir)) {
16723
16831
  addCheck2(checks, {
16724
16832
  level: "info",
16725
16833
  code: "consumer-opencode-skill-sync-not-applicable",
@@ -16730,17 +16838,17 @@ function checkInstalledOpenCodeSkills(checks, rootDir) {
16730
16838
  });
16731
16839
  return;
16732
16840
  }
16733
- const skillRoot = resolve16(dirname6(dirname6(rootDir)), "skills");
16841
+ const skillRoot = resolve16(dirname7(dirname7(rootDir)), "skills");
16734
16842
  const missingSkills = [];
16735
16843
  const malformedSkills = [];
16736
16844
  let expectedSkillCount = 0;
16737
16845
  for (const entry of readdirSync9(sourceSkillsDir, { withFileTypes: true })) {
16738
16846
  if (!entry.isDirectory()) continue;
16739
16847
  const sourceSkillPath = resolve16(sourceSkillsDir, entry.name, "SKILL.md");
16740
- if (!existsSync23(sourceSkillPath)) continue;
16848
+ if (!existsSync24(sourceSkillPath)) continue;
16741
16849
  expectedSkillCount++;
16742
16850
  const installedSkillPath = resolve16(skillRoot, `${pluginName}-${entry.name}`, "SKILL.md");
16743
- if (!existsSync23(installedSkillPath)) {
16851
+ if (!existsSync24(installedSkillPath)) {
16744
16852
  missingSkills.push(`${pluginName}-${entry.name}`);
16745
16853
  continue;
16746
16854
  }
@@ -16837,7 +16945,7 @@ async function doctorConsumer(rootDir = process.cwd()) {
16837
16945
  }
16838
16946
  async function doctorProject(rootDir = process.cwd()) {
16839
16947
  const checks = [];
16840
- const configPath = CONFIG_FILES.find((filename) => existsSync23(resolve16(rootDir, filename)));
16948
+ const configPath = CONFIG_FILES.find((filename) => existsSync24(resolve16(rootDir, filename)));
16841
16949
  addRuntimeChecks(checks, "project");
16842
16950
  if (!configPath) {
16843
16951
  addCheck2(checks, {
@@ -16936,7 +17044,7 @@ function printDoctorReport(report) {
16936
17044
 
16937
17045
  // src/cli/dev.ts
16938
17046
  import { watch } from "fs";
16939
- import { relative as relative9, resolve as resolve17 } from "path";
17047
+ import { relative as relative10, resolve as resolve17 } from "path";
16940
17048
  var WATCH_PATTERNS = [
16941
17049
  /^pluxx\.config\.(ts|js|json)$/,
16942
17050
  /^skills\//,
@@ -16962,7 +17070,7 @@ async function runDev(args2) {
16962
17070
  let pendingFile = null;
16963
17071
  const watcher = watch(rootDir, { recursive: true }, (_event, filename) => {
16964
17072
  if (!filename) return;
16965
- const rel = relative9(rootDir, resolve17(rootDir, filename));
17073
+ const rel = relative10(rootDir, resolve17(rootDir, filename));
16966
17074
  if (rel.startsWith("dist/") || rel.startsWith(".") || rel.includes("node_modules")) {
16967
17075
  return;
16968
17076
  }
@@ -17017,8 +17125,8 @@ async function runBuild(rootDir, targets) {
17017
17125
  }
17018
17126
 
17019
17127
  // src/cli/migrate.ts
17020
- import { basename as basename7, relative as relative10, resolve as resolve18 } from "path";
17021
- 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";
17022
17130
  function detectPlatform(pluginDir) {
17023
17131
  const checks = [
17024
17132
  { dir: ".claude-plugin", platform: "claude-code" },
@@ -17027,12 +17135,12 @@ function detectPlatform(pluginDir) {
17027
17135
  ];
17028
17136
  for (const check of checks) {
17029
17137
  const manifestPath = resolve18(pluginDir, check.dir, "plugin.json");
17030
- if (existsSync24(manifestPath)) {
17138
+ if (existsSync25(manifestPath)) {
17031
17139
  return { platform: check.platform, manifestPath };
17032
17140
  }
17033
17141
  }
17034
17142
  const pkgPath = resolve18(pluginDir, "package.json");
17035
- if (existsSync24(pkgPath)) {
17143
+ if (existsSync25(pkgPath)) {
17036
17144
  try {
17037
17145
  const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
17038
17146
  const deps = {
@@ -17085,7 +17193,7 @@ function parseMcp(pluginDir, detection) {
17085
17193
  } catch {
17086
17194
  }
17087
17195
  for (const mcpPath of mcpPaths) {
17088
- if (!existsSync24(mcpPath)) continue;
17196
+ if (!existsSync25(mcpPath)) continue;
17089
17197
  try {
17090
17198
  const raw = JSON.parse(readFileSync12(mcpPath, "utf-8"));
17091
17199
  const servers = raw.mcpServers ?? raw;
@@ -17179,7 +17287,7 @@ function parseHooks(pluginDir, detection) {
17179
17287
  } catch {
17180
17288
  }
17181
17289
  for (const hooksPath of hooksPaths) {
17182
- if (!existsSync24(hooksPath)) continue;
17290
+ if (!existsSync25(hooksPath)) continue;
17183
17291
  try {
17184
17292
  const raw = JSON.parse(readFileSync12(hooksPath, "utf-8"));
17185
17293
  const hooksObj = raw.hooks ?? raw;
@@ -17228,7 +17336,7 @@ function findInstructions(pluginDir) {
17228
17336
  ];
17229
17337
  for (const candidate of candidates) {
17230
17338
  const filePath = resolve18(pluginDir, candidate);
17231
- if (existsSync24(filePath)) {
17339
+ if (existsSync25(filePath)) {
17232
17340
  return `./${candidate}`;
17233
17341
  }
17234
17342
  }
@@ -17253,7 +17361,7 @@ function detectCanonicalSourcePaths(pluginDir) {
17253
17361
  for (const bucket of Object.keys(CANONICAL_SOURCE_CANDIDATES)) {
17254
17362
  for (const candidate of CANONICAL_SOURCE_CANDIDATES[bucket]) {
17255
17363
  const normalized = stripRelativePrefix(candidate);
17256
- if (!existsSync24(resolve18(pluginDir, normalized))) continue;
17364
+ if (!existsSync25(resolve18(pluginDir, normalized))) continue;
17257
17365
  result[bucket] = normalizeRelativeDir(normalized);
17258
17366
  break;
17259
17367
  }
@@ -17269,7 +17377,7 @@ function detectPassthroughDirs(pluginDir, mcp) {
17269
17377
  if (!match?.[1]) continue;
17270
17378
  const dirName = match[1];
17271
17379
  const dirPath = resolve18(pluginDir, dirName);
17272
- if (existsSync24(dirPath)) {
17380
+ if (existsSync25(dirPath)) {
17273
17381
  passthrough.add(`./${dirName}/`);
17274
17382
  }
17275
17383
  }
@@ -17370,7 +17478,7 @@ function inferPermissionsFromMigratedSkills(pluginDir, sourcePaths) {
17370
17478
  for (const entry of entries) {
17371
17479
  if (!entry.isDirectory()) continue;
17372
17480
  const skillPath = resolve18(skillsDir, entry.name, "SKILL.md");
17373
- if (!existsSync24(skillPath)) continue;
17481
+ if (!existsSync25(skillPath)) continue;
17374
17482
  const content = readFileSync12(skillPath, "utf-8");
17375
17483
  const inferredRules = extractAllowedTools(content).map(normalizeMigratedAllowedTool).filter((rule) => Boolean(rule));
17376
17484
  if (inferredRules.length === 0) continue;
@@ -17415,7 +17523,7 @@ function readMigratedSkills(pluginDir, sourcePaths) {
17415
17523
  const skillPath = resolve18(skillsDir, dirName, "SKILL.md");
17416
17524
  let title = titleCaseFromDirName(dirName);
17417
17525
  let description;
17418
- if (existsSync24(skillPath)) {
17526
+ if (existsSync25(skillPath)) {
17419
17527
  const content = readFileSync12(skillPath, "utf-8");
17420
17528
  title = extractFrontmatterField(content, "name") ?? firstHeading3(content) ?? title;
17421
17529
  description = extractFrontmatterField(content, "description");
@@ -17449,13 +17557,13 @@ var HOST_NATIVE_SKILL_FRONTMATTER_KEYS = /* @__PURE__ */ new Set([
17449
17557
  "allowed-tools"
17450
17558
  ]);
17451
17559
  function sanitizeMigratedSkillFrontmatter(outputDir) {
17452
- if (!existsSync24(resolve18(outputDir, "skills"))) return;
17560
+ if (!existsSync25(resolve18(outputDir, "skills"))) return;
17453
17561
  const skillsDir = resolve18(outputDir, "skills");
17454
17562
  const entries = readdirSync10(skillsDir, { withFileTypes: true });
17455
17563
  for (const entry of entries) {
17456
17564
  if (!entry.isDirectory()) continue;
17457
17565
  const skillPath = resolve18(skillsDir, entry.name, "SKILL.md");
17458
- if (!existsSync24(skillPath)) continue;
17566
+ if (!existsSync25(skillPath)) continue;
17459
17567
  const content = readFileSync12(skillPath, "utf-8");
17460
17568
  const lines = content.split(/\r?\n/);
17461
17569
  if (lines[0]?.trim() !== "---") continue;
@@ -17618,11 +17726,11 @@ function walkMarkdownFiles3(dir) {
17618
17726
  return files;
17619
17727
  }
17620
17728
  function normalizeMigratedOpenCodeAgents(destDir) {
17621
- if (!existsSync24(destDir)) return [];
17729
+ if (!existsSync25(destDir)) return [];
17622
17730
  const normalized = [];
17623
17731
  for (const filePath of walkMarkdownFiles3(destDir)) {
17624
17732
  if (normalizeMigratedOpenCodeAgentFile(filePath)) {
17625
- normalized.push(relative10(destDir, filePath).replace(/\\/g, "/"));
17733
+ normalized.push(relative11(destDir, filePath).replace(/\\/g, "/"));
17626
17734
  }
17627
17735
  }
17628
17736
  return normalized.sort();
@@ -17707,7 +17815,7 @@ function buildMigratedScaffoldMetadata(result, outputDir) {
17707
17815
  if (!result.sourcePaths[dir]) return [];
17708
17816
  const baseDir = dir;
17709
17817
  const dirPath = resolve18(outputDir, baseDir);
17710
- if (!existsSync24(dirPath)) return [];
17818
+ if (!existsSync25(dirPath)) return [];
17711
17819
  const entries = readdirSync10(dirPath, { withFileTypes: true });
17712
17820
  const files = [];
17713
17821
  for (const entry of entries) {
@@ -17774,7 +17882,7 @@ function copyDirectories(pluginDir, outputDir, sourcePaths, passthrough) {
17774
17882
  const normalizedSource = stripRelativePrefix(sourcePath);
17775
17883
  const src = resolve18(pluginDir, normalizedSource);
17776
17884
  const dest = resolve18(outputDir, dir);
17777
- if (existsSync24(dest)) {
17885
+ if (existsSync25(dest)) {
17778
17886
  console.log(` skip ./${dir}/ (already exists)`);
17779
17887
  continue;
17780
17888
  }
@@ -17791,7 +17899,7 @@ function copyDirectories(pluginDir, outputDir, sourcePaths, passthrough) {
17791
17899
  const normalized = entry.replace(/^\.\//, "").replace(/\/$/, "");
17792
17900
  const src = resolve18(pluginDir, normalized);
17793
17901
  const dest = resolve18(outputDir, normalized);
17794
- if (existsSync24(dest)) {
17902
+ if (existsSync25(dest)) {
17795
17903
  console.log(` skip ./${normalized}/ (already exists)`);
17796
17904
  continue;
17797
17905
  }
@@ -17937,7 +18045,7 @@ function quote(s) {
17937
18045
  async function migrate(inputPath) {
17938
18046
  const pluginDir = resolve18(inputPath);
17939
18047
  const outputDir = process.cwd();
17940
- if (!existsSync24(pluginDir)) {
18048
+ if (!existsSync25(pluginDir)) {
17941
18049
  console.error(`Error: Path does not exist: ${pluginDir}`);
17942
18050
  process.exit(1);
17943
18051
  }
@@ -18004,7 +18112,7 @@ async function migrate(inputPath) {
18004
18112
  };
18005
18113
  const configContent = generateConfigTs(result);
18006
18114
  const configPath = resolve18(outputDir, "pluxx.config.ts");
18007
- if (existsSync24(configPath)) {
18115
+ if (existsSync25(configPath)) {
18008
18116
  console.error(`
18009
18117
  Error: pluxx.config.ts already exists in ${outputDir}`);
18010
18118
  console.error("Remove it first or run from a different directory.");
@@ -18021,7 +18129,7 @@ Generated pluxx.config.ts`);
18021
18129
  if (instructions) {
18022
18130
  const srcInstr = resolve18(pluginDir, instructions);
18023
18131
  const destInstr = resolve18(outputDir, instructions);
18024
- if (!existsSync24(destInstr)) {
18132
+ if (!existsSync25(destInstr)) {
18025
18133
  const content = readFileSync12(srcInstr, "utf-8");
18026
18134
  await writeTextFile(destInstr, content);
18027
18135
  console.log(`Copied: ${instructions}`);
@@ -18057,7 +18165,7 @@ Generated pluxx.config.ts`);
18057
18165
 
18058
18166
  // src/cli/mcp-proxy.ts
18059
18167
  import { mkdirSync as mkdirSync6, readFileSync as readFileSync13 } from "fs";
18060
- import { dirname as dirname7, resolve as resolve19 } from "path";
18168
+ import { dirname as dirname8, resolve as resolve19 } from "path";
18061
18169
  import * as readline3 from "readline";
18062
18170
  function usage() {
18063
18171
  return [
@@ -18069,19 +18177,19 @@ function usage() {
18069
18177
  "- --replay serves a deterministic stdio MCP session from a recorded tape."
18070
18178
  ].join("\n");
18071
18179
  }
18072
- function readOption(rawArgs, flag) {
18073
- const index = rawArgs.indexOf(flag);
18180
+ function readOption(rawArgs2, flag) {
18181
+ const index = rawArgs2.indexOf(flag);
18074
18182
  if (index === -1) return void 0;
18075
- const value = rawArgs[index + 1];
18183
+ const value = rawArgs2[index + 1];
18076
18184
  if (!value || value.startsWith("-")) {
18077
18185
  return void 0;
18078
18186
  }
18079
18187
  return value;
18080
18188
  }
18081
- function parseOptions(rawArgs) {
18082
- const source = readOption(rawArgs, "--from-mcp");
18083
- const recordPath = readOption(rawArgs, "--record");
18084
- const replayPath = readOption(rawArgs, "--replay");
18189
+ function parseOptions(rawArgs2) {
18190
+ const source = readOption(rawArgs2, "--from-mcp");
18191
+ const recordPath = readOption(rawArgs2, "--record");
18192
+ const replayPath = readOption(rawArgs2, "--replay");
18085
18193
  if (recordPath && replayPath) {
18086
18194
  throw new Error("Choose either --record or --replay, not both.");
18087
18195
  }
@@ -18134,7 +18242,7 @@ async function loadReplayTape(filepath) {
18134
18242
  }
18135
18243
  async function writeTape(filepath, tape) {
18136
18244
  const absolutePath = resolve19(process.cwd(), filepath);
18137
- mkdirSync6(dirname7(absolutePath), { recursive: true });
18245
+ mkdirSync6(dirname8(absolutePath), { recursive: true });
18138
18246
  await writeTextFile(absolutePath, `${JSON.stringify(tape, null, 2)}
18139
18247
  `);
18140
18248
  }
@@ -18269,17 +18377,17 @@ async function replaySession(filepath, io) {
18269
18377
  rl.close();
18270
18378
  }
18271
18379
  }
18272
- async function runMcpProxy(rawArgs) {
18273
- return await runMcpProxyWithIo(rawArgs, {
18380
+ async function runMcpProxy(rawArgs2) {
18381
+ return await runMcpProxyWithIo(rawArgs2, {
18274
18382
  input: process.stdin,
18275
18383
  output: process.stdout,
18276
18384
  error: process.stderr
18277
18385
  });
18278
18386
  }
18279
- async function runMcpProxyWithIo(rawArgs, io) {
18387
+ async function runMcpProxyWithIo(rawArgs2, io) {
18280
18388
  let options;
18281
18389
  try {
18282
- options = parseOptions(rawArgs);
18390
+ options = parseOptions(rawArgs2);
18283
18391
  } catch (error) {
18284
18392
  io.error.write(`${error instanceof Error ? error.message : String(error)}
18285
18393
 
@@ -18305,7 +18413,7 @@ var PromptCancelledError = class extends Error {
18305
18413
  }
18306
18414
  };
18307
18415
  function ask(question) {
18308
- return new Promise((resolve24, reject) => {
18416
+ return new Promise((resolve25, reject) => {
18309
18417
  const rl = readline4.createInterface({
18310
18418
  input: process.stdin,
18311
18419
  output: process.stdout
@@ -18333,7 +18441,7 @@ function ask(question) {
18333
18441
  rl.once("close", onClose);
18334
18442
  rl.question(question, (answer) => {
18335
18443
  settle(() => {
18336
- resolve24(answer);
18444
+ resolve25(answer);
18337
18445
  rl.close();
18338
18446
  });
18339
18447
  });
@@ -19308,13 +19416,13 @@ ${c2}
19308
19416
  } }).prompt();
19309
19417
 
19310
19418
  // src/cli/index.ts
19311
- import { basename as basename8, resolve as resolve23 } from "path";
19419
+ import { basename as basename8, resolve as resolve24 } from "path";
19312
19420
  import { mkdir as mkdir4, mkdtemp as mkdtemp3, rm as rm4 } from "fs/promises";
19313
19421
  import { tmpdir as tmpdir5 } from "os";
19314
- import { spawn as spawn4 } from "child_process";
19422
+ import { spawn as spawn4, spawnSync as spawnSync3 } from "child_process";
19315
19423
 
19316
19424
  // src/cli/publish.ts
19317
- 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";
19318
19426
  import { createHash } from "crypto";
19319
19427
  import { resolve as resolve20 } from "path";
19320
19428
  import { spawnSync as spawnSync2 } from "child_process";
@@ -19349,7 +19457,7 @@ function resolveRequestedChannels(options) {
19349
19457
  }
19350
19458
  function getBuiltTargets(rootDir, config) {
19351
19459
  return config.targets.filter(
19352
- (platform) => existsSync25(resolve20(rootDir, config.outDir, platform))
19460
+ (platform) => existsSync26(resolve20(rootDir, config.outDir, platform))
19353
19461
  );
19354
19462
  }
19355
19463
  function getArchiveAssetName(pluginName, platform, version, variant) {
@@ -19408,7 +19516,7 @@ function buildReleaseAssets(rootDir, config, version, targets) {
19408
19516
  function readNpmPackageName(rootDir, config) {
19409
19517
  const packageDir = resolve20(rootDir, config.outDir, "opencode");
19410
19518
  const packageJsonPath = resolve20(packageDir, "package.json");
19411
- if (!existsSync25(packageJsonPath)) {
19519
+ if (!existsSync26(packageJsonPath)) {
19412
19520
  return {};
19413
19521
  }
19414
19522
  try {
@@ -19461,7 +19569,7 @@ function collectChecks(args2) {
19461
19569
  if (args2.npmEnabled) {
19462
19570
  checks.push({
19463
19571
  name: "npm-package-ready",
19464
- 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),
19465
19573
  code: "npm-package-ready",
19466
19574
  detail: args2.packageDir ? `OpenCode package dir: ${args2.packageDir}` : "No npm-backed target package found."
19467
19575
  });
@@ -20222,36 +20330,36 @@ function runPublish(config, options = {}) {
20222
20330
  }
20223
20331
 
20224
20332
  // src/cli/runtime.ts
20225
- function createCliRuntime(rawArgs) {
20333
+ function createCliRuntime(rawArgs2) {
20226
20334
  const isCI = process.env.CI === "1" || process.env.CI === "true";
20227
20335
  const isTTY = process.stdin.isTTY === true && process.stdout.isTTY === true;
20228
20336
  return {
20229
- dryRun: rawArgs.includes("--dry-run"),
20230
- jsonOutput: rawArgs.includes("--json"),
20231
- quiet: rawArgs.includes("--quiet"),
20337
+ dryRun: rawArgs2.includes("--dry-run"),
20338
+ jsonOutput: rawArgs2.includes("--json"),
20339
+ quiet: rawArgs2.includes("--quiet"),
20232
20340
  isCI,
20233
20341
  isTTY,
20234
20342
  isInteractive: isTTY && !isCI
20235
20343
  };
20236
20344
  }
20237
- function readFlag(rawArgs, flag) {
20238
- return rawArgs.includes(flag);
20345
+ function readFlag(rawArgs2, flag) {
20346
+ return rawArgs2.includes(flag);
20239
20347
  }
20240
- function readOption2(rawArgs, flag) {
20241
- const index = rawArgs.indexOf(flag);
20348
+ function readOption2(rawArgs2, flag) {
20349
+ const index = rawArgs2.indexOf(flag);
20242
20350
  if (index === -1) return void 0;
20243
- const value = rawArgs[index + 1];
20351
+ const value = rawArgs2[index + 1];
20244
20352
  if (!value || value.startsWith("-")) {
20245
20353
  return void 0;
20246
20354
  }
20247
20355
  return value;
20248
20356
  }
20249
- function readMultiValueOption(rawArgs, flag) {
20250
- const index = rawArgs.indexOf(flag);
20357
+ function readMultiValueOption(rawArgs2, flag) {
20358
+ const index = rawArgs2.indexOf(flag);
20251
20359
  if (index === -1) return void 0;
20252
20360
  const values = [];
20253
- for (let i = index + 1; i < rawArgs.length; i += 1) {
20254
- const value = rawArgs[i];
20361
+ for (let i = index + 1; i < rawArgs2.length; i += 1) {
20362
+ const value = rawArgs2[i];
20255
20363
  if (value.startsWith("-")) break;
20256
20364
  values.push(value);
20257
20365
  }
@@ -20268,7 +20376,7 @@ function printJson(value) {
20268
20376
  }
20269
20377
 
20270
20378
  // src/cli/verify-install.ts
20271
- import { existsSync as existsSync26 } from "fs";
20379
+ import { existsSync as existsSync27 } from "fs";
20272
20380
  import { resolve as resolve21 } from "path";
20273
20381
  function buildCheckFromReport(target, pluginName, report) {
20274
20382
  const consumerPath = resolveInstalledConsumerPath(target, pluginName);
@@ -20277,7 +20385,7 @@ function buildCheckFromReport(target, pluginName, report) {
20277
20385
  installPath: target.pluginDir,
20278
20386
  consumerPath,
20279
20387
  built: target.built,
20280
- installed: existsSync26(consumerPath),
20388
+ installed: existsSync27(consumerPath),
20281
20389
  ok: report.errors === 0,
20282
20390
  errors: report.errors,
20283
20391
  warnings: report.warnings,
@@ -20316,7 +20424,7 @@ function printVerifyInstallResult(result) {
20316
20424
  }
20317
20425
 
20318
20426
  // src/cli/behavioral.ts
20319
- import { existsSync as existsSync27, readFileSync as readFileSync15 } from "fs";
20427
+ import { existsSync as existsSync28, readFileSync as readFileSync15 } from "fs";
20320
20428
  import { mkdtemp as mkdtemp2, rm as rm3 } from "fs/promises";
20321
20429
  import { tmpdir as tmpdir4 } from "os";
20322
20430
  import { resolve as resolve22 } from "path";
@@ -20353,7 +20461,7 @@ function loadBehavioralCases(rootDir, targets, promptOverride) {
20353
20461
  }];
20354
20462
  }
20355
20463
  const filePath = resolve22(rootDir, BEHAVIORAL_CONFIG_PATH);
20356
- if (!existsSync27(filePath)) {
20464
+ if (!existsSync28(filePath)) {
20357
20465
  throw new Error(
20358
20466
  `No behavioral smoke config found at ${BEHAVIORAL_CONFIG_PATH}. Add that file or pass --behavioral-prompt to define a real example query.`
20359
20467
  );
@@ -20468,7 +20576,7 @@ async function executeBehavioralCommand(platform, command2, cwd) {
20468
20576
  child.on("close", (code) => {
20469
20577
  const stdout = Buffer.concat(stdoutChunks).toString("utf-8");
20470
20578
  const stderr = Buffer.concat(stderrChunks).toString("utf-8");
20471
- const codexMessage = codexLastMessagePath && existsSync27(codexLastMessagePath) ? readFileSync15(codexLastMessagePath, "utf-8") : "";
20579
+ const codexMessage = codexLastMessagePath && existsSync28(codexLastMessagePath) ? readFileSync15(codexLastMessagePath, "utf-8") : "";
20472
20580
  resolvePromise({
20473
20581
  exitCode: code ?? 1,
20474
20582
  response: codexMessage.trim() || stdout.trim() || stderr.trim()
@@ -20530,8 +20638,400 @@ function shellQuote2(value) {
20530
20638
  return `'${value.replace(/'/g, `'\\''`)}'`;
20531
20639
  }
20532
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
+
20533
21031
  // src/cli/index.ts
20534
- var args = process.argv.slice(2);
21032
+ var CLI_PACKAGE_NAME = "@orchid-labs/pluxx";
21033
+ var rawArgs = process.argv.slice(2);
21034
+ var args = normalizeTopLevelArgs(rawArgs);
20535
21035
  var command = args[0];
20536
21036
  var runtime = createCliRuntime(args);
20537
21037
  var DEFAULT_INIT_TARGETS = ["claude-code", "cursor", "codex", "opencode"];
@@ -20551,6 +21051,12 @@ var ALL_TARGET_PLATFORMS = [
20551
21051
  ];
20552
21052
  async function main() {
20553
21053
  switch (command) {
21054
+ case "version":
21055
+ await runVersionCommand();
21056
+ break;
21057
+ case "upgrade":
21058
+ await runUpgradeCommand();
21059
+ break;
20554
21060
  case "build":
20555
21061
  await runBuild2();
20556
21062
  break;
@@ -20572,6 +21078,9 @@ async function main() {
20572
21078
  case "mcp":
20573
21079
  await runMcp();
20574
21080
  break;
21081
+ case "discover-mcp":
21082
+ await runDiscoverMcp();
21083
+ break;
20575
21084
  case "autopilot":
20576
21085
  await runAutopilot();
20577
21086
  break;
@@ -20585,6 +21094,10 @@ async function main() {
20585
21094
  await runVerifyInstall();
20586
21095
  break;
20587
21096
  case "publish":
21097
+ if (isHelpRequested(args.slice(1))) {
21098
+ printPublishHelp();
21099
+ break;
21100
+ }
20588
21101
  await runPublishCommand();
20589
21102
  break;
20590
21103
  case "uninstall":
@@ -20614,6 +21127,98 @@ async function main() {
20614
21127
  process.exit(1);
20615
21128
  }
20616
21129
  }
21130
+ function normalizeTopLevelArgs(input) {
21131
+ if (input[0] === "--version" || input[0] === "-v") {
21132
+ return ["version", ...input.slice(1)];
21133
+ }
21134
+ if (input[0] === "--upgrade") {
21135
+ return ["upgrade", ...input.slice(1)];
21136
+ }
21137
+ return input;
21138
+ }
21139
+ function isHelpRequested(input) {
21140
+ return input.includes("--help") || input.includes("-h");
21141
+ }
21142
+ function getCliPackageVersion() {
21143
+ const packageJsonPath = new URL("../../package.json", import.meta.url);
21144
+ const raw = JSON.parse(readFileSync17(packageJsonPath, "utf-8"));
21145
+ if (typeof raw.version !== "string" || raw.version.trim() === "") {
21146
+ throw new Error("Unable to determine the installed pluxx version from package.json.");
21147
+ }
21148
+ return raw.version.trim();
21149
+ }
21150
+ function resolveNpmExecutable() {
21151
+ return process.platform === "win32" ? "npm.cmd" : "npm";
21152
+ }
21153
+ function buildUpgradeSummary() {
21154
+ const requestedVersion = readOption2(args, "--version") ?? "latest";
21155
+ const specifier = `${CLI_PACKAGE_NAME}@${requestedVersion}`;
21156
+ return {
21157
+ dryRun: runtime.dryRun,
21158
+ packageName: CLI_PACKAGE_NAME,
21159
+ currentVersion: getCliPackageVersion(),
21160
+ requestedVersion,
21161
+ specifier,
21162
+ command: [resolveNpmExecutable(), "install", "-g", specifier],
21163
+ note: "This updates the global npm install used by `pluxx` on your PATH. Repo-local and `npx` invocations are separate entrypoints."
21164
+ };
21165
+ }
21166
+ async function runVersionCommand() {
21167
+ const version = getCliPackageVersion();
21168
+ if (runtime.jsonOutput) {
21169
+ printJson({ version });
21170
+ return;
21171
+ }
21172
+ console.log(version);
21173
+ }
21174
+ async function runUpgradeCommand() {
21175
+ const summary = buildUpgradeSummary();
21176
+ if (runtime.dryRun) {
21177
+ if (runtime.jsonOutput) {
21178
+ printJson(summary);
21179
+ return;
21180
+ }
21181
+ if (!runtime.quiet) {
21182
+ console.log(`Dry run: would run \`${summary.command.join(" ")}\``);
21183
+ console.log(summary.note);
21184
+ console.log(`Current version: ${summary.currentVersion}`);
21185
+ }
21186
+ return;
21187
+ }
21188
+ const install = spawnSync3(summary.command[0], summary.command.slice(1), runtime.jsonOutput ? {
21189
+ env: process.env,
21190
+ encoding: "utf-8",
21191
+ stdio: "pipe"
21192
+ } : {
21193
+ env: process.env,
21194
+ stdio: "inherit"
21195
+ });
21196
+ if (install.status !== 0) {
21197
+ if (runtime.jsonOutput) {
21198
+ printJson({
21199
+ ...summary,
21200
+ ok: false,
21201
+ stdout: typeof install.stdout === "string" ? install.stdout : "",
21202
+ stderr: typeof install.stderr === "string" ? install.stderr : "",
21203
+ exitCode: install.status ?? 1
21204
+ });
21205
+ }
21206
+ throw new Error(`Failed to upgrade ${CLI_PACKAGE_NAME}.`);
21207
+ }
21208
+ const result = {
21209
+ ...summary,
21210
+ ok: true
21211
+ };
21212
+ if (runtime.jsonOutput) {
21213
+ printJson(result);
21214
+ return;
21215
+ }
21216
+ if (!runtime.quiet) {
21217
+ console.log(`Upgraded ${summary.packageName} with \`${summary.command.join(" ")}\`.`);
21218
+ console.log("Run `pluxx --version` to verify the active version on your PATH.");
21219
+ console.log(summary.note);
21220
+ }
21221
+ }
20617
21222
  function hasAgentContextHints(input) {
20618
21223
  return Boolean(input.docsUrl || input.websiteUrl || (input.contextPaths?.length ?? 0) > 0);
20619
21224
  }
@@ -20899,8 +21504,8 @@ function parseTargetPlatforms(raw) {
20899
21504
  }
20900
21505
  return targets;
20901
21506
  }
20902
- function parseTargetFlagValues(rawArgs) {
20903
- const values = readMultiValueOption(rawArgs, "--target");
21507
+ function parseTargetFlagValues(rawArgs2) {
21508
+ const values = readMultiValueOption(rawArgs2, "--target");
20904
21509
  if (!values) return void 0;
20905
21510
  return parseTargetPlatforms(values.join(","));
20906
21511
  }
@@ -21196,7 +21801,7 @@ async function planInitContextArtifactFiles(rootDir, contextPack) {
21196
21801
  return plannedFiles;
21197
21802
  }
21198
21803
  async function planAuxiliaryFile(rootDir, relativePath, content) {
21199
- const filePath = resolve23(rootDir, relativePath);
21804
+ const filePath = resolve24(rootDir, relativePath);
21200
21805
  const action = await planTextFileAction(filePath, content);
21201
21806
  return {
21202
21807
  relativePath,
@@ -21223,29 +21828,30 @@ function formatMcpDiscoverySummary(introspection) {
21223
21828
  }
21224
21829
  return `${parts.join(", ")} discovered`;
21225
21830
  }
21226
- function parseInitFromMcpOptions(rawArgs, initialName, initialSource) {
21831
+ function parseInitFromMcpOptions(rawArgs2, initialName, initialSource) {
21227
21832
  return {
21228
- source: initialSource ?? readOption2(rawArgs, "--from-mcp"),
21229
- assumeDefaults: rawArgs.includes("--yes"),
21230
- name: readOption2(rawArgs, "--name") ?? initialName,
21231
- author: readOption2(rawArgs, "--author"),
21232
- displayName: readOption2(rawArgs, "--display-name"),
21233
- targets: readOption2(rawArgs, "--targets"),
21234
- docsUrl: readOption2(rawArgs, "--docs"),
21235
- websiteUrl: readOption2(rawArgs, "--website"),
21236
- contextPaths: readMultiValueOption(rawArgs, "--context"),
21237
- ingestProvider: readOption2(rawArgs, "--ingest-provider"),
21238
- authEnv: readOption2(rawArgs, "--auth-env"),
21239
- authType: readOption2(rawArgs, "--auth-type"),
21240
- authHeader: readOption2(rawArgs, "--auth-header"),
21241
- authTemplate: readOption2(rawArgs, "--auth-template"),
21242
- runtimeAuth: readOption2(rawArgs, "--runtime-auth"),
21243
- oauthWrapper: rawArgs.includes("--oauth-wrapper"),
21244
- approveMcpTools: rawArgs.includes("--approve-mcp-tools"),
21245
- grouping: readOption2(rawArgs, "--grouping"),
21246
- hooks: readOption2(rawArgs, "--hooks"),
21247
- transport: readOption2(rawArgs, "--transport"),
21248
- jsonOutput: rawArgs.includes("--json")
21833
+ source: initialSource ?? readOption2(rawArgs2, "--from-mcp"),
21834
+ installedMcp: readOption2(rawArgs2, "--from-installed-mcp"),
21835
+ assumeDefaults: rawArgs2.includes("--yes"),
21836
+ name: readOption2(rawArgs2, "--name") ?? initialName,
21837
+ author: readOption2(rawArgs2, "--author"),
21838
+ displayName: readOption2(rawArgs2, "--display-name"),
21839
+ targets: readOption2(rawArgs2, "--targets"),
21840
+ docsUrl: readOption2(rawArgs2, "--docs"),
21841
+ websiteUrl: readOption2(rawArgs2, "--website"),
21842
+ contextPaths: readMultiValueOption(rawArgs2, "--context"),
21843
+ ingestProvider: readOption2(rawArgs2, "--ingest-provider"),
21844
+ authEnv: readOption2(rawArgs2, "--auth-env"),
21845
+ authType: readOption2(rawArgs2, "--auth-type"),
21846
+ authHeader: readOption2(rawArgs2, "--auth-header"),
21847
+ authTemplate: readOption2(rawArgs2, "--auth-template"),
21848
+ runtimeAuth: readOption2(rawArgs2, "--runtime-auth"),
21849
+ oauthWrapper: rawArgs2.includes("--oauth-wrapper"),
21850
+ approveMcpTools: rawArgs2.includes("--approve-mcp-tools"),
21851
+ grouping: readOption2(rawArgs2, "--grouping"),
21852
+ hooks: readOption2(rawArgs2, "--hooks"),
21853
+ transport: readOption2(rawArgs2, "--transport"),
21854
+ jsonOutput: rawArgs2.includes("--json")
21249
21855
  };
21250
21856
  }
21251
21857
  function toKebabCase3(value) {
@@ -21254,11 +21860,49 @@ function toKebabCase3(value) {
21254
21860
  function toTsString(value) {
21255
21861
  return JSON.stringify(value);
21256
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
+ }
21257
21900
  async function runInit() {
21258
21901
  const positionalName = args[1] && !args[1].startsWith("-") ? args[1] : void 0;
21259
21902
  const fromMcpFlag = args.indexOf("--from-mcp");
21903
+ const fromInstalledMcpFlag = args.indexOf("--from-installed-mcp");
21260
21904
  const fromMcpInput = fromMcpFlag !== -1 && args[fromMcpFlag + 1] && !args[fromMcpFlag + 1].startsWith("-") ? args[fromMcpFlag + 1] : void 0;
21261
- if (fromMcpFlag !== -1) {
21905
+ if (fromMcpFlag !== -1 || fromInstalledMcpFlag !== -1) {
21262
21906
  await runInitFromMcp(positionalName, fromMcpInput);
21263
21907
  return;
21264
21908
  }
@@ -21342,7 +21986,7 @@ ${mcpBlock}${brandBlock}
21342
21986
  targets: [${targetsList}],
21343
21987
  })
21344
21988
  `;
21345
- await writeTextFile(resolve23(process.cwd(), "pluxx.config.ts"), template);
21989
+ await writeTextFile(resolve24(process.cwd(), "pluxx.config.ts"), template);
21346
21990
  const skillDir = `skills/${skillName}`;
21347
21991
  await mkdir4(skillDir, { recursive: true });
21348
21992
  const skillContent = `---
@@ -21364,7 +22008,7 @@ Describe how agents should use this skill.
21364
22008
  Example prompt or command here
21365
22009
  \`\`\`
21366
22010
  `;
21367
- await writeTextFile(resolve23(process.cwd(), `${skillDir}/SKILL.md`), skillContent);
22011
+ await writeTextFile(resolve24(process.cwd(), `${skillDir}/SKILL.md`), skillContent);
21368
22012
  console.log("");
21369
22013
  console.log(" Created:");
21370
22014
  console.log(" pluxx.config.ts");
@@ -21393,14 +22037,15 @@ async function runInitFromMcp(initialName, initialSource) {
21393
22037
  const contextPaths = options.contextPaths ?? [];
21394
22038
  const ingestProvider = options.ingestProvider ? parseChoiceOption(options.ingestProvider, AGENT_INGEST_PROVIDERS, "Ingestion provider") : void 0;
21395
22039
  if (!options.jsonOutput && !runtime.quiet) {
21396
- mt("pluxx init --from-mcp");
22040
+ mt(options.installedMcp ? "pluxx init --from-installed-mcp" : "pluxx init --from-mcp");
21397
22041
  }
21398
22042
  try {
21399
- 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") : "");
21400
22045
  if (!rawSource) {
21401
- 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");
21402
22047
  }
21403
- let source = parseMcpSourceInput(rawSource, options.transport);
22048
+ let source = installedMcpSource?.server ?? parseMcpSourceInput(rawSource, options.transport);
21404
22049
  let introspectionSource = source;
21405
22050
  const configuredRemoteAuth = source.transport === "stdio" ? void 0 : buildRemoteAuthConfig(options);
21406
22051
  if (configuredRemoteAuth && !source.auth) {
@@ -21553,7 +22198,7 @@ ${formatAuthRequiredMessage("init", retryError, source)}`);
21553
22198
  if (!options.jsonOutput && !runtime.quiet) {
21554
22199
  O2.step("Step 2/4 \xB7 Plugin identity");
21555
22200
  }
21556
- 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);
21557
22202
  const pluginName = toKebabCase3(
21558
22203
  options.name ?? (interactive ? await clackText("Plugin name", defaultPluginName) : defaultPluginName)
21559
22204
  );
@@ -21587,6 +22232,7 @@ ${formatAuthRequiredMessage("init", retryError, source)}`);
21587
22232
  source,
21588
22233
  runtimeAuthMode,
21589
22234
  introspection,
22235
+ serverName: installedMcpSource?.serverName,
21590
22236
  displayName,
21591
22237
  description: sourcedContextPack?.docsContext?.shortDescription,
21592
22238
  websiteUrl,
@@ -21608,7 +22254,7 @@ ${formatAuthRequiredMessage("init", retryError, source)}`);
21608
22254
  if (!runtime.dryRun) {
21609
22255
  await applyMcpScaffoldPlan(process.cwd(), plan);
21610
22256
  for (const file of contextArtifactFiles) {
21611
- await writeTextFile(resolve23(process.cwd(), file.relativePath), file.content);
22257
+ await writeTextFile(resolve24(process.cwd(), file.relativePath), file.content);
21612
22258
  }
21613
22259
  }
21614
22260
  const lintResult = runtime.dryRun ? { errors: 0, warnings: 0, issues: [] } : await lintProject(process.cwd());
@@ -21678,6 +22324,11 @@ ${formatAuthRequiredMessage("init", retryError, source)}`);
21678
22324
  O2.info(n);
21679
22325
  }
21680
22326
  }
22327
+ if (installedMcpSource && installedMcpSource.warnings.length > 0) {
22328
+ for (const warning of installedMcpSource.warnings) {
22329
+ O2.warn(`Installed MCP import: ${warning}`);
22330
+ }
22331
+ }
21681
22332
  if (summary.quality.issues.length > 0) {
21682
22333
  for (const line of formatMcpQualityLines(summary.quality)) {
21683
22334
  O2.info(line);
@@ -21771,7 +22422,7 @@ async function runSync() {
21771
22422
  async function runDoctor() {
21772
22423
  const consumerMode = readFlag(args, "--consumer");
21773
22424
  const doctorPath = args.slice(1).find((value) => !value.startsWith("-"));
21774
- const rootDir = doctorPath ? resolve23(process.cwd(), doctorPath) : process.cwd();
22425
+ const rootDir = doctorPath ? resolve24(process.cwd(), doctorPath) : process.cwd();
21775
22426
  const report = consumerMode ? await doctorConsumer(rootDir) : await doctorProject(rootDir);
21776
22427
  if (runtime.jsonOutput) {
21777
22428
  printJson(report);
@@ -22728,7 +23379,7 @@ async function runVerifyInstall() {
22728
23379
  const targets = parseTargetFlagValues(args);
22729
23380
  const config = await loadConfig();
22730
23381
  if (runtime.dryRun) {
22731
- const distDir = resolve23(process.cwd(), config.outDir);
23382
+ const distDir = resolve24(process.cwd(), config.outDir);
22732
23383
  const plan = planInstallPlugin(distDir, config.name, targets ?? config.targets);
22733
23384
  const summary = {
22734
23385
  dryRun: true,
@@ -22805,6 +23456,8 @@ function printHelp() {
22805
23456
  pluxx \u2014 Cross-platform AI agent plugin SDK
22806
23457
 
22807
23458
  Usage:
23459
+ pluxx --version | -v Print the installed Pluxx CLI version
23460
+ pluxx upgrade [--version x.y.z] Upgrade the global npm install of Pluxx
22808
23461
  pluxx build [--target <platforms...>] [--install] Generate platform-specific plugin files
22809
23462
  pluxx dev [--target <platforms...>] Watch for changes and auto-rebuild
22810
23463
  pluxx validate Validate your config
@@ -22813,9 +23466,10 @@ Usage:
22813
23466
  pluxx agent prepare Generate agent context + boundary files for host agents
22814
23467
  pluxx agent prompt <kind> Generate a prompt pack (taxonomy, instructions, review)
22815
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
22816
23470
  pluxx mcp proxy ... Run a local MCP proxy with optional record/replay tapes
22817
23471
  pluxx autopilot --from-mcp ... Run import + agent refinement + verification in one command
22818
- 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
22819
23473
  pluxx sync [--from-mcp <source>] Refresh MCP-derived scaffold files
22820
23474
  pluxx migrate <path> Import an existing plugin into pluxx
22821
23475
  pluxx test [--target <platforms...>] [--install] [--behavioral] Run config, lint, eval, build, and smoke checks
@@ -22841,12 +23495,18 @@ Targets:
22841
23495
  warp, gemini-cli, roo-code, cline, amp
22842
23496
 
22843
23497
  Examples:
23498
+ pluxx --version Print the installed CLI version
23499
+ pluxx upgrade Upgrade the global npm install to latest
23500
+ pluxx upgrade --version x.y.z Upgrade the global npm install to a specific version
22844
23501
  pluxx build Build for all configured targets
22845
23502
  pluxx build --install Build and install all configured targets locally
22846
23503
  pluxx build --target claude-code cursor Build for specific platforms
22847
23504
  pluxx init my-plugin Scaffold a new plugin config
22848
23505
  pluxx init --from-mcp https://example.com/mcp Scaffold from a remote MCP server
22849
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
22850
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
22851
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}"
22852
23512
  pluxx init --from-mcp https://example.com/mcp --yes --auth-type platform --runtime-auth platform
@@ -22887,6 +23547,39 @@ Examples:
22887
23547
  pluxx verify-install --target codex Verify the installed Codex bundle in its native local path
22888
23548
  pluxx install --dry-run Preview local install paths and trust implications
22889
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
22890
23583
  `);
22891
23584
  }
22892
23585
  if (import.meta.main) {