@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/README.md +20 -0
- package/dist/cli/discover-installed-mcp.d.ts +21 -0
- package/dist/cli/discover-installed-mcp.d.ts.map +1 -0
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +892 -199
- package/dist/cli/init-from-mcp.d.ts +1 -0
- package/dist/cli/init-from-mcp.d.ts.map +1 -1
- package/dist/cli/lint.d.ts.map +1 -1
- package/dist/index.js +29 -0
- package/package.json +1 -1
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 =
|
|
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
|
|
11521
|
-
import { dirname as
|
|
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((
|
|
11882
|
+
const endpointReady = new Promise((resolve25, reject) => {
|
|
11804
11883
|
resolveEndpoint = (value) => {
|
|
11805
11884
|
endpointSettled = true;
|
|
11806
|
-
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 (
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
12578
|
+
if (!existsSync21(filepath)) return;
|
|
12500
12579
|
rmSync2(filepath, { force: true });
|
|
12501
|
-
pruneEmptyDirectories(rootDir,
|
|
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 (!
|
|
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 =
|
|
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 =
|
|
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(` + ${
|
|
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(` ~ ${
|
|
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(` - ${
|
|
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(` ! ${
|
|
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 && !
|
|
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) =>
|
|
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 (!
|
|
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 (
|
|
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(
|
|
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 (!
|
|
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 || !
|
|
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(
|
|
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(
|
|
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 ?
|
|
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 (!
|
|
15050
|
+
if (!existsSync22(sourcePath)) continue;
|
|
14972
15051
|
await copyFile(sourcePath, resolve14(isolatedCodexHome, relativePath));
|
|
14973
15052
|
}
|
|
14974
15053
|
const rulesSourceDir = resolve14(currentCodexHome, "rules");
|
|
14975
|
-
if (
|
|
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 (
|
|
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 (!
|
|
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
|
|
15120
|
-
import { basename as basename6, dirname as
|
|
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
|
|
15124
|
-
import { existsSync as
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
15507
|
+
if (!existsSync23(sourceSkillPath)) continue;
|
|
15429
15508
|
const installedSkillPath = resolve15(getOpenCodeInstalledSkillDir(pluginName, entry.name), "SKILL.md");
|
|
15430
|
-
if (!
|
|
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 (!
|
|
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 (
|
|
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 (!
|
|
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(
|
|
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 (!
|
|
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 (
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 (
|
|
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 =
|
|
15839
|
+
const hadMarketplaceRoot = existsSync23(marketplaceRoot);
|
|
15761
15840
|
rmSync3(marketplaceRoot, { recursive: true, force: true });
|
|
15762
|
-
const hadLegacyPluginDir =
|
|
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:
|
|
15777
|
-
existing:
|
|
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 (
|
|
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 (
|
|
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 (!
|
|
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 (!
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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) =>
|
|
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) =>
|
|
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 =
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 =
|
|
16664
|
-
const grandparent =
|
|
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 (!
|
|
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 (!
|
|
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(
|
|
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 (!
|
|
16848
|
+
if (!existsSync24(sourceSkillPath)) continue;
|
|
16741
16849
|
expectedSkillCount++;
|
|
16742
16850
|
const installedSkillPath = resolve16(skillRoot, `${pluginName}-${entry.name}`, "SKILL.md");
|
|
16743
|
-
if (!
|
|
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) =>
|
|
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
|
|
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 =
|
|
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
|
|
17021
|
-
import { existsSync as
|
|
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 (
|
|
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 (
|
|
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 (!
|
|
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 (!
|
|
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 (
|
|
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 (!
|
|
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 (
|
|
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 (!
|
|
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 (
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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(
|
|
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 (!
|
|
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 (
|
|
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 (
|
|
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 (!
|
|
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 (
|
|
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 (!
|
|
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
|
|
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(
|
|
18073
|
-
const index =
|
|
18180
|
+
function readOption(rawArgs2, flag) {
|
|
18181
|
+
const index = rawArgs2.indexOf(flag);
|
|
18074
18182
|
if (index === -1) return void 0;
|
|
18075
|
-
const value =
|
|
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(
|
|
18082
|
-
const source = readOption(
|
|
18083
|
-
const recordPath = readOption(
|
|
18084
|
-
const replayPath = readOption(
|
|
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(
|
|
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(
|
|
18273
|
-
return await runMcpProxyWithIo(
|
|
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(
|
|
18387
|
+
async function runMcpProxyWithIo(rawArgs2, io) {
|
|
18280
18388
|
let options;
|
|
18281
18389
|
try {
|
|
18282
|
-
options = parseOptions(
|
|
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((
|
|
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
|
-
|
|
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
|
|
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
|
|
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) =>
|
|
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 (!
|
|
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 &&
|
|
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(
|
|
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:
|
|
20230
|
-
jsonOutput:
|
|
20231
|
-
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(
|
|
20238
|
-
return
|
|
20345
|
+
function readFlag(rawArgs2, flag) {
|
|
20346
|
+
return rawArgs2.includes(flag);
|
|
20239
20347
|
}
|
|
20240
|
-
function readOption2(
|
|
20241
|
-
const index =
|
|
20348
|
+
function readOption2(rawArgs2, flag) {
|
|
20349
|
+
const index = rawArgs2.indexOf(flag);
|
|
20242
20350
|
if (index === -1) return void 0;
|
|
20243
|
-
const value =
|
|
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(
|
|
20250
|
-
const index =
|
|
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 <
|
|
20254
|
-
const value =
|
|
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
|
|
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:
|
|
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
|
|
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 (!
|
|
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 &&
|
|
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
|
|
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(
|
|
20903
|
-
const values = readMultiValueOption(
|
|
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 =
|
|
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(
|
|
21831
|
+
function parseInitFromMcpOptions(rawArgs2, initialName, initialSource) {
|
|
21227
21832
|
return {
|
|
21228
|
-
source: initialSource ?? readOption2(
|
|
21229
|
-
|
|
21230
|
-
|
|
21231
|
-
|
|
21232
|
-
|
|
21233
|
-
|
|
21234
|
-
|
|
21235
|
-
|
|
21236
|
-
|
|
21237
|
-
|
|
21238
|
-
|
|
21239
|
-
|
|
21240
|
-
|
|
21241
|
-
|
|
21242
|
-
|
|
21243
|
-
|
|
21244
|
-
|
|
21245
|
-
|
|
21246
|
-
|
|
21247
|
-
|
|
21248
|
-
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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(
|
|
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 ?
|
|
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 =
|
|
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) {
|