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