@orchid-labs/pluxx 0.1.6 → 0.1.8
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/eval.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 +1502 -188
- package/dist/cli/init-from-mcp.d.ts +1 -0
- package/dist/cli/init-from-mcp.d.ts.map +1 -1
- package/dist/cli/install.d.ts +6 -0
- package/dist/cli/install.d.ts.map +1 -1
- package/dist/cli/lint.d.ts.map +1 -1
- package/dist/cli/publish.d.ts.map +1 -1
- package/dist/cli/verify-install.d.ts +2 -0
- package/dist/cli/verify-install.d.ts.map +1 -1
- package/dist/index.js +650 -9
- package/dist/user-config.d.ts +1 -0
- package/dist/user-config.d.ts.map +1 -1
- package/package.json +2 -2
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 readFileSync18 } from "fs";
|
|
89
89
|
|
|
90
90
|
// src/config/load.ts
|
|
91
91
|
import { resolve, extname, dirname } from "path";
|
|
@@ -5192,6 +5192,51 @@ async function generateClaudeFamilyOutputs(args2) {
|
|
|
5192
5192
|
writeInstructions(config, rootDir, options, writeFile3)
|
|
5193
5193
|
]);
|
|
5194
5194
|
}
|
|
5195
|
+
function shellSingleQuote(value) {
|
|
5196
|
+
return `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
5197
|
+
}
|
|
5198
|
+
function buildClaudeHookCommandWrapperScript(command2) {
|
|
5199
|
+
const serializedCommand = shellSingleQuote(command2);
|
|
5200
|
+
const exportLoader = [
|
|
5201
|
+
'import { readFileSync } from "node:fs"',
|
|
5202
|
+
"",
|
|
5203
|
+
"const filepath = process.argv[1]",
|
|
5204
|
+
"if (!filepath) process.exit(0)",
|
|
5205
|
+
'const payload = JSON.parse(readFileSync(filepath, "utf8"))',
|
|
5206
|
+
'const env = payload && typeof payload === "object" && payload.env && typeof payload.env === "object"',
|
|
5207
|
+
" ? payload.env",
|
|
5208
|
+
" : {}",
|
|
5209
|
+
"",
|
|
5210
|
+
"for (const [key, value] of Object.entries(env)) {",
|
|
5211
|
+
" if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) continue",
|
|
5212
|
+
' console.log(`export ${key}=${JSON.stringify(String(value ?? ""))}`)',
|
|
5213
|
+
"}"
|
|
5214
|
+
].join("\n");
|
|
5215
|
+
return [
|
|
5216
|
+
"#!/usr/bin/env bash",
|
|
5217
|
+
"set -euo pipefail",
|
|
5218
|
+
"",
|
|
5219
|
+
'PLUXX_PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}"',
|
|
5220
|
+
'PLUXX_USER_CONFIG_PATH="$PLUXX_PLUGIN_ROOT/.pluxx-user.json"',
|
|
5221
|
+
"",
|
|
5222
|
+
'if [ -f "$PLUXX_USER_CONFIG_PATH" ]; then',
|
|
5223
|
+
" while IFS= read -r pluxx_export; do",
|
|
5224
|
+
' if [ -n "$pluxx_export" ]; then',
|
|
5225
|
+
' eval "$pluxx_export"',
|
|
5226
|
+
' if [ -n "${CLAUDE_ENV_FILE:-}" ]; then',
|
|
5227
|
+
` printf '%s\\n' "$pluxx_export" >> "$CLAUDE_ENV_FILE"`,
|
|
5228
|
+
" fi",
|
|
5229
|
+
" fi",
|
|
5230
|
+
" done < <(",
|
|
5231
|
+
` node --input-type=module -e ${shellSingleQuote(exportLoader)} "$PLUXX_USER_CONFIG_PATH"`,
|
|
5232
|
+
" )",
|
|
5233
|
+
"fi",
|
|
5234
|
+
"",
|
|
5235
|
+
`PLUXX_HOOK_COMMAND=${serializedCommand}`,
|
|
5236
|
+
'eval "$PLUXX_HOOK_COMMAND"',
|
|
5237
|
+
""
|
|
5238
|
+
].join("\n");
|
|
5239
|
+
}
|
|
5195
5240
|
async function writeManifest(config, rootDir, options, writeJson) {
|
|
5196
5241
|
const manifest = {
|
|
5197
5242
|
name: config.name,
|
|
@@ -5269,7 +5314,9 @@ async function writeHooks(config, platform, options, writeJson, writeFile3) {
|
|
|
5269
5314
|
const hooks = {};
|
|
5270
5315
|
const mapEventName = options.mapEventName ?? defaultMapEventName;
|
|
5271
5316
|
const usesPlatformManagedAuth = platform === "claude-code" && config.platforms?.["claude-code"]?.mcpAuth === "platform";
|
|
5317
|
+
const shouldWrapClaudeHookCommands = platform === "claude-code";
|
|
5272
5318
|
const permissionScript = buildGeneratedPermissionHookScript(config.permissions);
|
|
5319
|
+
let generatedClaudeHookCommandCount = 0;
|
|
5273
5320
|
if (permissionScript) {
|
|
5274
5321
|
await writeFile3("hooks/pluxx-permissions.mjs", permissionScript);
|
|
5275
5322
|
hooks.PreToolUse = [{
|
|
@@ -5299,12 +5346,21 @@ async function writeHooks(config, platform, options, writeJson, writeFile3) {
|
|
|
5299
5346
|
if (commandEntries.length === 0) continue;
|
|
5300
5347
|
hooks[mappedEvent] = [
|
|
5301
5348
|
...hooks[mappedEvent] ?? [],
|
|
5302
|
-
...commandEntries.map((entry) =>
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
|
|
5349
|
+
...await Promise.all(commandEntries.map(async (entry) => {
|
|
5350
|
+
const command2 = entry.command.replace("${PLUGIN_ROOT}", `\${${options.pluginRootVar}}`);
|
|
5351
|
+
const finalCommand = shouldWrapClaudeHookCommands ? await (async () => {
|
|
5352
|
+
generatedClaudeHookCommandCount += 1;
|
|
5353
|
+
const relativePath = `hooks/pluxx-hook-command-${generatedClaudeHookCommandCount}.sh`;
|
|
5354
|
+
await writeFile3(relativePath, buildClaudeHookCommandWrapperScript(command2));
|
|
5355
|
+
return `bash "\${${options.pluginRootVar}}/${relativePath}"`;
|
|
5356
|
+
})() : command2;
|
|
5357
|
+
return {
|
|
5358
|
+
...entry.matcher !== void 0 ? { matcher: entry.matcher } : {},
|
|
5359
|
+
hooks: [{
|
|
5360
|
+
type: "command",
|
|
5361
|
+
command: finalCommand
|
|
5362
|
+
}]
|
|
5363
|
+
};
|
|
5308
5364
|
}))
|
|
5309
5365
|
];
|
|
5310
5366
|
}
|
|
@@ -7084,14 +7140,14 @@ async function build(config, rootDir, options = {}) {
|
|
|
7084
7140
|
}
|
|
7085
7141
|
|
|
7086
7142
|
// src/cli/agent.ts
|
|
7087
|
-
import { existsSync as
|
|
7143
|
+
import { existsSync as existsSync22, readdirSync as readdirSync7, readFileSync as readFileSync9, statSync as statSync4 } from "fs";
|
|
7088
7144
|
import { chmod, copyFile, mkdir as mkdir3, mkdtemp, readFile as readFile3, rm as rm2 } from "fs/promises";
|
|
7089
7145
|
import { homedir, tmpdir as tmpdir2 } from "os";
|
|
7090
|
-
import { relative as
|
|
7146
|
+
import { relative as relative9, resolve as resolve14 } from "path";
|
|
7091
7147
|
import { spawn as spawn2 } from "child_process";
|
|
7092
7148
|
|
|
7093
7149
|
// src/cli/lint.ts
|
|
7094
|
-
import { existsSync as existsSync17, readdirSync as readdirSync5, readFileSync as readFileSync6 } from "fs";
|
|
7150
|
+
import { existsSync as existsSync17, lstatSync, readdirSync as readdirSync5, readFileSync as readFileSync6 } from "fs";
|
|
7095
7151
|
import { resolve as resolve9, relative as relative6, basename as basename4, dirname as dirname3 } from "path";
|
|
7096
7152
|
|
|
7097
7153
|
// src/validation/platform-rules.ts
|
|
@@ -8452,10 +8508,11 @@ function lintMcpUrls(config, issues) {
|
|
|
8452
8508
|
}
|
|
8453
8509
|
}
|
|
8454
8510
|
}
|
|
8455
|
-
function lintMcpRuntimeState(config, issues) {
|
|
8511
|
+
function lintMcpRuntimeState(rootDir, config, issues) {
|
|
8456
8512
|
if (!config.mcp) return;
|
|
8457
8513
|
const claudeUsesPlatformAuth = config.targets.includes("claude-code") && config.platforms?.["claude-code"]?.mcpAuth === "platform";
|
|
8458
8514
|
const cursorUsesPlatformAuth = config.targets.includes("cursor") && config.platforms?.cursor?.mcpAuth === "platform";
|
|
8515
|
+
const passthroughDirs = (config.passthrough ?? []).map((entry) => resolveBundledPassthroughDir(rootDir, entry)).filter((entry) => Boolean(entry));
|
|
8459
8516
|
for (const [serverName, server] of Object.entries(config.mcp)) {
|
|
8460
8517
|
if (server.transport === "stdio") {
|
|
8461
8518
|
pushIssue(issues, {
|
|
@@ -8465,6 +8522,17 @@ function lintMcpRuntimeState(config, issues) {
|
|
|
8465
8522
|
file: "pluxx.config.ts",
|
|
8466
8523
|
platform: "MCP"
|
|
8467
8524
|
});
|
|
8525
|
+
for (const runtimePath of findLocalStdioRuntimePaths(rootDir, server)) {
|
|
8526
|
+
if (passthroughDirs.some((dir) => runtimePath === dir || runtimePath.startsWith(`${dir}/`))) continue;
|
|
8527
|
+
const relativeDir = `./${relative6(rootDir, runtimePath).replace(/\\/g, "/")}/`;
|
|
8528
|
+
pushIssue(issues, {
|
|
8529
|
+
level: "warning",
|
|
8530
|
+
code: "mcp-stdio-runtime-unbundled",
|
|
8531
|
+
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.`,
|
|
8532
|
+
file: "pluxx.config.ts",
|
|
8533
|
+
platform: "MCP"
|
|
8534
|
+
});
|
|
8535
|
+
}
|
|
8468
8536
|
}
|
|
8469
8537
|
const runtimeAuthTargets = [];
|
|
8470
8538
|
if (server.auth?.type === "platform") {
|
|
@@ -8488,6 +8556,37 @@ function lintMcpRuntimeState(config, issues) {
|
|
|
8488
8556
|
}
|
|
8489
8557
|
}
|
|
8490
8558
|
}
|
|
8559
|
+
function resolveBundledPassthroughDir(rootDir, entry) {
|
|
8560
|
+
const resolvedPath = resolve9(rootDir, entry);
|
|
8561
|
+
if (!existsSync17(resolvedPath)) return null;
|
|
8562
|
+
try {
|
|
8563
|
+
const stats = lstatSync(resolvedPath);
|
|
8564
|
+
if (!stats.isDirectory()) return null;
|
|
8565
|
+
return resolvedPath.replace(/\/+$/, "");
|
|
8566
|
+
} catch {
|
|
8567
|
+
return null;
|
|
8568
|
+
}
|
|
8569
|
+
}
|
|
8570
|
+
function findLocalStdioRuntimePaths(rootDir, server) {
|
|
8571
|
+
if (server.transport !== "stdio") return [];
|
|
8572
|
+
const runtimeDirs = /* @__PURE__ */ new Set();
|
|
8573
|
+
const candidates = [server.command, ...server.args ?? []];
|
|
8574
|
+
for (const candidate of candidates) {
|
|
8575
|
+
if (!isLikelyLocalRuntimePath(candidate)) continue;
|
|
8576
|
+
const resolvedPath = resolve9(rootDir, candidate);
|
|
8577
|
+
if (!existsSync17(resolvedPath)) continue;
|
|
8578
|
+
try {
|
|
8579
|
+
const stats = lstatSync(resolvedPath);
|
|
8580
|
+
const runtimeDir = stats.isDirectory() ? resolvedPath : dirname3(resolvedPath);
|
|
8581
|
+
runtimeDirs.add(runtimeDir.replace(/\/+$/, ""));
|
|
8582
|
+
} catch {
|
|
8583
|
+
}
|
|
8584
|
+
}
|
|
8585
|
+
return [...runtimeDirs].sort();
|
|
8586
|
+
}
|
|
8587
|
+
function isLikelyLocalRuntimePath(value) {
|
|
8588
|
+
return value.startsWith("./") || value.startsWith("../") || value.startsWith(".\\") || value.startsWith("..\\");
|
|
8589
|
+
}
|
|
8491
8590
|
function lintCodexHookCompatibility(config, issues) {
|
|
8492
8591
|
if (!isCodexTargetEnabled(config) || !config.hooks) return;
|
|
8493
8592
|
for (const hookEvent of Object.keys(config.hooks)) {
|
|
@@ -8721,15 +8820,19 @@ function lintOpenCodeAgentFrontmatter(dir, config, issues) {
|
|
|
8721
8820
|
const agents = readCanonicalAgentFiles(resolve9(dir, config.agents));
|
|
8722
8821
|
for (const agent of agents) {
|
|
8723
8822
|
if (!("tools" in agent.frontmatter)) continue;
|
|
8823
|
+
if (hasCanonicalAgentPermission(agent.frontmatter.permission)) continue;
|
|
8724
8824
|
pushIssue(issues, {
|
|
8725
8825
|
level: "warning",
|
|
8726
8826
|
code: "opencode-agent-tools-deprecated",
|
|
8727
|
-
message: "OpenCode agent `tools` is deprecated. Pluxx
|
|
8827
|
+
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
8828
|
file: relative6(dir, agent.filePath).replace(/\\/g, "/"),
|
|
8729
8829
|
platform: "OpenCode"
|
|
8730
8830
|
});
|
|
8731
8831
|
}
|
|
8732
8832
|
}
|
|
8833
|
+
function hasCanonicalAgentPermission(value) {
|
|
8834
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
8835
|
+
}
|
|
8733
8836
|
function lintAbsolutePaths(config, issues) {
|
|
8734
8837
|
const absolutePathPattern = /^\/[a-zA-Z]|^[A-Z]:\\/;
|
|
8735
8838
|
if (config.hooks) {
|
|
@@ -9172,7 +9275,7 @@ async function lintProject(dir = process.cwd(), options = {}) {
|
|
|
9172
9275
|
lintAgentIsolation(agentFiles, issues, frontmatterCache);
|
|
9173
9276
|
lintOpenCodeAgentFrontmatter(dir, { ...lintConfig, agents: lintConfig.agents ?? "./agents/" }, issues);
|
|
9174
9277
|
lintMcpUrls(lintConfig, issues);
|
|
9175
|
-
lintMcpRuntimeState(lintConfig, issues);
|
|
9278
|
+
lintMcpRuntimeState(dir, lintConfig, issues);
|
|
9176
9279
|
lintBrandMetadata(lintConfig, issues);
|
|
9177
9280
|
lintCodexOverrides(lintConfig, issues);
|
|
9178
9281
|
lintCodexHookCompatibility(lintConfig, issues);
|
|
@@ -9256,19 +9359,30 @@ async function runLint(dir = process.cwd()) {
|
|
|
9256
9359
|
}
|
|
9257
9360
|
|
|
9258
9361
|
// src/cli/test.ts
|
|
9259
|
-
import { existsSync as
|
|
9362
|
+
import { existsSync as existsSync20 } from "fs";
|
|
9260
9363
|
import { resolve as resolve12 } from "path";
|
|
9261
9364
|
|
|
9262
9365
|
// src/cli/eval.ts
|
|
9263
|
-
import { existsSync as
|
|
9366
|
+
import { existsSync as existsSync19, readFileSync as readFileSync7 } from "fs";
|
|
9264
9367
|
import { resolve as resolve11 } from "path";
|
|
9265
9368
|
|
|
9266
9369
|
// src/cli/init-from-mcp.ts
|
|
9370
|
+
import { existsSync as existsSync18, lstatSync as lstatSync2 } from "fs";
|
|
9267
9371
|
import { mkdir as mkdir2 } from "fs/promises";
|
|
9268
|
-
import { basename as basename5, resolve as resolve10 } from "path";
|
|
9372
|
+
import { basename as basename5, dirname as dirname4, relative as relative7, resolve as resolve10 } from "path";
|
|
9269
9373
|
|
|
9270
9374
|
// src/user-config.ts
|
|
9271
9375
|
var ENV_VAR_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
9376
|
+
var PLACEHOLDER_SECRET_PATTERNS = [
|
|
9377
|
+
/\bdummy\b/i,
|
|
9378
|
+
/\bplaceholder\b/i,
|
|
9379
|
+
/\bexample\b/i,
|
|
9380
|
+
/\bchangeme\b/i,
|
|
9381
|
+
/\breplace[_ -]?me\b/i,
|
|
9382
|
+
/\byour[_ -]?(api[_ -]?)?key\b/i,
|
|
9383
|
+
/\bapi[_ -]?key[_ -]?here\b/i,
|
|
9384
|
+
/\btoken[_ -]?here\b/i
|
|
9385
|
+
];
|
|
9272
9386
|
function normalizeUserConfigKey(value) {
|
|
9273
9387
|
return value.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2").replace(/[._/\s]+/g, "-").toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
|
|
9274
9388
|
}
|
|
@@ -9283,6 +9397,12 @@ function extractEnvReference(value) {
|
|
|
9283
9397
|
const match = value.match(/^\$\{([A-Za-z_][A-Za-z0-9_]*)\}$/);
|
|
9284
9398
|
return match?.[1];
|
|
9285
9399
|
}
|
|
9400
|
+
function isPlaceholderSecretValue(value) {
|
|
9401
|
+
if (typeof value !== "string") return false;
|
|
9402
|
+
const normalized = value.trim();
|
|
9403
|
+
if (normalized === "") return false;
|
|
9404
|
+
return PLACEHOLDER_SECRET_PATTERNS.some((pattern) => pattern.test(normalized));
|
|
9405
|
+
}
|
|
9286
9406
|
function isRuntimePlatformManaged(config, target, server) {
|
|
9287
9407
|
if (server.transport === "stdio") return false;
|
|
9288
9408
|
if (server.auth?.type === "platform") {
|
|
@@ -9459,6 +9579,42 @@ var WORKFLOW_SKILL_DEFINITIONS = [
|
|
|
9459
9579
|
title: "General Research",
|
|
9460
9580
|
description: "Handle broad search and query workflows when there is not a more specific product surface match.",
|
|
9461
9581
|
match: ["search", "query", "lookup", "look up", "discover", "find"]
|
|
9582
|
+
},
|
|
9583
|
+
{
|
|
9584
|
+
key: "web-research",
|
|
9585
|
+
title: "Web Research",
|
|
9586
|
+
description: "Search the web, fetch pages, and synthesize public source-backed research.",
|
|
9587
|
+
match: ["web", "website", "url", "page", "fetch", "crawl", "scrape", "search", "result", "source"]
|
|
9588
|
+
},
|
|
9589
|
+
{
|
|
9590
|
+
key: "source-review",
|
|
9591
|
+
title: "Source Review",
|
|
9592
|
+
description: "Audit sources, citations, evidence quality, and duplicate or weak research results.",
|
|
9593
|
+
match: ["source", "citation", "evidence", "audit", "review", "quality", "rank", "dedupe", "duplicate"]
|
|
9594
|
+
},
|
|
9595
|
+
{
|
|
9596
|
+
key: "content-extraction",
|
|
9597
|
+
title: "Content Extraction",
|
|
9598
|
+
description: "Extract structured content, entities, and summaries from pages, documents, or search results.",
|
|
9599
|
+
match: ["extract", "parse", "content", "summary", "summarize", "document", "html", "markdown"]
|
|
9600
|
+
},
|
|
9601
|
+
{
|
|
9602
|
+
key: "knowledge-search",
|
|
9603
|
+
title: "Knowledge Search",
|
|
9604
|
+
description: "Search docs, knowledge bases, papers, repositories, and internal reference material.",
|
|
9605
|
+
match: ["docs", "documentation", "knowledge", "paper", "papers", "repo", "repository", "api", "reference"]
|
|
9606
|
+
},
|
|
9607
|
+
{
|
|
9608
|
+
key: "news-monitoring",
|
|
9609
|
+
title: "News Monitoring",
|
|
9610
|
+
description: "Find recent news, launches, announcements, and time-bounded developments.",
|
|
9611
|
+
match: ["news", "recent", "latest", "launch", "announcement", "announced", "date", "published"]
|
|
9612
|
+
},
|
|
9613
|
+
{
|
|
9614
|
+
key: "code-research",
|
|
9615
|
+
title: "Code Research",
|
|
9616
|
+
description: "Find implementation docs, code examples, API usage, migration notes, and troubleshooting context.",
|
|
9617
|
+
match: ["code", "sdk", "api", "github", "example", "migration", "error", "troubleshoot", "implementation"]
|
|
9462
9618
|
}
|
|
9463
9619
|
];
|
|
9464
9620
|
var WORKFLOW_MATCH_MIN_SCORE = 2;
|
|
@@ -9541,6 +9697,8 @@ async function planMcpScaffold(options) {
|
|
|
9541
9697
|
options.persistedSkills,
|
|
9542
9698
|
options.toolRenames
|
|
9543
9699
|
);
|
|
9700
|
+
const skillGrouping = options.skillGrouping ?? "workflow";
|
|
9701
|
+
const plannedCommands = planCommandScaffolds(plannedSkills, skillGrouping);
|
|
9544
9702
|
const description = options.description ?? deriveScaffoldDescription({
|
|
9545
9703
|
displayName,
|
|
9546
9704
|
introspection: options.introspection,
|
|
@@ -9548,6 +9706,7 @@ async function planMcpScaffold(options) {
|
|
|
9548
9706
|
});
|
|
9549
9707
|
const serverName = options.serverName ?? toKebabCase(options.introspection.serverInfo.name) ?? pluginName;
|
|
9550
9708
|
const permissions = options.permissions ?? (options.approveMcpTools ? { allow: [`MCP(${serverName}.*)`] } : void 0);
|
|
9709
|
+
const passthroughPaths = inferLocalRuntimePassthroughPaths(options.rootDir, options.source);
|
|
9551
9710
|
const runtimeAuthMode = options.runtimeAuthMode ?? (options.source.transport !== "stdio" && options.source.auth?.type === "platform" ? "platform" : "inline");
|
|
9552
9711
|
const instructionsPath = resolve10(options.rootDir, "INSTRUCTIONS.md");
|
|
9553
9712
|
const skillRoot = resolve10(options.rootDir, "skills");
|
|
@@ -9589,9 +9748,10 @@ async function planMcpScaffold(options) {
|
|
|
9589
9748
|
userConfig,
|
|
9590
9749
|
hooks: generatedHooks.hookEntries,
|
|
9591
9750
|
scriptsPath: generatedHooks.scriptsPath,
|
|
9751
|
+
passthroughPaths,
|
|
9592
9752
|
runtimeAuthMode,
|
|
9593
9753
|
permissions,
|
|
9594
|
-
commandsPath: "./commands/"
|
|
9754
|
+
commandsPath: plannedCommands.length > 0 ? "./commands/" : void 0
|
|
9595
9755
|
})
|
|
9596
9756
|
);
|
|
9597
9757
|
await addPlannedFile(
|
|
@@ -9621,8 +9781,6 @@ async function planMcpScaffold(options) {
|
|
|
9621
9781
|
for (const skill of plannedSkills) {
|
|
9622
9782
|
const relativeSkillPath = `skills/${skill.dirName}`;
|
|
9623
9783
|
const skillPath = resolve10(skillRoot, skill.dirName, "SKILL.md");
|
|
9624
|
-
const relativeCommandPath = `commands/${skill.dirName}.md`;
|
|
9625
|
-
const commandPath = resolve10(commandsRoot, `${skill.dirName}.md`);
|
|
9626
9784
|
await addPlannedFile(
|
|
9627
9785
|
`${relativeSkillPath}/SKILL.md`,
|
|
9628
9786
|
wrapManagedMarkdown(
|
|
@@ -9636,6 +9794,10 @@ async function planMcpScaffold(options) {
|
|
|
9636
9794
|
);
|
|
9637
9795
|
skillDirectories.push(relativeSkillPath);
|
|
9638
9796
|
generatedFiles.push(`${relativeSkillPath}/SKILL.md`);
|
|
9797
|
+
}
|
|
9798
|
+
for (const skill of plannedCommands) {
|
|
9799
|
+
const relativeCommandPath = `commands/${skill.dirName}.md`;
|
|
9800
|
+
const commandPath = resolve10(commandsRoot, `${skill.dirName}.md`);
|
|
9639
9801
|
await addPlannedFile(
|
|
9640
9802
|
relativeCommandPath,
|
|
9641
9803
|
buildCommandContent(
|
|
@@ -9655,7 +9817,7 @@ async function planMcpScaffold(options) {
|
|
|
9655
9817
|
introspection: options.introspection,
|
|
9656
9818
|
pluginName,
|
|
9657
9819
|
displayName,
|
|
9658
|
-
skillGrouping
|
|
9820
|
+
skillGrouping,
|
|
9659
9821
|
requestedHookMode: options.hookMode ?? "none",
|
|
9660
9822
|
generatedHookMode: generatedHooks.mode,
|
|
9661
9823
|
generatedHookEvents: Object.keys(generatedHooks.hookEntries ?? {}),
|
|
@@ -9882,6 +10044,19 @@ function buildCommandContent(skill, existingContent) {
|
|
|
9882
10044
|
}
|
|
9883
10045
|
);
|
|
9884
10046
|
}
|
|
10047
|
+
function hasStrongCommandShape(skill, grouping) {
|
|
10048
|
+
if (grouping === "tool") return true;
|
|
10049
|
+
if (skill.tools.length > 1) return true;
|
|
10050
|
+
if (skill.prompts.length > 0) return true;
|
|
10051
|
+
if (skill.resources.length > 0 || skill.resourceTemplates.length > 0) return true;
|
|
10052
|
+
const requiredFields = skill.tools.flatMap((tool) => getTopLevelSchemaFields(tool.inputSchema).filter((field) => field.required));
|
|
10053
|
+
return requiredFields.length >= 2;
|
|
10054
|
+
}
|
|
10055
|
+
function planCommandScaffolds(plannedSkills, grouping) {
|
|
10056
|
+
const commands = plannedSkills.filter((skill) => hasStrongCommandShape(skill, grouping));
|
|
10057
|
+
if (commands.length > 0) return commands;
|
|
10058
|
+
return plannedSkills.slice(0, 1);
|
|
10059
|
+
}
|
|
9885
10060
|
function formatArgumentHintFrontmatter(value) {
|
|
9886
10061
|
const trimmed = value.trim();
|
|
9887
10062
|
if (!trimmed) return '""';
|
|
@@ -9986,6 +10161,8 @@ function buildConfigTemplate(input) {
|
|
|
9986
10161
|
userConfig: ${serializeUserConfig(input.userConfig)},
|
|
9987
10162
|
` : "";
|
|
9988
10163
|
const scriptsBlock = input.scriptsPath ? ` scripts: ${JSON.stringify(input.scriptsPath)},
|
|
10164
|
+
` : "";
|
|
10165
|
+
const passthroughBlock = input.passthroughPaths && input.passthroughPaths.length > 0 ? ` passthrough: ${JSON.stringify(input.passthroughPaths)},
|
|
9989
10166
|
` : "";
|
|
9990
10167
|
const commandsBlock = input.commandsPath ? ` commands: ${JSON.stringify(input.commandsPath)},
|
|
9991
10168
|
` : "";
|
|
@@ -10021,6 +10198,7 @@ ${commandsBlock}
|
|
|
10021
10198
|
instructions: './INSTRUCTIONS.md',
|
|
10022
10199
|
${userConfigBlock}
|
|
10023
10200
|
${scriptsBlock}
|
|
10201
|
+
${passthroughBlock}
|
|
10024
10202
|
|
|
10025
10203
|
mcp: {
|
|
10026
10204
|
${mcpBlock}
|
|
@@ -10037,6 +10215,29 @@ ${platformsBlock}
|
|
|
10037
10215
|
})
|
|
10038
10216
|
`;
|
|
10039
10217
|
}
|
|
10218
|
+
function inferLocalRuntimePassthroughPaths(rootDir, source) {
|
|
10219
|
+
if (source.transport !== "stdio") return [];
|
|
10220
|
+
const passthrough = /* @__PURE__ */ new Set();
|
|
10221
|
+
const candidates = [source.command, ...source.args ?? []];
|
|
10222
|
+
for (const candidate of candidates) {
|
|
10223
|
+
if (!isLikelyLocalRuntimePath2(candidate)) continue;
|
|
10224
|
+
const resolvedPath = resolve10(rootDir, candidate);
|
|
10225
|
+
if (!existsSync18(resolvedPath)) continue;
|
|
10226
|
+
const stats = lstatSync2(resolvedPath);
|
|
10227
|
+
const runtimeDir = stats.isDirectory() ? resolvedPath : dirname4(resolvedPath);
|
|
10228
|
+
const normalized = normalizePassthroughDir(rootDir, runtimeDir);
|
|
10229
|
+
if (normalized) passthrough.add(normalized);
|
|
10230
|
+
}
|
|
10231
|
+
return [...passthrough].sort();
|
|
10232
|
+
}
|
|
10233
|
+
function isLikelyLocalRuntimePath2(value) {
|
|
10234
|
+
return value.startsWith("./") || value.startsWith("../") || value.startsWith(".\\") || value.startsWith("..\\");
|
|
10235
|
+
}
|
|
10236
|
+
function normalizePassthroughDir(rootDir, dirPath) {
|
|
10237
|
+
const relativePath = relative7(rootDir, dirPath).replace(/\\/g, "/").replace(/\/+$/, "");
|
|
10238
|
+
if (!relativePath || relativePath === ".") return null;
|
|
10239
|
+
return `./${relativePath}/`;
|
|
10240
|
+
}
|
|
10040
10241
|
function buildMcpBlock(serverName, source) {
|
|
10041
10242
|
if (source.transport === "stdio") {
|
|
10042
10243
|
const argsLine = source.args && source.args.length > 0 ? `,
|
|
@@ -11076,7 +11277,7 @@ function summarizeChecks(checks) {
|
|
|
11076
11277
|
}
|
|
11077
11278
|
async function loadMcpScaffoldMetadata(rootDir) {
|
|
11078
11279
|
const metadataPath = resolve11(rootDir, MCP_SCAFFOLD_METADATA_PATH);
|
|
11079
|
-
if (!
|
|
11280
|
+
if (!existsSync19(metadataPath)) {
|
|
11080
11281
|
return null;
|
|
11081
11282
|
}
|
|
11082
11283
|
const parsed = JSON.parse(readFileSync7(metadataPath, "utf-8"));
|
|
@@ -11107,7 +11308,7 @@ function collectSkillPromptLabels(skill) {
|
|
|
11107
11308
|
function evaluateInstructions(rootDir, metadata, checks) {
|
|
11108
11309
|
const relativePath = "INSTRUCTIONS.md";
|
|
11109
11310
|
const filePath = resolve11(rootDir, relativePath);
|
|
11110
|
-
if (!
|
|
11311
|
+
if (!existsSync19(filePath)) {
|
|
11111
11312
|
addCheck(checks, {
|
|
11112
11313
|
level: "error",
|
|
11113
11314
|
code: "instructions-missing",
|
|
@@ -11153,7 +11354,7 @@ function evaluateSkills(rootDir, metadata, checks) {
|
|
|
11153
11354
|
for (const skill of metadata.skills) {
|
|
11154
11355
|
const relativePath = `skills/${skill.dirName}/SKILL.md`;
|
|
11155
11356
|
const filePath = resolve11(rootDir, relativePath);
|
|
11156
|
-
if (!
|
|
11357
|
+
if (!existsSync19(filePath)) {
|
|
11157
11358
|
failures.push({ path: relativePath, missing: ["skill file"] });
|
|
11158
11359
|
continue;
|
|
11159
11360
|
}
|
|
@@ -11196,6 +11397,11 @@ function evaluateSkills(rootDir, metadata, checks) {
|
|
|
11196
11397
|
function hasManagedCommands(metadata) {
|
|
11197
11398
|
return metadata.managedFiles.some((file) => file.startsWith("commands/"));
|
|
11198
11399
|
}
|
|
11400
|
+
function managedCommandSkillNames(metadata) {
|
|
11401
|
+
return new Set(
|
|
11402
|
+
metadata.managedFiles.filter((file) => file.startsWith("commands/") && file.endsWith(".md")).map((file) => file.replace(/^commands\//, "").replace(/\.md$/, ""))
|
|
11403
|
+
);
|
|
11404
|
+
}
|
|
11199
11405
|
function evaluateCommands(rootDir, metadata, checks) {
|
|
11200
11406
|
if (!hasManagedCommands(metadata)) {
|
|
11201
11407
|
addCheck(checks, {
|
|
@@ -11208,10 +11414,11 @@ function evaluateCommands(rootDir, metadata, checks) {
|
|
|
11208
11414
|
return;
|
|
11209
11415
|
}
|
|
11210
11416
|
const failures = [];
|
|
11211
|
-
|
|
11417
|
+
const commandSkillNames = managedCommandSkillNames(metadata);
|
|
11418
|
+
for (const skill of metadata.skills.filter((entry) => commandSkillNames.has(entry.dirName))) {
|
|
11212
11419
|
const relativePath = `commands/${skill.dirName}.md`;
|
|
11213
11420
|
const filePath = resolve11(rootDir, relativePath);
|
|
11214
|
-
if (!
|
|
11421
|
+
if (!existsSync19(filePath)) {
|
|
11215
11422
|
failures.push({ path: relativePath, missing: ["command file"] });
|
|
11216
11423
|
continue;
|
|
11217
11424
|
}
|
|
@@ -11256,10 +11463,36 @@ function evaluateCommands(rootDir, metadata, checks) {
|
|
|
11256
11463
|
level: "success",
|
|
11257
11464
|
code: "command-quality-contract",
|
|
11258
11465
|
title: "Command scaffolds expose expected routing guidance and related surfaces",
|
|
11259
|
-
detail: `Checked ${
|
|
11466
|
+
detail: `Checked ${commandSkillNames.size} generated command file(s) for argument hints, tool routing, and related discovery surfaces.`,
|
|
11260
11467
|
fix: "No action needed."
|
|
11261
11468
|
});
|
|
11262
11469
|
}
|
|
11470
|
+
function evaluateScaffoldArchitecture(metadata, checks) {
|
|
11471
|
+
const commandSkillNames = managedCommandSkillNames(metadata);
|
|
11472
|
+
if (metadata.tools.length === 0 || commandSkillNames.size === 0) return;
|
|
11473
|
+
const singletonSkills = metadata.skills.filter((skill) => (skill.toolNames?.length ?? 0) === 1);
|
|
11474
|
+
const commandBackedSingletons = singletonSkills.filter((skill) => commandSkillNames.has(skill.dirName));
|
|
11475
|
+
const commandPerTool = commandSkillNames.size >= metadata.tools.length && commandBackedSingletons.length >= Math.ceil(commandSkillNames.size * 0.8);
|
|
11476
|
+
if (commandPerTool) {
|
|
11477
|
+
addCheck(checks, {
|
|
11478
|
+
level: "warning",
|
|
11479
|
+
code: "scaffold-command-per-tool-shape",
|
|
11480
|
+
title: "Scaffold looks like command-per-tool output",
|
|
11481
|
+
detail: `This scaffold exposes ${commandSkillNames.size} command(s) for ${metadata.tools.length} MCP tool(s), with most commands wrapping a single tool.`,
|
|
11482
|
+
fix: "Prefer workflow-level commands and keep singleton raw tool wrappers as skills only unless the tool has a strong user-facing workflow or required arguments."
|
|
11483
|
+
});
|
|
11484
|
+
}
|
|
11485
|
+
const singletonRatio = singletonSkills.length / Math.max(metadata.skills.length, 1);
|
|
11486
|
+
if (metadata.skills.length >= 6 && singletonRatio >= 0.75) {
|
|
11487
|
+
addCheck(checks, {
|
|
11488
|
+
level: "warning",
|
|
11489
|
+
code: "scaffold-singleton-heavy-taxonomy",
|
|
11490
|
+
title: "Scaffold taxonomy is singleton-heavy",
|
|
11491
|
+
detail: `${singletonSkills.length} of ${metadata.skills.length} skill(s) wrap a single MCP tool, which usually means the plugin is still shaped like an API surface rather than user workflows.`,
|
|
11492
|
+
fix: "Run `pluxx agent run taxonomy` or `pluxx autopilot --mode standard` to merge raw tools into product workflows before shipping."
|
|
11493
|
+
});
|
|
11494
|
+
}
|
|
11495
|
+
}
|
|
11263
11496
|
function evaluateAgentContext(contextContent, metadata, checks) {
|
|
11264
11497
|
const missing = [];
|
|
11265
11498
|
if (((metadata.resources?.length ?? 0) > 0 || (metadata.resourceTemplates?.length ?? 0) > 0 || (metadata.prompts?.length ?? 0) > 0) && !contextContent.includes("## MCP Discovery Surfaces")) {
|
|
@@ -11399,6 +11632,7 @@ async function runEvalSuite(options = {}) {
|
|
|
11399
11632
|
evaluateInstructions(rootDir, metadata, checks);
|
|
11400
11633
|
evaluateSkills(rootDir, metadata, checks);
|
|
11401
11634
|
evaluateCommands(rootDir, metadata, checks);
|
|
11635
|
+
evaluateScaffoldArchitecture(metadata, checks);
|
|
11402
11636
|
}
|
|
11403
11637
|
evaluateAgentContext(contextContent, metadata, checks);
|
|
11404
11638
|
for (const kind of AGENT_PROMPT_KINDS) {
|
|
@@ -11463,7 +11697,7 @@ async function runTestSuite(options = {}) {
|
|
|
11463
11697
|
const evalReport = await runEvalSuite({ rootDir });
|
|
11464
11698
|
const checks = targets.map((platform) => {
|
|
11465
11699
|
const requiredPath = SMOKE_PATHS[platform];
|
|
11466
|
-
const ok =
|
|
11700
|
+
const ok = existsSync20(resolve12(rootDir, config.outDir, platform, requiredPath));
|
|
11467
11701
|
return { platform, requiredPath, ok };
|
|
11468
11702
|
});
|
|
11469
11703
|
return {
|
|
@@ -11520,8 +11754,8 @@ function printTestResult(result) {
|
|
|
11520
11754
|
}
|
|
11521
11755
|
|
|
11522
11756
|
// src/cli/sync-from-mcp.ts
|
|
11523
|
-
import { cpSync as cpSync2, existsSync as
|
|
11524
|
-
import { dirname as
|
|
11757
|
+
import { cpSync as cpSync2, existsSync as existsSync21, mkdtempSync, readFileSync as readFileSync8, rmSync as rmSync2, readdirSync as readdirSync6, rmdirSync, writeFileSync as writeFileSync3 } from "fs";
|
|
11758
|
+
import { dirname as dirname5, isAbsolute, relative as relative8, resolve as resolve13 } from "path";
|
|
11525
11759
|
import { tmpdir } from "os";
|
|
11526
11760
|
|
|
11527
11761
|
// src/mcp/introspect.ts
|
|
@@ -11803,10 +12037,10 @@ async function createSseClient(server) {
|
|
|
11803
12037
|
let resolveEndpoint;
|
|
11804
12038
|
let rejectEndpoint;
|
|
11805
12039
|
let endpointSettled = false;
|
|
11806
|
-
const endpointReady = new Promise((
|
|
12040
|
+
const endpointReady = new Promise((resolve25, reject) => {
|
|
11807
12041
|
resolveEndpoint = (value) => {
|
|
11808
12042
|
endpointSettled = true;
|
|
11809
|
-
|
|
12043
|
+
resolve25(value);
|
|
11810
12044
|
};
|
|
11811
12045
|
rejectEndpoint = (error) => {
|
|
11812
12046
|
endpointSettled = true;
|
|
@@ -11943,7 +12177,7 @@ async function createSseClient(server) {
|
|
|
11943
12177
|
async request(method, params) {
|
|
11944
12178
|
const requestId = nextRequestId();
|
|
11945
12179
|
const endpoint = endpointUrl ?? await endpointReady;
|
|
11946
|
-
const resultPromise = new Promise((
|
|
12180
|
+
const resultPromise = new Promise((resolve25, reject) => {
|
|
11947
12181
|
const timeout = setTimeout(() => {
|
|
11948
12182
|
pending.delete(requestId);
|
|
11949
12183
|
reject(new McpIntrospectionError(`Timed out waiting for MCP SSE response to ${method}.`));
|
|
@@ -11951,7 +12185,7 @@ async function createSseClient(server) {
|
|
|
11951
12185
|
pending.set(requestId, {
|
|
11952
12186
|
resolve: (value) => {
|
|
11953
12187
|
clearTimeout(timeout);
|
|
11954
|
-
|
|
12188
|
+
resolve25(value);
|
|
11955
12189
|
},
|
|
11956
12190
|
reject: (error) => {
|
|
11957
12191
|
clearTimeout(timeout);
|
|
@@ -12104,7 +12338,7 @@ async function createStdioClient(server) {
|
|
|
12104
12338
|
method,
|
|
12105
12339
|
...params ? { params } : {}
|
|
12106
12340
|
});
|
|
12107
|
-
return new Promise((
|
|
12341
|
+
return new Promise((resolve25, reject) => {
|
|
12108
12342
|
const timeout = setTimeout(() => {
|
|
12109
12343
|
pending.delete(id);
|
|
12110
12344
|
reject(new McpIntrospectionError(`Timed out waiting for MCP stdio response to ${method}.`));
|
|
@@ -12112,7 +12346,7 @@ async function createStdioClient(server) {
|
|
|
12112
12346
|
pending.set(id, {
|
|
12113
12347
|
resolve: (value) => {
|
|
12114
12348
|
clearTimeout(timeout);
|
|
12115
|
-
|
|
12349
|
+
resolve25(value);
|
|
12116
12350
|
},
|
|
12117
12351
|
reject: (error) => {
|
|
12118
12352
|
clearTimeout(timeout);
|
|
@@ -12290,7 +12524,7 @@ function nextRequestId() {
|
|
|
12290
12524
|
// src/cli/sync-from-mcp.ts
|
|
12291
12525
|
async function readMcpScaffoldMetadata(rootDir) {
|
|
12292
12526
|
const filepath = resolveWithinRoot(rootDir, MCP_SCAFFOLD_METADATA_PATH);
|
|
12293
|
-
if (!
|
|
12527
|
+
if (!existsSync21(filepath)) {
|
|
12294
12528
|
throw new Error(
|
|
12295
12529
|
`No MCP scaffold metadata found at ${MCP_SCAFFOLD_METADATA_PATH}. Run "pluxx init --from-mcp" first.`
|
|
12296
12530
|
);
|
|
@@ -12325,14 +12559,14 @@ async function syncFromMcp(options) {
|
|
|
12325
12559
|
const skillRenames = detectSkillRenames(metadata.skills, newMetadata.skills, toolRenames);
|
|
12326
12560
|
for (const [oldSkillDir, newSkillDir] of skillRenames) {
|
|
12327
12561
|
const oldSkillPath = resolveWithinRoot(options.rootDir, `skills/${oldSkillDir}/SKILL.md`);
|
|
12328
|
-
if (!
|
|
12562
|
+
if (!existsSync21(oldSkillPath)) continue;
|
|
12329
12563
|
const oldContent = readFileSync8(oldSkillPath, "utf-8");
|
|
12330
12564
|
const extracted = extractMixedMarkdownContent(oldContent, "");
|
|
12331
12565
|
if (!hasMeaningfulCustomContent(oldContent)) continue;
|
|
12332
12566
|
const newSkill = newMetadata.skills.find((s) => s.dirName === newSkillDir);
|
|
12333
12567
|
if (!newSkill) continue;
|
|
12334
12568
|
const newSkillPath = resolveWithinRoot(options.rootDir, `skills/${newSkill.dirName}/SKILL.md`);
|
|
12335
|
-
if (!
|
|
12569
|
+
if (!existsSync21(newSkillPath)) continue;
|
|
12336
12570
|
const currentContent = readFileSync8(newSkillPath, "utf-8");
|
|
12337
12571
|
const updatedContent = injectCustomContent(currentContent, extracted.customContent);
|
|
12338
12572
|
writeFileSync3(newSkillPath, updatedContent, "utf-8");
|
|
@@ -12375,7 +12609,7 @@ async function syncFromMcp(options) {
|
|
|
12375
12609
|
if (!beforeManaged.has(file)) return false;
|
|
12376
12610
|
const before = beforeContents.get(file);
|
|
12377
12611
|
const currentPath = resolveWithinRoot(options.rootDir, file);
|
|
12378
|
-
if (!
|
|
12612
|
+
if (!existsSync21(currentPath)) return false;
|
|
12379
12613
|
const after = readFileSync8(currentPath, "utf-8");
|
|
12380
12614
|
return before !== after;
|
|
12381
12615
|
});
|
|
@@ -12464,7 +12698,7 @@ async function planSyncFromMcp(options) {
|
|
|
12464
12698
|
}
|
|
12465
12699
|
function readPersistedSkills(rootDir, metadata) {
|
|
12466
12700
|
const taxonomyPath = resolveWithinRoot(rootDir, MCP_TAXONOMY_PATH);
|
|
12467
|
-
if (
|
|
12701
|
+
if (existsSync21(taxonomyPath)) {
|
|
12468
12702
|
return JSON.parse(readFileSync8(taxonomyPath, "utf-8"));
|
|
12469
12703
|
}
|
|
12470
12704
|
return metadata.skills.map((skill) => ({
|
|
@@ -12477,12 +12711,12 @@ function readPersistedSkills(rootDir, metadata) {
|
|
|
12477
12711
|
function preserveCustomContentForRenames(rootDir, renames, pathForName) {
|
|
12478
12712
|
for (const [oldName, newName] of renames) {
|
|
12479
12713
|
const oldPath = resolveWithinRoot(rootDir, pathForName(oldName));
|
|
12480
|
-
if (!
|
|
12714
|
+
if (!existsSync21(oldPath)) continue;
|
|
12481
12715
|
const oldContent = readFileSync8(oldPath, "utf-8");
|
|
12482
12716
|
const extracted = extractMixedMarkdownContent(oldContent, "");
|
|
12483
12717
|
if (!hasMeaningfulCustomContent(oldContent)) continue;
|
|
12484
12718
|
const newPath = resolveWithinRoot(rootDir, pathForName(newName));
|
|
12485
|
-
if (!
|
|
12719
|
+
if (!existsSync21(newPath)) continue;
|
|
12486
12720
|
const currentContent = readFileSync8(newPath, "utf-8");
|
|
12487
12721
|
const updatedContent = injectCustomContent(currentContent, extracted.customContent);
|
|
12488
12722
|
writeFileSync3(newPath, updatedContent, "utf-8");
|
|
@@ -12492,21 +12726,21 @@ function snapshotManagedFiles(rootDir, files) {
|
|
|
12492
12726
|
const contents = /* @__PURE__ */ new Map();
|
|
12493
12727
|
for (const file of files) {
|
|
12494
12728
|
const filepath = resolveWithinRoot(rootDir, file);
|
|
12495
|
-
if (!
|
|
12729
|
+
if (!existsSync21(filepath)) continue;
|
|
12496
12730
|
contents.set(file, readFileSync8(filepath, "utf-8"));
|
|
12497
12731
|
}
|
|
12498
12732
|
return contents;
|
|
12499
12733
|
}
|
|
12500
12734
|
function removeManagedFile(rootDir, relativePath) {
|
|
12501
12735
|
const filepath = resolveWithinRoot(rootDir, relativePath);
|
|
12502
|
-
if (!
|
|
12736
|
+
if (!existsSync21(filepath)) return;
|
|
12503
12737
|
rmSync2(filepath, { force: true });
|
|
12504
|
-
pruneEmptyDirectories(rootDir,
|
|
12738
|
+
pruneEmptyDirectories(rootDir, dirname5(filepath));
|
|
12505
12739
|
}
|
|
12506
12740
|
function shouldPreserveManagedFile(rootDir, relativePath) {
|
|
12507
12741
|
if (!relativePath.endsWith(".md")) return false;
|
|
12508
12742
|
const filepath = resolveWithinRoot(rootDir, relativePath);
|
|
12509
|
-
if (!
|
|
12743
|
+
if (!existsSync21(filepath)) return false;
|
|
12510
12744
|
return hasMeaningfulCustomContent(readFileSync8(filepath, "utf-8"));
|
|
12511
12745
|
}
|
|
12512
12746
|
function pruneEmptyDirectories(rootDir, startDir) {
|
|
@@ -12516,7 +12750,7 @@ function pruneEmptyDirectories(rootDir, startDir) {
|
|
|
12516
12750
|
const entries = readdirSync6(current);
|
|
12517
12751
|
if (entries.length > 0) return;
|
|
12518
12752
|
rmdirSync(current);
|
|
12519
|
-
current =
|
|
12753
|
+
current = dirname5(current);
|
|
12520
12754
|
}
|
|
12521
12755
|
}
|
|
12522
12756
|
var AGENT_PACK_FILES = [
|
|
@@ -12680,7 +12914,7 @@ function computeSkillRenameScore(oldSkill, newSkill, toolRenames) {
|
|
|
12680
12914
|
function resolveWithinRoot(rootDir, relativePath) {
|
|
12681
12915
|
const rootPath = resolve13(rootDir);
|
|
12682
12916
|
const filepath = resolve13(rootPath, relativePath);
|
|
12683
|
-
const relativePathFromRoot =
|
|
12917
|
+
const relativePathFromRoot = relative8(rootPath, filepath);
|
|
12684
12918
|
if (relativePathFromRoot === "" || !relativePathFromRoot.startsWith("..") && !isAbsolute(relativePathFromRoot)) {
|
|
12685
12919
|
return filepath;
|
|
12686
12920
|
}
|
|
@@ -12696,13 +12930,13 @@ function formatSyncSummary(result, rootDir) {
|
|
|
12696
12930
|
result.renamedFiles.forEach((rename) => lines.push(` \u2192 ${rename.from} \u2192 ${rename.to}`));
|
|
12697
12931
|
}
|
|
12698
12932
|
lines.push(`Added: ${result.addedFiles.length}`);
|
|
12699
|
-
result.addedFiles.forEach((file) => lines.push(` + ${
|
|
12933
|
+
result.addedFiles.forEach((file) => lines.push(` + ${relative8(rootDir, resolve13(rootDir, file))}`));
|
|
12700
12934
|
lines.push(`Updated: ${result.updatedFiles.length}`);
|
|
12701
|
-
result.updatedFiles.forEach((file) => lines.push(` ~ ${
|
|
12935
|
+
result.updatedFiles.forEach((file) => lines.push(` ~ ${relative8(rootDir, resolve13(rootDir, file))}`));
|
|
12702
12936
|
lines.push(`Removed: ${result.removedFiles.length}`);
|
|
12703
|
-
result.removedFiles.forEach((file) => lines.push(` - ${
|
|
12937
|
+
result.removedFiles.forEach((file) => lines.push(` - ${relative8(rootDir, resolve13(rootDir, file))}`));
|
|
12704
12938
|
lines.push(`Preserved: ${result.preservedFiles.length}`);
|
|
12705
|
-
result.preservedFiles.forEach((file) => lines.push(` ! ${
|
|
12939
|
+
result.preservedFiles.forEach((file) => lines.push(` ! ${relative8(rootDir, resolve13(rootDir, file))}`));
|
|
12706
12940
|
return lines;
|
|
12707
12941
|
}
|
|
12708
12942
|
|
|
@@ -12791,7 +13025,7 @@ async function planAgentPrompt(rootDir, kind, options = {}) {
|
|
|
12791
13025
|
const project = await loadAgentProjectModel(rootDir, config);
|
|
12792
13026
|
const overrides = await loadAgentOverrides(rootDir);
|
|
12793
13027
|
const contextPath = resolve14(rootDir, AGENT_CONTEXT_PATH);
|
|
12794
|
-
if (!options.allowMissingContext && !
|
|
13028
|
+
if (!options.allowMissingContext && !existsSync22(contextPath)) {
|
|
12795
13029
|
throw new Error(`No agent context found at ${AGENT_CONTEXT_PATH}. Run "pluxx agent prepare" first.`);
|
|
12796
13030
|
}
|
|
12797
13031
|
if (project.sourceKind !== "mcp-derived" && kind !== "review") {
|
|
@@ -12803,7 +13037,7 @@ async function planAgentPrompt(rootDir, kind, options = {}) {
|
|
|
12803
13037
|
displayName: project.displayName,
|
|
12804
13038
|
skillPaths: project.skills.map((skill) => skill.path),
|
|
12805
13039
|
commandPaths: project.commands.map((command2) => command2.path),
|
|
12806
|
-
extraContextPaths: [AGENT_SOURCES_PATH, AGENT_DOCS_CONTEXT_PATH].filter((path) =>
|
|
13040
|
+
extraContextPaths: [AGENT_SOURCES_PATH, AGENT_DOCS_CONTEXT_PATH].filter((path) => existsSync22(resolve14(rootDir, path))),
|
|
12807
13041
|
sourceKind: project.sourceKind,
|
|
12808
13042
|
taxonomyPath: project.taxonomyPath,
|
|
12809
13043
|
overrides
|
|
@@ -12971,7 +13205,7 @@ function buildProtectedFiles() {
|
|
|
12971
13205
|
}
|
|
12972
13206
|
async function loadMcpScaffoldMetadata2(rootDir) {
|
|
12973
13207
|
const metadataPath = resolve14(rootDir, MCP_SCAFFOLD_METADATA_PATH);
|
|
12974
|
-
if (!
|
|
13208
|
+
if (!existsSync22(metadataPath)) {
|
|
12975
13209
|
throw new Error(`No MCP scaffold metadata found at ${MCP_SCAFFOLD_METADATA_PATH}. Run "pluxx init --from-mcp" first.`);
|
|
12976
13210
|
}
|
|
12977
13211
|
try {
|
|
@@ -12985,7 +13219,7 @@ async function loadMcpScaffoldMetadata2(rootDir) {
|
|
|
12985
13219
|
}
|
|
12986
13220
|
async function loadAgentProjectModel(rootDir, config) {
|
|
12987
13221
|
const metadataPath = resolve14(rootDir, MCP_SCAFFOLD_METADATA_PATH);
|
|
12988
|
-
if (
|
|
13222
|
+
if (existsSync22(metadataPath)) {
|
|
12989
13223
|
const metadata = await loadMcpScaffoldMetadata2(rootDir);
|
|
12990
13224
|
const serverEntry = Object.entries(config.mcp ?? {})[0];
|
|
12991
13225
|
const [serverName, server] = serverEntry ?? ["unknown", metadata.source];
|
|
@@ -13023,7 +13257,7 @@ function loadManualAgentProjectModel(rootDir, config) {
|
|
|
13023
13257
|
const commandsDir = config.commands ? resolve14(rootDir, config.commands) : void 0;
|
|
13024
13258
|
const skills = readCanonicalSkillFiles(rootDir, skillsDir);
|
|
13025
13259
|
const commands = readCanonicalCommandFiles(commandsDir).map((command2) => ({
|
|
13026
|
-
path: normalizeRelativePath(
|
|
13260
|
+
path: normalizeRelativePath(relative9(rootDir, command2.filePath)),
|
|
13027
13261
|
title: command2.title,
|
|
13028
13262
|
description: command2.description
|
|
13029
13263
|
}));
|
|
@@ -13354,7 +13588,7 @@ async function collectAgentContextPackInternal(rootDir, options, overrides) {
|
|
|
13354
13588
|
if (seenFilePaths.has(relativePath)) continue;
|
|
13355
13589
|
seenFilePaths.add(relativePath);
|
|
13356
13590
|
const filePath = resolve14(rootDir, relativePath);
|
|
13357
|
-
if (!
|
|
13591
|
+
if (!existsSync22(filePath)) {
|
|
13358
13592
|
const source2 = {
|
|
13359
13593
|
label: relativePath,
|
|
13360
13594
|
kind: "file",
|
|
@@ -14524,18 +14758,18 @@ function normalizeRelativePath(path) {
|
|
|
14524
14758
|
return path.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
14525
14759
|
}
|
|
14526
14760
|
function readCanonicalSkillFiles(rootDir, skillsDir) {
|
|
14527
|
-
if (!skillsDir || !
|
|
14761
|
+
if (!skillsDir || !existsSync22(skillsDir)) return [];
|
|
14528
14762
|
return walkSkillMarkdownFiles(skillsDir).sort((a, b) => a.localeCompare(b)).map((filePath) => {
|
|
14529
14763
|
const content = readFileSync9(filePath, "utf-8");
|
|
14530
14764
|
const { frontmatterLines, body } = splitSkillMarkdownFrontmatter(content);
|
|
14531
|
-
const dirName = normalizeRelativePath(
|
|
14765
|
+
const dirName = normalizeRelativePath(relative9(skillsDir, filePath).replace(/\/SKILL\.md$/i, ""));
|
|
14532
14766
|
const title = firstMarkdownHeading(body) ?? dirName;
|
|
14533
14767
|
return {
|
|
14534
14768
|
dirName,
|
|
14535
14769
|
title,
|
|
14536
14770
|
description: parseYamlDescription(frontmatterLines),
|
|
14537
14771
|
toolNames: [],
|
|
14538
|
-
path: normalizeRelativePath(
|
|
14772
|
+
path: normalizeRelativePath(relative9(rootDir, filePath))
|
|
14539
14773
|
};
|
|
14540
14774
|
});
|
|
14541
14775
|
}
|
|
@@ -14845,7 +15079,7 @@ async function executeCommand(command2, cwd, options = {}) {
|
|
|
14845
15079
|
let claudeTurnCompleted = false;
|
|
14846
15080
|
let claudeTurnFailed = false;
|
|
14847
15081
|
const sentinelInterval = codexLastMessagePath || isClaudeStreamJson ? setInterval(() => {
|
|
14848
|
-
const sawCompletionSignal = codexTurnCompleted || codexTurnFailed || claudeTurnCompleted || claudeTurnFailed || (codexLastMessagePath ?
|
|
15082
|
+
const sawCompletionSignal = codexTurnCompleted || codexTurnFailed || claudeTurnCompleted || claudeTurnFailed || (codexLastMessagePath ? existsSync22(codexLastMessagePath) : false);
|
|
14849
15083
|
if (!sawCompletionSignal) return;
|
|
14850
15084
|
if (sawFinalMessageAt == null) {
|
|
14851
15085
|
sawFinalMessageAt = Date.now();
|
|
@@ -14971,15 +15205,15 @@ exec ${shellQuote(cursorBinary)} "$@"
|
|
|
14971
15205
|
await mkdir3(resolve14(isolatedCodexHome, "memories"), { recursive: true });
|
|
14972
15206
|
for (const relativePath of ["auth.json", "config.toml", "hooks.json", "installation_id"]) {
|
|
14973
15207
|
const sourcePath = resolve14(currentCodexHome, relativePath);
|
|
14974
|
-
if (!
|
|
15208
|
+
if (!existsSync22(sourcePath)) continue;
|
|
14975
15209
|
await copyFile(sourcePath, resolve14(isolatedCodexHome, relativePath));
|
|
14976
15210
|
}
|
|
14977
15211
|
const rulesSourceDir = resolve14(currentCodexHome, "rules");
|
|
14978
|
-
if (
|
|
15212
|
+
if (existsSync22(rulesSourceDir)) {
|
|
14979
15213
|
const rulesTargetDir = resolve14(isolatedCodexHome, "rules");
|
|
14980
15214
|
await mkdir3(rulesTargetDir, { recursive: true });
|
|
14981
15215
|
const defaultRulesPath = resolve14(rulesSourceDir, "default.rules");
|
|
14982
|
-
if (
|
|
15216
|
+
if (existsSync22(defaultRulesPath)) {
|
|
14983
15217
|
await copyFile(defaultRulesPath, resolve14(rulesTargetDir, "default.rules"));
|
|
14984
15218
|
}
|
|
14985
15219
|
}
|
|
@@ -15017,7 +15251,7 @@ function titleCase(value) {
|
|
|
15017
15251
|
}
|
|
15018
15252
|
async function loadAgentOverrides(rootDir) {
|
|
15019
15253
|
const overridesPath = resolve14(rootDir, AGENT_OVERRIDES_PATH);
|
|
15020
|
-
if (!
|
|
15254
|
+
if (!existsSync22(overridesPath)) {
|
|
15021
15255
|
return null;
|
|
15022
15256
|
}
|
|
15023
15257
|
const content = await readTextFile(overridesPath);
|
|
@@ -15119,12 +15353,12 @@ ${additions.map((block) => `- ${block.replace(/\n/g, "\n ")}`).join("\n")}
|
|
|
15119
15353
|
}
|
|
15120
15354
|
|
|
15121
15355
|
// src/cli/doctor.ts
|
|
15122
|
-
import { accessSync, constants, existsSync as
|
|
15123
|
-
import { basename as basename6, dirname as
|
|
15356
|
+
import { accessSync, constants, existsSync as existsSync24, lstatSync as lstatSync3, readFileSync as readFileSync11, readdirSync as readdirSync9 } from "fs";
|
|
15357
|
+
import { basename as basename6, dirname as dirname7, resolve as resolve16 } from "path";
|
|
15124
15358
|
|
|
15125
15359
|
// src/cli/install.ts
|
|
15126
|
-
import { resolve as resolve15, dirname as
|
|
15127
|
-
import { existsSync as
|
|
15360
|
+
import { resolve as resolve15, dirname as dirname6 } from "path";
|
|
15361
|
+
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
15362
|
import { spawnSync } from "child_process";
|
|
15129
15363
|
import * as readline2 from "readline";
|
|
15130
15364
|
function listHookCommands(hooks) {
|
|
@@ -15173,6 +15407,10 @@ async function resolveInstallUserConfig(config, platforms = config.targets, opti
|
|
|
15173
15407
|
const isTTY = options.isTTY ?? process.stdin.isTTY === true;
|
|
15174
15408
|
for (const entry of planned) {
|
|
15175
15409
|
if (entry.value !== void 0) {
|
|
15410
|
+
if (entry.field.type === "secret" && isPlaceholderSecretValue(entry.value)) {
|
|
15411
|
+
const hint = entry.envVar ? ` Export a real value first: export ${entry.envVar}='your_real_key'. Then rerun pluxx install.` : " Provide a real secret value before installing.";
|
|
15412
|
+
throw new Error(`Refusing to install placeholder secret for userConfig "${entry.field.key}". Placeholder values like "dummy" are not usable by installed MCP servers.${hint}`);
|
|
15413
|
+
}
|
|
15176
15414
|
resolved.push({
|
|
15177
15415
|
field: entry.field,
|
|
15178
15416
|
value: entry.value,
|
|
@@ -15184,8 +15422,8 @@ async function resolveInstallUserConfig(config, platforms = config.targets, opti
|
|
|
15184
15422
|
continue;
|
|
15185
15423
|
}
|
|
15186
15424
|
if (!isTTY) {
|
|
15187
|
-
const hint = entry.envVar ? ` Export ${entry.envVar}
|
|
15188
|
-
throw new Error(`Missing required userConfig "${entry.field.key}".${hint}`);
|
|
15425
|
+
const hint = entry.envVar ? ` Export it before installing: export ${entry.envVar}='your_real_key'. Then rerun pluxx install.` : " Re-run interactively to provide it.";
|
|
15426
|
+
throw new Error(`Missing required userConfig "${entry.field.key}". Installed plugins cannot prompt for this value later in every host UI.${hint}`);
|
|
15189
15427
|
}
|
|
15190
15428
|
const promptLabel = entry.field.title || entry.field.key;
|
|
15191
15429
|
const envHint = entry.envVar ? ` [env: ${entry.envVar}]` : "";
|
|
@@ -15260,7 +15498,7 @@ async function ensureHookTrust(options) {
|
|
|
15260
15498
|
const isTTY = options.isTTY ?? process.stdin.isTTY === true;
|
|
15261
15499
|
if (!isTTY) {
|
|
15262
15500
|
throw new Error(
|
|
15263
|
-
`Refusing to install plugin with hooks in non-interactive mode. Re-run with --trust
|
|
15501
|
+
`Refusing to install plugin with hooks in non-interactive mode. Review the hook commands above. Re-run with --trust if you trust this plugin author.`
|
|
15264
15502
|
);
|
|
15265
15503
|
}
|
|
15266
15504
|
const confirm = options.confirmPrompt ?? promptTrustConfirmation;
|
|
@@ -15393,12 +15631,12 @@ ${content.slice(frontmatterMatch[0].length)}`;
|
|
|
15393
15631
|
}
|
|
15394
15632
|
function syncOpenCodeSkills(pluginDir, pluginName) {
|
|
15395
15633
|
const sourceSkillsDir = resolve15(pluginDir, "skills");
|
|
15396
|
-
if (!
|
|
15634
|
+
if (!existsSync23(sourceSkillsDir)) return;
|
|
15397
15635
|
mkdirSync4(getOpenCodeSkillRoot(), { recursive: true });
|
|
15398
15636
|
for (const entry of readdirSync8(sourceSkillsDir, { withFileTypes: true })) {
|
|
15399
15637
|
if (!entry.isDirectory()) continue;
|
|
15400
15638
|
const skillSourceDir = resolve15(sourceSkillsDir, entry.name);
|
|
15401
|
-
if (!
|
|
15639
|
+
if (!existsSync23(resolve15(skillSourceDir, "SKILL.md"))) continue;
|
|
15402
15640
|
const installedSkillDir = getOpenCodeInstalledSkillDir(pluginName, entry.name);
|
|
15403
15641
|
rmSync3(installedSkillDir, { recursive: true, force: true });
|
|
15404
15642
|
cpSync3(skillSourceDir, installedSkillDir, { recursive: true });
|
|
@@ -15411,7 +15649,7 @@ function syncOpenCodeSkills(pluginDir, pluginName) {
|
|
|
15411
15649
|
}
|
|
15412
15650
|
function verifyOpenCodeInstall(pluginDir, pluginName) {
|
|
15413
15651
|
const entryPath = getOpenCodeEntryPath(pluginDir);
|
|
15414
|
-
if (!
|
|
15652
|
+
if (!existsSync23(entryPath)) {
|
|
15415
15653
|
throw new Error(`OpenCode install is incomplete: missing host entry file at ${entryPath}`);
|
|
15416
15654
|
}
|
|
15417
15655
|
const entryContent = readFileSync10(entryPath, "utf-8");
|
|
@@ -15424,13 +15662,13 @@ function verifyOpenCodeInstall(pluginDir, pluginName) {
|
|
|
15424
15662
|
throw new Error(`OpenCode install is incomplete: ${entryPath} does not preserve the plugin root bridge`);
|
|
15425
15663
|
}
|
|
15426
15664
|
const sourceSkillsDir = resolve15(pluginDir, "skills");
|
|
15427
|
-
if (!
|
|
15665
|
+
if (!existsSync23(sourceSkillsDir)) return;
|
|
15428
15666
|
for (const entry of readdirSync8(sourceSkillsDir, { withFileTypes: true })) {
|
|
15429
15667
|
if (!entry.isDirectory()) continue;
|
|
15430
15668
|
const sourceSkillPath = resolve15(sourceSkillsDir, entry.name, "SKILL.md");
|
|
15431
|
-
if (!
|
|
15669
|
+
if (!existsSync23(sourceSkillPath)) continue;
|
|
15432
15670
|
const installedSkillPath = resolve15(getOpenCodeInstalledSkillDir(pluginName, entry.name), "SKILL.md");
|
|
15433
|
-
if (!
|
|
15671
|
+
if (!existsSync23(installedSkillPath)) {
|
|
15434
15672
|
throw new Error(`OpenCode install is incomplete: missing synced skill at ${installedSkillPath}`);
|
|
15435
15673
|
}
|
|
15436
15674
|
const installedSkillContent = readFileSync10(installedSkillPath, "utf-8");
|
|
@@ -15441,7 +15679,7 @@ function verifyOpenCodeInstall(pluginDir, pluginName) {
|
|
|
15441
15679
|
}
|
|
15442
15680
|
function removeOpenCodeSkills(pluginName) {
|
|
15443
15681
|
const root = getOpenCodeSkillRoot();
|
|
15444
|
-
if (!
|
|
15682
|
+
if (!existsSync23(root)) return false;
|
|
15445
15683
|
let removed = false;
|
|
15446
15684
|
for (const entry of readdirSync8(root, { withFileTypes: true })) {
|
|
15447
15685
|
if (!entry.name.startsWith(`${pluginName}-`)) continue;
|
|
@@ -15459,7 +15697,7 @@ function getInstallFollowupNotes(platforms) {
|
|
|
15459
15697
|
notes.push("Cursor note: if Cursor is already open, use Developer: Reload Window or restart Cursor to pick up the new install.");
|
|
15460
15698
|
}
|
|
15461
15699
|
if (platforms.includes("codex")) {
|
|
15462
|
-
notes.push("Codex note: if Codex is already open, use Plugins > Refresh if that action is available in your current UI, or restart Codex to pick up the new install.");
|
|
15700
|
+
notes.push("Codex note: if Codex is already open, use Plugins > Refresh if that action is available in your current UI, or restart Codex to pick up the new install. Plugin-bundled MCP servers may appear on the plugin detail page without appearing in the global MCP servers settings page.");
|
|
15463
15701
|
}
|
|
15464
15702
|
if (platforms.includes("opencode")) {
|
|
15465
15703
|
notes.push("OpenCode note: if OpenCode is already open, restart or reload it so the plugin is picked up.");
|
|
@@ -15477,7 +15715,7 @@ function runCommandDefault(command2, args2) {
|
|
|
15477
15715
|
function createSymlinkInstall(target) {
|
|
15478
15716
|
const parentDir = resolve15(target.pluginDir, "..");
|
|
15479
15717
|
mkdirSync4(parentDir, { recursive: true });
|
|
15480
|
-
if (
|
|
15718
|
+
if (existsSync23(target.pluginDir)) {
|
|
15481
15719
|
rmSync3(target.pluginDir, { recursive: true, force: true });
|
|
15482
15720
|
}
|
|
15483
15721
|
symlinkSync(target.sourceDir, target.pluginDir);
|
|
@@ -15495,7 +15733,7 @@ function getCodexMarketplacePluginPath(pluginName) {
|
|
|
15495
15733
|
return `./.codex/plugins/${pluginName}`;
|
|
15496
15734
|
}
|
|
15497
15735
|
function readCodexMarketplace(filepath) {
|
|
15498
|
-
if (!
|
|
15736
|
+
if (!existsSync23(filepath)) {
|
|
15499
15737
|
return {
|
|
15500
15738
|
name: "pluxx-local",
|
|
15501
15739
|
interface: {
|
|
@@ -15514,7 +15752,7 @@ function readCodexMarketplace(filepath) {
|
|
|
15514
15752
|
}
|
|
15515
15753
|
function ensureCodexMarketplace(pluginName) {
|
|
15516
15754
|
const filepath = getCodexMarketplacePath();
|
|
15517
|
-
mkdirSync4(
|
|
15755
|
+
mkdirSync4(dirname6(filepath), { recursive: true });
|
|
15518
15756
|
const marketplace = readCodexMarketplace(filepath);
|
|
15519
15757
|
const nextPlugins = (marketplace.plugins ?? []).filter((plugin) => plugin.name !== pluginName);
|
|
15520
15758
|
nextPlugins.push({
|
|
@@ -15540,7 +15778,7 @@ function ensureCodexMarketplace(pluginName) {
|
|
|
15540
15778
|
}
|
|
15541
15779
|
function removeCodexMarketplacePlugin(pluginName) {
|
|
15542
15780
|
const filepath = getCodexMarketplacePath();
|
|
15543
|
-
if (!
|
|
15781
|
+
if (!existsSync23(filepath)) return;
|
|
15544
15782
|
const marketplace = readCodexMarketplace(filepath);
|
|
15545
15783
|
const nextPlugins = (marketplace.plugins ?? []).filter((plugin) => plugin.name !== pluginName);
|
|
15546
15784
|
if (nextPlugins.length === (marketplace.plugins ?? []).length) {
|
|
@@ -15562,7 +15800,7 @@ function removeCodexMarketplacePlugin(pluginName) {
|
|
|
15562
15800
|
function createCopiedInstall(target) {
|
|
15563
15801
|
const parentDir = resolve15(target.pluginDir, "..");
|
|
15564
15802
|
mkdirSync4(parentDir, { recursive: true });
|
|
15565
|
-
if (
|
|
15803
|
+
if (existsSync23(target.pluginDir)) {
|
|
15566
15804
|
rmSync3(target.pluginDir, { recursive: true, force: true });
|
|
15567
15805
|
}
|
|
15568
15806
|
cpSync3(target.sourceDir, target.pluginDir, { recursive: true });
|
|
@@ -15587,7 +15825,7 @@ function patchInstalledMcpConfig(pluginDir, platform, config, entries) {
|
|
|
15587
15825
|
const env = buildUserConfigEnvMap(entries);
|
|
15588
15826
|
if (platform === "claude-code" || platform === "cursor") {
|
|
15589
15827
|
const filepath = resolve15(pluginDir, platform === "claude-code" ? ".mcp.json" : "mcp.json");
|
|
15590
|
-
if (!
|
|
15828
|
+
if (!existsSync23(filepath)) return;
|
|
15591
15829
|
const mcpServers = {};
|
|
15592
15830
|
const usesPlatformManagedAuth = platform === "claude-code" ? config.platforms?.["claude-code"]?.mcpAuth === "platform" : config.platforms?.cursor?.mcpAuth === "platform";
|
|
15593
15831
|
for (const [name, server] of Object.entries(config.mcp)) {
|
|
@@ -15619,7 +15857,7 @@ function patchInstalledMcpConfig(pluginDir, platform, config, entries) {
|
|
|
15619
15857
|
}
|
|
15620
15858
|
if (platform === "codex") {
|
|
15621
15859
|
const filepath = resolve15(pluginDir, ".mcp.json");
|
|
15622
|
-
if (!
|
|
15860
|
+
if (!existsSync23(filepath)) return;
|
|
15623
15861
|
const mcpServers = {};
|
|
15624
15862
|
for (const [name, server] of Object.entries(config.mcp)) {
|
|
15625
15863
|
if (server.transport === "stdio") {
|
|
@@ -15659,7 +15897,7 @@ function writeInstalledUserConfig(pluginDir, entries) {
|
|
|
15659
15897
|
function disableInstalledEnvValidation(pluginDir, entries) {
|
|
15660
15898
|
if (entries.length === 0) return;
|
|
15661
15899
|
const filepath = resolve15(pluginDir, "scripts/check-env.sh");
|
|
15662
|
-
if (!
|
|
15900
|
+
if (!existsSync23(filepath)) return;
|
|
15663
15901
|
writeFileSync4(
|
|
15664
15902
|
filepath,
|
|
15665
15903
|
"#!/usr/bin/env bash\nset -euo pipefail\n# pluxx install materialized required config for this local plugin install.\nexit 0\n"
|
|
@@ -15679,11 +15917,159 @@ function getClaudeMarketplaceRoot(pluginName) {
|
|
|
15679
15917
|
return resolve15(home, ".claude/plugins/data", getClaudeMarketplaceName(pluginName));
|
|
15680
15918
|
}
|
|
15681
15919
|
function resolveInstalledConsumerPath(target, pluginName) {
|
|
15682
|
-
if (target.platform === "claude-code") {
|
|
15683
|
-
return
|
|
15920
|
+
if (target.platform === "claude-code" && pluginName !== "") {
|
|
15921
|
+
return target.pluginDir;
|
|
15684
15922
|
}
|
|
15685
15923
|
return target.pluginDir;
|
|
15686
15924
|
}
|
|
15925
|
+
function manifestPathForPlatform(platform) {
|
|
15926
|
+
switch (platform) {
|
|
15927
|
+
case "claude-code":
|
|
15928
|
+
return ".claude-plugin/plugin.json";
|
|
15929
|
+
case "cursor":
|
|
15930
|
+
return ".cursor-plugin/plugin.json";
|
|
15931
|
+
case "codex":
|
|
15932
|
+
return ".codex-plugin/plugin.json";
|
|
15933
|
+
case "opencode":
|
|
15934
|
+
return "package.json";
|
|
15935
|
+
default:
|
|
15936
|
+
return void 0;
|
|
15937
|
+
}
|
|
15938
|
+
}
|
|
15939
|
+
function isRelativeBundlePath(value) {
|
|
15940
|
+
return value.startsWith("./") || value.startsWith("../") || value.startsWith(".\\") || value.startsWith("..\\");
|
|
15941
|
+
}
|
|
15942
|
+
function resolveBundleReference(rootDir, value) {
|
|
15943
|
+
if (isRelativeBundlePath(value)) {
|
|
15944
|
+
return resolve15(rootDir, value);
|
|
15945
|
+
}
|
|
15946
|
+
const pluginRootMatch = value.match(/^\$\{(?:CLAUDE_PLUGIN_ROOT|CURSOR_PLUGIN_ROOT|PLUGIN_ROOT)\}[\\/](.+)$/);
|
|
15947
|
+
if (pluginRootMatch) {
|
|
15948
|
+
return resolve15(rootDir, pluginRootMatch[1]);
|
|
15949
|
+
}
|
|
15950
|
+
return void 0;
|
|
15951
|
+
}
|
|
15952
|
+
function readBundleManifestReferences(manifest) {
|
|
15953
|
+
const references = [];
|
|
15954
|
+
for (const key of ["commands", "skills", "hooks", "mcpServers"]) {
|
|
15955
|
+
const value = manifest[key];
|
|
15956
|
+
if (typeof value === "string") {
|
|
15957
|
+
references.push(value);
|
|
15958
|
+
}
|
|
15959
|
+
}
|
|
15960
|
+
const agents = manifest.agents;
|
|
15961
|
+
if (typeof agents === "string") {
|
|
15962
|
+
references.push(agents);
|
|
15963
|
+
} else if (Array.isArray(agents)) {
|
|
15964
|
+
for (const entry of agents) {
|
|
15965
|
+
if (typeof entry === "string") {
|
|
15966
|
+
references.push(entry);
|
|
15967
|
+
}
|
|
15968
|
+
}
|
|
15969
|
+
}
|
|
15970
|
+
return references;
|
|
15971
|
+
}
|
|
15972
|
+
function collectHookCommandStrings(value, commands) {
|
|
15973
|
+
if (Array.isArray(value)) {
|
|
15974
|
+
for (const entry of value) {
|
|
15975
|
+
collectHookCommandStrings(entry, commands);
|
|
15976
|
+
}
|
|
15977
|
+
return;
|
|
15978
|
+
}
|
|
15979
|
+
if (!value || typeof value !== "object") return;
|
|
15980
|
+
for (const [key, child] of Object.entries(value)) {
|
|
15981
|
+
if (key === "command" && typeof child === "string") {
|
|
15982
|
+
commands.push(child);
|
|
15983
|
+
continue;
|
|
15984
|
+
}
|
|
15985
|
+
collectHookCommandStrings(child, commands);
|
|
15986
|
+
}
|
|
15987
|
+
}
|
|
15988
|
+
function extractBundleCommandTargets(command2) {
|
|
15989
|
+
const matches = command2.match(/\$\{(?:CLAUDE_PLUGIN_ROOT|CURSOR_PLUGIN_ROOT|PLUGIN_ROOT)\}[\\/][^\s"'`;$|&<>]+|\.\.?[\\/][^\s"'`;$|&<>]+/g);
|
|
15990
|
+
return matches ?? [];
|
|
15991
|
+
}
|
|
15992
|
+
function findInstalledBundleIntegrityIssues(rootDir, platform) {
|
|
15993
|
+
const manifestPath = manifestPathForPlatform(platform);
|
|
15994
|
+
if (!manifestPath) {
|
|
15995
|
+
return {
|
|
15996
|
+
missingManifestPaths: [],
|
|
15997
|
+
missingHookTargets: []
|
|
15998
|
+
};
|
|
15999
|
+
}
|
|
16000
|
+
const manifestFile = resolve15(rootDir, manifestPath);
|
|
16001
|
+
if (!existsSync23(manifestFile)) {
|
|
16002
|
+
return {
|
|
16003
|
+
manifestIssue: `missing plugin manifest at ${manifestPath}`,
|
|
16004
|
+
missingManifestPaths: [],
|
|
16005
|
+
missingHookTargets: []
|
|
16006
|
+
};
|
|
16007
|
+
}
|
|
16008
|
+
let manifest;
|
|
16009
|
+
try {
|
|
16010
|
+
manifest = JSON.parse(readFileSync10(manifestFile, "utf-8"));
|
|
16011
|
+
} catch (error) {
|
|
16012
|
+
return {
|
|
16013
|
+
manifestIssue: `plugin manifest at ${manifestPath} is not parseable: ${error instanceof Error ? error.message : String(error)}`,
|
|
16014
|
+
missingManifestPaths: [],
|
|
16015
|
+
missingHookTargets: []
|
|
16016
|
+
};
|
|
16017
|
+
}
|
|
16018
|
+
const missingManifestPaths = readBundleManifestReferences(manifest).filter((value) => {
|
|
16019
|
+
const resolved = resolveBundleReference(rootDir, value);
|
|
16020
|
+
return resolved !== void 0 && !existsSync23(resolved);
|
|
16021
|
+
}).sort();
|
|
16022
|
+
const hooksReference = typeof manifest.hooks === "string" ? manifest.hooks : void 0;
|
|
16023
|
+
if (!hooksReference) {
|
|
16024
|
+
return {
|
|
16025
|
+
missingManifestPaths,
|
|
16026
|
+
missingHookTargets: []
|
|
16027
|
+
};
|
|
16028
|
+
}
|
|
16029
|
+
const hooksPath = resolveBundleReference(rootDir, hooksReference);
|
|
16030
|
+
if (!hooksPath || !existsSync23(hooksPath)) {
|
|
16031
|
+
return {
|
|
16032
|
+
missingManifestPaths,
|
|
16033
|
+
missingHookTargets: []
|
|
16034
|
+
};
|
|
16035
|
+
}
|
|
16036
|
+
try {
|
|
16037
|
+
const hooks = JSON.parse(readFileSync10(hooksPath, "utf-8"));
|
|
16038
|
+
const commands = [];
|
|
16039
|
+
collectHookCommandStrings(hooks, commands);
|
|
16040
|
+
const missingHookTargets = [...new Set(
|
|
16041
|
+
commands.flatMap(extractBundleCommandTargets).filter((value) => {
|
|
16042
|
+
const resolved = resolveBundleReference(rootDir, value);
|
|
16043
|
+
return resolved !== void 0 && !existsSync23(resolved);
|
|
16044
|
+
})
|
|
16045
|
+
)].sort();
|
|
16046
|
+
return {
|
|
16047
|
+
missingManifestPaths,
|
|
16048
|
+
missingHookTargets
|
|
16049
|
+
};
|
|
16050
|
+
} catch {
|
|
16051
|
+
return {
|
|
16052
|
+
missingManifestPaths,
|
|
16053
|
+
missingHookTargets: []
|
|
16054
|
+
};
|
|
16055
|
+
}
|
|
16056
|
+
}
|
|
16057
|
+
function assertInstalledBundleIntegrity(rootDir, platform, label) {
|
|
16058
|
+
const issues = findInstalledBundleIntegrityIssues(rootDir, platform);
|
|
16059
|
+
const details = [];
|
|
16060
|
+
if (issues.manifestIssue) {
|
|
16061
|
+
details.push(issues.manifestIssue);
|
|
16062
|
+
}
|
|
16063
|
+
if (issues.missingManifestPaths.length > 0) {
|
|
16064
|
+
details.push(`manifest paths missing: ${issues.missingManifestPaths.join(", ")}`);
|
|
16065
|
+
}
|
|
16066
|
+
if (issues.missingHookTargets.length > 0) {
|
|
16067
|
+
details.push(`hook targets missing: ${issues.missingHookTargets.join(", ")}`);
|
|
16068
|
+
}
|
|
16069
|
+
if (details.length > 0) {
|
|
16070
|
+
throw new Error(`${label} is incomplete: ${details.join("; ")}`);
|
|
16071
|
+
}
|
|
16072
|
+
}
|
|
15687
16073
|
function ensureClaudeMarketplace(pluginName, sourceDir, materialized) {
|
|
15688
16074
|
const marketplaceName = getClaudeMarketplaceName(pluginName);
|
|
15689
16075
|
const marketplaceRoot = getClaudeMarketplaceRoot(pluginName);
|
|
@@ -15694,12 +16080,11 @@ function ensureClaudeMarketplace(pluginName, sourceDir, materialized) {
|
|
|
15694
16080
|
rmSync3(marketplaceRoot, { recursive: true, force: true });
|
|
15695
16081
|
mkdirSync4(marketplaceManifestDir, { recursive: true });
|
|
15696
16082
|
mkdirSync4(resolve15(marketplaceRoot, "plugins"), { recursive: true });
|
|
16083
|
+
cpSync3(sourceDir, marketplacePluginDir, { recursive: true });
|
|
15697
16084
|
if (materialized && materialized.entries.length > 0) {
|
|
15698
|
-
cpSync3(sourceDir, marketplacePluginDir, { recursive: true });
|
|
15699
16085
|
materializeInstalledPlugin(marketplacePluginDir, "claude-code", materialized.config, materialized.entries);
|
|
15700
|
-
} else {
|
|
15701
|
-
symlinkSync(sourceDir, marketplacePluginDir);
|
|
15702
16086
|
}
|
|
16087
|
+
assertInstalledBundleIntegrity(marketplacePluginDir, "claude-code", "Claude marketplace bundle");
|
|
15703
16088
|
writeFileSync4(
|
|
15704
16089
|
resolve15(marketplaceManifestDir, "marketplace.json"),
|
|
15705
16090
|
JSON.stringify({
|
|
@@ -15741,7 +16126,7 @@ function ensureClaudeMarketplaceRegistered(pluginName, sourceDir, runCommand, ma
|
|
|
15741
16126
|
}
|
|
15742
16127
|
function installClaudePlugin(target, pluginName, runCommand, materialized) {
|
|
15743
16128
|
const marketplaceName = ensureClaudeMarketplaceRegistered(pluginName, target.sourceDir, runCommand, materialized);
|
|
15744
|
-
if (
|
|
16129
|
+
if (existsSync23(target.pluginDir)) {
|
|
15745
16130
|
rmSync3(target.pluginDir, { recursive: true, force: true });
|
|
15746
16131
|
}
|
|
15747
16132
|
runCommand("claude", ["plugin", "uninstall", `${pluginName}@${marketplaceName}`]);
|
|
@@ -15749,6 +16134,7 @@ function installClaudePlugin(target, pluginName, runCommand, materialized) {
|
|
|
15749
16134
|
if (install.status !== 0) {
|
|
15750
16135
|
throw new Error(`Failed to install Claude plugin: ${install.stderr || install.stdout}`);
|
|
15751
16136
|
}
|
|
16137
|
+
assertInstalledBundleIntegrity(target.pluginDir, "claude-code", "Installed Claude plugin bundle");
|
|
15752
16138
|
}
|
|
15753
16139
|
function uninstallClaudePlugin(target, pluginName, runCommand, options = {}) {
|
|
15754
16140
|
const marketplaceName = getClaudeMarketplaceName(pluginName);
|
|
@@ -15760,9 +16146,9 @@ function uninstallClaudePlugin(target, pluginName, runCommand, options = {}) {
|
|
|
15760
16146
|
}
|
|
15761
16147
|
}
|
|
15762
16148
|
const marketplaceRoot = getClaudeMarketplaceRoot(pluginName);
|
|
15763
|
-
const hadMarketplaceRoot =
|
|
16149
|
+
const hadMarketplaceRoot = existsSync23(marketplaceRoot);
|
|
15764
16150
|
rmSync3(marketplaceRoot, { recursive: true, force: true });
|
|
15765
|
-
const hadLegacyPluginDir =
|
|
16151
|
+
const hadLegacyPluginDir = existsSync23(target.pluginDir);
|
|
15766
16152
|
if (hadLegacyPluginDir) {
|
|
15767
16153
|
rmSync3(target.pluginDir, { recursive: true, force: true });
|
|
15768
16154
|
}
|
|
@@ -15776,8 +16162,8 @@ function planInstallPlugin(distDir, pluginName, platforms) {
|
|
|
15776
16162
|
return {
|
|
15777
16163
|
...target,
|
|
15778
16164
|
sourceDir,
|
|
15779
|
-
built:
|
|
15780
|
-
existing:
|
|
16165
|
+
built: existsSync23(sourceDir),
|
|
16166
|
+
existing: existsSync23(target.pluginDir)
|
|
15781
16167
|
};
|
|
15782
16168
|
});
|
|
15783
16169
|
}
|
|
@@ -15830,7 +16216,11 @@ async function installPlugin(distDir, pluginName, platforms, options = {}) {
|
|
|
15830
16216
|
console.log("Nothing to install. Run `pluxx build` first.");
|
|
15831
16217
|
} else if (!options.quiet) {
|
|
15832
16218
|
console.log(`
|
|
15833
|
-
Installed ${installed} plugin(s)
|
|
16219
|
+
Installed ${installed} plugin(s).`);
|
|
16220
|
+
console.log("Next checks:");
|
|
16221
|
+
console.log(` 1. Run: pluxx verify-install --target ${filtered.map((target) => target.platform).join(",")}`);
|
|
16222
|
+
console.log(" 2. Open the host plugin screen and confirm the plugin appears there.");
|
|
16223
|
+
console.log(" 3. Reload or restart the host if it was already open.");
|
|
15834
16224
|
for (const note of getInstallFollowupNotes(filtered.map((target) => target.platform))) {
|
|
15835
16225
|
console.log(note);
|
|
15836
16226
|
}
|
|
@@ -15853,13 +16243,13 @@ async function uninstallPlugin(pluginName, platforms, options = {}) {
|
|
|
15853
16243
|
continue;
|
|
15854
16244
|
}
|
|
15855
16245
|
let removedTarget = false;
|
|
15856
|
-
if (
|
|
16246
|
+
if (existsSync23(target.pluginDir)) {
|
|
15857
16247
|
rmSync3(target.pluginDir, { recursive: true, force: true });
|
|
15858
16248
|
removedTarget = true;
|
|
15859
16249
|
}
|
|
15860
16250
|
if (target.platform === "opencode") {
|
|
15861
16251
|
const entryPath = getOpenCodeEntryPath(target.pluginDir);
|
|
15862
|
-
if (
|
|
16252
|
+
if (existsSync23(entryPath)) {
|
|
15863
16253
|
rmSync3(entryPath, { force: true });
|
|
15864
16254
|
removedTarget = true;
|
|
15865
16255
|
}
|
|
@@ -15969,7 +16359,7 @@ function checkReadablePath(checks, rootDir, label, configuredPath, required) {
|
|
|
15969
16359
|
return;
|
|
15970
16360
|
}
|
|
15971
16361
|
const resolvedPath = resolve16(rootDir, configuredPath);
|
|
15972
|
-
if (!
|
|
16362
|
+
if (!existsSync24(resolvedPath)) {
|
|
15973
16363
|
addCheck2(checks, {
|
|
15974
16364
|
level: "error",
|
|
15975
16365
|
code: "path-not-found",
|
|
@@ -16335,7 +16725,7 @@ function checkCompilerIntent(checks, rootDir) {
|
|
|
16335
16725
|
}
|
|
16336
16726
|
function checkScaffoldMetadata(checks, rootDir, config) {
|
|
16337
16727
|
const metadataPath = resolve16(rootDir, MCP_SCAFFOLD_METADATA_PATH);
|
|
16338
|
-
if (!
|
|
16728
|
+
if (!existsSync24(metadataPath)) {
|
|
16339
16729
|
addCheck2(checks, {
|
|
16340
16730
|
level: "info",
|
|
16341
16731
|
code: "mcp-metadata-missing",
|
|
@@ -16400,7 +16790,7 @@ function checkScaffoldMetadata(checks, rootDir, config) {
|
|
|
16400
16790
|
}
|
|
16401
16791
|
}
|
|
16402
16792
|
function detectConsumerLayout(rootDir) {
|
|
16403
|
-
if (
|
|
16793
|
+
if (existsSync24(resolve16(rootDir, ".claude-plugin/plugin.json"))) {
|
|
16404
16794
|
return {
|
|
16405
16795
|
kind: "installed-platform",
|
|
16406
16796
|
platform: "claude-code",
|
|
@@ -16408,7 +16798,7 @@ function detectConsumerLayout(rootDir) {
|
|
|
16408
16798
|
mcpConfigPath: ".mcp.json"
|
|
16409
16799
|
};
|
|
16410
16800
|
}
|
|
16411
|
-
if (
|
|
16801
|
+
if (existsSync24(resolve16(rootDir, ".cursor-plugin/plugin.json"))) {
|
|
16412
16802
|
return {
|
|
16413
16803
|
kind: "installed-platform",
|
|
16414
16804
|
platform: "cursor",
|
|
@@ -16416,7 +16806,7 @@ function detectConsumerLayout(rootDir) {
|
|
|
16416
16806
|
mcpConfigPath: "mcp.json"
|
|
16417
16807
|
};
|
|
16418
16808
|
}
|
|
16419
|
-
if (
|
|
16809
|
+
if (existsSync24(resolve16(rootDir, ".codex-plugin/plugin.json"))) {
|
|
16420
16810
|
return {
|
|
16421
16811
|
kind: "installed-platform",
|
|
16422
16812
|
platform: "codex",
|
|
@@ -16426,7 +16816,7 @@ function detectConsumerLayout(rootDir) {
|
|
|
16426
16816
|
}
|
|
16427
16817
|
const packagePath = resolve16(rootDir, "package.json");
|
|
16428
16818
|
const indexPath = resolve16(rootDir, "index.ts");
|
|
16429
|
-
if (
|
|
16819
|
+
if (existsSync24(packagePath) && existsSync24(indexPath)) {
|
|
16430
16820
|
try {
|
|
16431
16821
|
const pkg = JSON.parse(readFileSync11(packagePath, "utf-8"));
|
|
16432
16822
|
if (pkg.peerDependencies?.["@opencode-ai/plugin"] || pkg.keywords?.includes("opencode-plugin")) {
|
|
@@ -16444,10 +16834,10 @@ function detectConsumerLayout(rootDir) {
|
|
|
16444
16834
|
};
|
|
16445
16835
|
}
|
|
16446
16836
|
}
|
|
16447
|
-
if (CONFIG_FILES.some((filename) =>
|
|
16837
|
+
if (CONFIG_FILES.some((filename) => existsSync24(resolve16(rootDir, filename)))) {
|
|
16448
16838
|
return { kind: "source-project" };
|
|
16449
16839
|
}
|
|
16450
|
-
if (["claude-code", "cursor", "codex", "opencode"].some((dir) =>
|
|
16840
|
+
if (["claude-code", "cursor", "codex", "opencode"].some((dir) => existsSync24(resolve16(rootDir, dir)))) {
|
|
16451
16841
|
return { kind: "multi-target-dist" };
|
|
16452
16842
|
}
|
|
16453
16843
|
return { kind: "unknown" };
|
|
@@ -16458,7 +16848,7 @@ function readJsonFile2(rootDir, relativePath) {
|
|
|
16458
16848
|
function checkConsumerBundlePath(checks, rootDir) {
|
|
16459
16849
|
try {
|
|
16460
16850
|
accessSync(rootDir, constants.R_OK);
|
|
16461
|
-
const details =
|
|
16851
|
+
const details = lstatSync3(rootDir);
|
|
16462
16852
|
addCheck2(checks, {
|
|
16463
16853
|
level: "success",
|
|
16464
16854
|
code: "consumer-path-readable",
|
|
@@ -16505,7 +16895,7 @@ function checkConsumerManifest(checks, rootDir, layout) {
|
|
|
16505
16895
|
function checkInstalledUserConfig(checks, rootDir) {
|
|
16506
16896
|
const userConfigPath = ".pluxx-user.json";
|
|
16507
16897
|
const resolvedPath = resolve16(rootDir, userConfigPath);
|
|
16508
|
-
if (!
|
|
16898
|
+
if (!existsSync24(resolvedPath)) {
|
|
16509
16899
|
addCheck2(checks, {
|
|
16510
16900
|
level: "info",
|
|
16511
16901
|
code: "consumer-user-config-missing",
|
|
@@ -16520,6 +16910,10 @@ function checkInstalledUserConfig(checks, rootDir) {
|
|
|
16520
16910
|
const payload = JSON.parse(readFileSync11(resolvedPath, "utf-8"));
|
|
16521
16911
|
const valueCount = Object.keys(payload.values ?? {}).length;
|
|
16522
16912
|
const envCount = Object.keys(payload.env ?? {}).length;
|
|
16913
|
+
const placeholderKeys = [
|
|
16914
|
+
...Object.entries(payload.values ?? {}).filter(([, value]) => isPlaceholderSecretValue(value)).map(([key]) => key),
|
|
16915
|
+
...Object.entries(payload.env ?? {}).filter(([, value]) => isPlaceholderSecretValue(value)).map(([key]) => key)
|
|
16916
|
+
];
|
|
16523
16917
|
addCheck2(checks, {
|
|
16524
16918
|
level: "success",
|
|
16525
16919
|
code: "consumer-user-config-valid",
|
|
@@ -16528,6 +16922,16 @@ function checkInstalledUserConfig(checks, rootDir) {
|
|
|
16528
16922
|
fix: "No action needed.",
|
|
16529
16923
|
path: userConfigPath
|
|
16530
16924
|
});
|
|
16925
|
+
if (placeholderKeys.length > 0) {
|
|
16926
|
+
addCheck2(checks, {
|
|
16927
|
+
level: "warning",
|
|
16928
|
+
code: "consumer-user-config-placeholder-secret",
|
|
16929
|
+
title: "Local install config contains placeholder-looking secret values",
|
|
16930
|
+
detail: `.pluxx-user.json contains placeholder-looking value${placeholderKeys.length === 1 ? "" : "s"} for ${placeholderKeys.join(", ")}.`,
|
|
16931
|
+
fix: "Reinstall the plugin with real secret values, or edit .pluxx-user.json and refresh/restart the host.",
|
|
16932
|
+
path: userConfigPath
|
|
16933
|
+
});
|
|
16934
|
+
}
|
|
16531
16935
|
} catch (error) {
|
|
16532
16936
|
addCheck2(checks, {
|
|
16533
16937
|
level: "error",
|
|
@@ -16542,7 +16946,7 @@ function checkInstalledUserConfig(checks, rootDir) {
|
|
|
16542
16946
|
function checkInstalledEnvValidation(checks, rootDir) {
|
|
16543
16947
|
const envScriptPath = "scripts/check-env.sh";
|
|
16544
16948
|
const resolvedPath = resolve16(rootDir, envScriptPath);
|
|
16545
|
-
if (!
|
|
16949
|
+
if (!existsSync24(resolvedPath)) {
|
|
16546
16950
|
addCheck2(checks, {
|
|
16547
16951
|
level: "info",
|
|
16548
16952
|
code: "consumer-env-script-missing",
|
|
@@ -16587,7 +16991,7 @@ function checkInstalledMcpConfig(checks, rootDir, layout) {
|
|
|
16587
16991
|
return;
|
|
16588
16992
|
}
|
|
16589
16993
|
const resolvedPath = resolve16(rootDir, layout.mcpConfigPath);
|
|
16590
|
-
if (!
|
|
16994
|
+
if (!existsSync24(resolvedPath)) {
|
|
16591
16995
|
addCheck2(checks, {
|
|
16592
16996
|
level: "info",
|
|
16593
16997
|
code: "consumer-mcp-config-missing",
|
|
@@ -16612,6 +17016,16 @@ function checkInstalledMcpConfig(checks, rootDir, layout) {
|
|
|
16612
17016
|
if (servers.length === 0) {
|
|
16613
17017
|
return;
|
|
16614
17018
|
}
|
|
17019
|
+
if (layout.platform === "codex") {
|
|
17020
|
+
addCheck2(checks, {
|
|
17021
|
+
level: "info",
|
|
17022
|
+
code: "consumer-codex-mcp-bundled-visibility",
|
|
17023
|
+
title: "Codex plugin-bundled MCP visibility clarified",
|
|
17024
|
+
detail: `This Codex plugin bundle includes ${servers.length} MCP server${servers.length === 1 ? "" : "s"} through ${layout.mcpConfigPath}. Codex may show this on the plugin detail page without listing it on the global MCP servers settings page.`,
|
|
17025
|
+
fix: "Use the plugin detail page, tool availability in chat, and `pluxx verify-install --target codex` as the source of truth for plugin-bundled MCP wiring. If the MCP remains unavailable after install, use Plugins > Refresh if present or restart Codex.",
|
|
17026
|
+
path: layout.mcpConfigPath
|
|
17027
|
+
});
|
|
17028
|
+
}
|
|
16615
17029
|
const remoteEntries = servers.filter((server) => "url" in server);
|
|
16616
17030
|
const stdioEntries = servers.filter((server) => "command" in server);
|
|
16617
17031
|
const inlineHeaderEntries = servers.filter((server) => {
|
|
@@ -16628,6 +17042,17 @@ function checkInstalledMcpConfig(checks, rootDir, layout) {
|
|
|
16628
17042
|
fix: "If tools fail, verify the bundled command or its runtime dependencies on this machine.",
|
|
16629
17043
|
path: layout.mcpConfigPath
|
|
16630
17044
|
});
|
|
17045
|
+
const missingRuntimePaths = findMissingInstalledStdioRuntimePaths(rootDir, stdioEntries);
|
|
17046
|
+
if (missingRuntimePaths.length > 0) {
|
|
17047
|
+
addCheck2(checks, {
|
|
17048
|
+
level: "warning",
|
|
17049
|
+
code: "consumer-mcp-stdio-runtime-missing",
|
|
17050
|
+
title: "Bundled stdio MCP runtime files are missing",
|
|
17051
|
+
detail: `This installed MCP config references local runtime path${missingRuntimePaths.length === 1 ? "" : "s"} that do not exist in the bundle: ${missingRuntimePaths.join(", ")}.`,
|
|
17052
|
+
fix: "Rebuild the plugin with the MCP runtime directory included in passthrough, then reinstall the host bundle.",
|
|
17053
|
+
path: layout.mcpConfigPath
|
|
17054
|
+
});
|
|
17055
|
+
}
|
|
16631
17056
|
}
|
|
16632
17057
|
if (remoteEntries.length > 0 && inlineHeaderEntries.length > 0) {
|
|
16633
17058
|
addCheck2(checks, {
|
|
@@ -16662,9 +17087,59 @@ function checkInstalledMcpConfig(checks, rootDir, layout) {
|
|
|
16662
17087
|
});
|
|
16663
17088
|
}
|
|
16664
17089
|
}
|
|
17090
|
+
function checkInstalledBundleIntegrity(checks, rootDir, layout) {
|
|
17091
|
+
const issues = findInstalledBundleIntegrityIssues(rootDir, layout.platform);
|
|
17092
|
+
const details = [];
|
|
17093
|
+
if (issues.manifestIssue) {
|
|
17094
|
+
details.push(issues.manifestIssue);
|
|
17095
|
+
}
|
|
17096
|
+
if (issues.missingManifestPaths.length > 0) {
|
|
17097
|
+
details.push(`manifest references missing path${issues.missingManifestPaths.length === 1 ? "" : "s"}: ${issues.missingManifestPaths.join(", ")}`);
|
|
17098
|
+
}
|
|
17099
|
+
if (issues.missingHookTargets.length > 0) {
|
|
17100
|
+
details.push(`hook commands reference missing bundle target${issues.missingHookTargets.length === 1 ? "" : "s"}: ${issues.missingHookTargets.join(", ")}`);
|
|
17101
|
+
}
|
|
17102
|
+
if (details.length === 0) {
|
|
17103
|
+
addCheck2(checks, {
|
|
17104
|
+
level: "success",
|
|
17105
|
+
code: "consumer-bundle-integrity-valid",
|
|
17106
|
+
title: "Installed bundle references resolve inside the plugin",
|
|
17107
|
+
detail: "Every manifest-declared path and bundle-relative hook target exists in this installed bundle.",
|
|
17108
|
+
fix: "No action needed.",
|
|
17109
|
+
path: layout.manifestPath
|
|
17110
|
+
});
|
|
17111
|
+
return;
|
|
17112
|
+
}
|
|
17113
|
+
addCheck2(checks, {
|
|
17114
|
+
level: "error",
|
|
17115
|
+
code: "consumer-bundle-integrity-invalid",
|
|
17116
|
+
title: "Installed bundle is missing referenced files",
|
|
17117
|
+
detail: details.join("; "),
|
|
17118
|
+
fix: "Reinstall the plugin or rebuild the bundle so every manifest path and hook target exists in the installed plugin.",
|
|
17119
|
+
path: layout.manifestPath
|
|
17120
|
+
});
|
|
17121
|
+
}
|
|
17122
|
+
function findMissingInstalledStdioRuntimePaths(rootDir, stdioEntries) {
|
|
17123
|
+
const missing = /* @__PURE__ */ new Set();
|
|
17124
|
+
for (const server of stdioEntries) {
|
|
17125
|
+
const command2 = typeof server.command === "string" ? server.command : void 0;
|
|
17126
|
+
const args2 = Array.isArray(server.args) ? server.args.filter((value) => typeof value === "string") : [];
|
|
17127
|
+
for (const candidate of [command2, ...args2]) {
|
|
17128
|
+
if (!candidate || !isLikelyLocalRuntimePath3(candidate)) continue;
|
|
17129
|
+
const resolvedPath = resolve16(rootDir, candidate);
|
|
17130
|
+
if (!existsSync24(resolvedPath)) {
|
|
17131
|
+
missing.add(candidate);
|
|
17132
|
+
}
|
|
17133
|
+
}
|
|
17134
|
+
}
|
|
17135
|
+
return [...missing].sort();
|
|
17136
|
+
}
|
|
17137
|
+
function isLikelyLocalRuntimePath3(value) {
|
|
17138
|
+
return value.startsWith("./") || value.startsWith("../") || value.startsWith(".\\") || value.startsWith("..\\");
|
|
17139
|
+
}
|
|
16665
17140
|
function isLikelyOpenCodeInstallPath(rootDir) {
|
|
16666
|
-
const parent =
|
|
16667
|
-
const grandparent =
|
|
17141
|
+
const parent = dirname7(rootDir);
|
|
17142
|
+
const grandparent = dirname7(parent);
|
|
16668
17143
|
return basename6(parent) === "plugins" && basename6(grandparent) === "opencode";
|
|
16669
17144
|
}
|
|
16670
17145
|
function checkInstalledOpenCodeHostBridge(checks, rootDir) {
|
|
@@ -16682,7 +17157,7 @@ function checkInstalledOpenCodeHostBridge(checks, rootDir) {
|
|
|
16682
17157
|
const pluginName = basename6(rootDir);
|
|
16683
17158
|
const entryPath = `${rootDir}.ts`;
|
|
16684
17159
|
const entryRelativePath = `${pluginName}.ts`;
|
|
16685
|
-
if (!
|
|
17160
|
+
if (!existsSync24(entryPath)) {
|
|
16686
17161
|
addCheck2(checks, {
|
|
16687
17162
|
level: "error",
|
|
16688
17163
|
code: "consumer-opencode-entry-missing",
|
|
@@ -16722,7 +17197,7 @@ function checkInstalledOpenCodeSkills(checks, rootDir) {
|
|
|
16722
17197
|
}
|
|
16723
17198
|
const pluginName = basename6(rootDir);
|
|
16724
17199
|
const sourceSkillsDir = resolve16(rootDir, "skills");
|
|
16725
|
-
if (!
|
|
17200
|
+
if (!existsSync24(sourceSkillsDir)) {
|
|
16726
17201
|
addCheck2(checks, {
|
|
16727
17202
|
level: "info",
|
|
16728
17203
|
code: "consumer-opencode-skill-sync-not-applicable",
|
|
@@ -16733,17 +17208,17 @@ function checkInstalledOpenCodeSkills(checks, rootDir) {
|
|
|
16733
17208
|
});
|
|
16734
17209
|
return;
|
|
16735
17210
|
}
|
|
16736
|
-
const skillRoot = resolve16(
|
|
17211
|
+
const skillRoot = resolve16(dirname7(dirname7(rootDir)), "skills");
|
|
16737
17212
|
const missingSkills = [];
|
|
16738
17213
|
const malformedSkills = [];
|
|
16739
17214
|
let expectedSkillCount = 0;
|
|
16740
17215
|
for (const entry of readdirSync9(sourceSkillsDir, { withFileTypes: true })) {
|
|
16741
17216
|
if (!entry.isDirectory()) continue;
|
|
16742
17217
|
const sourceSkillPath = resolve16(sourceSkillsDir, entry.name, "SKILL.md");
|
|
16743
|
-
if (!
|
|
17218
|
+
if (!existsSync24(sourceSkillPath)) continue;
|
|
16744
17219
|
expectedSkillCount++;
|
|
16745
17220
|
const installedSkillPath = resolve16(skillRoot, `${pluginName}-${entry.name}`, "SKILL.md");
|
|
16746
|
-
if (!
|
|
17221
|
+
if (!existsSync24(installedSkillPath)) {
|
|
16747
17222
|
missingSkills.push(`${pluginName}-${entry.name}`);
|
|
16748
17223
|
continue;
|
|
16749
17224
|
}
|
|
@@ -16829,6 +17304,7 @@ async function doctorConsumer(rootDir = process.cwd()) {
|
|
|
16829
17304
|
path: layout.manifestPath
|
|
16830
17305
|
});
|
|
16831
17306
|
checkConsumerManifest(checks, rootDir, layout);
|
|
17307
|
+
checkInstalledBundleIntegrity(checks, rootDir, layout);
|
|
16832
17308
|
checkInstalledUserConfig(checks, rootDir);
|
|
16833
17309
|
checkInstalledEnvValidation(checks, rootDir);
|
|
16834
17310
|
checkInstalledMcpConfig(checks, rootDir, layout);
|
|
@@ -16840,7 +17316,7 @@ async function doctorConsumer(rootDir = process.cwd()) {
|
|
|
16840
17316
|
}
|
|
16841
17317
|
async function doctorProject(rootDir = process.cwd()) {
|
|
16842
17318
|
const checks = [];
|
|
16843
|
-
const configPath = CONFIG_FILES.find((filename) =>
|
|
17319
|
+
const configPath = CONFIG_FILES.find((filename) => existsSync24(resolve16(rootDir, filename)));
|
|
16844
17320
|
addRuntimeChecks(checks, "project");
|
|
16845
17321
|
if (!configPath) {
|
|
16846
17322
|
addCheck2(checks, {
|
|
@@ -16939,7 +17415,7 @@ function printDoctorReport(report) {
|
|
|
16939
17415
|
|
|
16940
17416
|
// src/cli/dev.ts
|
|
16941
17417
|
import { watch } from "fs";
|
|
16942
|
-
import { relative as
|
|
17418
|
+
import { relative as relative10, resolve as resolve17 } from "path";
|
|
16943
17419
|
var WATCH_PATTERNS = [
|
|
16944
17420
|
/^pluxx\.config\.(ts|js|json)$/,
|
|
16945
17421
|
/^skills\//,
|
|
@@ -16965,7 +17441,7 @@ async function runDev(args2) {
|
|
|
16965
17441
|
let pendingFile = null;
|
|
16966
17442
|
const watcher = watch(rootDir, { recursive: true }, (_event, filename) => {
|
|
16967
17443
|
if (!filename) return;
|
|
16968
|
-
const rel =
|
|
17444
|
+
const rel = relative10(rootDir, resolve17(rootDir, filename));
|
|
16969
17445
|
if (rel.startsWith("dist/") || rel.startsWith(".") || rel.includes("node_modules")) {
|
|
16970
17446
|
return;
|
|
16971
17447
|
}
|
|
@@ -17020,8 +17496,8 @@ async function runBuild(rootDir, targets) {
|
|
|
17020
17496
|
}
|
|
17021
17497
|
|
|
17022
17498
|
// src/cli/migrate.ts
|
|
17023
|
-
import { basename as basename7, relative as
|
|
17024
|
-
import { existsSync as
|
|
17499
|
+
import { basename as basename7, relative as relative11, resolve as resolve18 } from "path";
|
|
17500
|
+
import { existsSync as existsSync25, readdirSync as readdirSync10, mkdirSync as mkdirSync5, cpSync as cpSync4, readFileSync as readFileSync12, writeFileSync as writeFileSync5 } from "fs";
|
|
17025
17501
|
function detectPlatform(pluginDir) {
|
|
17026
17502
|
const checks = [
|
|
17027
17503
|
{ dir: ".claude-plugin", platform: "claude-code" },
|
|
@@ -17030,12 +17506,12 @@ function detectPlatform(pluginDir) {
|
|
|
17030
17506
|
];
|
|
17031
17507
|
for (const check of checks) {
|
|
17032
17508
|
const manifestPath = resolve18(pluginDir, check.dir, "plugin.json");
|
|
17033
|
-
if (
|
|
17509
|
+
if (existsSync25(manifestPath)) {
|
|
17034
17510
|
return { platform: check.platform, manifestPath };
|
|
17035
17511
|
}
|
|
17036
17512
|
}
|
|
17037
17513
|
const pkgPath = resolve18(pluginDir, "package.json");
|
|
17038
|
-
if (
|
|
17514
|
+
if (existsSync25(pkgPath)) {
|
|
17039
17515
|
try {
|
|
17040
17516
|
const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
|
|
17041
17517
|
const deps = {
|
|
@@ -17088,7 +17564,7 @@ function parseMcp(pluginDir, detection) {
|
|
|
17088
17564
|
} catch {
|
|
17089
17565
|
}
|
|
17090
17566
|
for (const mcpPath of mcpPaths) {
|
|
17091
|
-
if (!
|
|
17567
|
+
if (!existsSync25(mcpPath)) continue;
|
|
17092
17568
|
try {
|
|
17093
17569
|
const raw = JSON.parse(readFileSync12(mcpPath, "utf-8"));
|
|
17094
17570
|
const servers = raw.mcpServers ?? raw;
|
|
@@ -17182,7 +17658,7 @@ function parseHooks(pluginDir, detection) {
|
|
|
17182
17658
|
} catch {
|
|
17183
17659
|
}
|
|
17184
17660
|
for (const hooksPath of hooksPaths) {
|
|
17185
|
-
if (!
|
|
17661
|
+
if (!existsSync25(hooksPath)) continue;
|
|
17186
17662
|
try {
|
|
17187
17663
|
const raw = JSON.parse(readFileSync12(hooksPath, "utf-8"));
|
|
17188
17664
|
const hooksObj = raw.hooks ?? raw;
|
|
@@ -17231,7 +17707,7 @@ function findInstructions(pluginDir) {
|
|
|
17231
17707
|
];
|
|
17232
17708
|
for (const candidate of candidates) {
|
|
17233
17709
|
const filePath = resolve18(pluginDir, candidate);
|
|
17234
|
-
if (
|
|
17710
|
+
if (existsSync25(filePath)) {
|
|
17235
17711
|
return `./${candidate}`;
|
|
17236
17712
|
}
|
|
17237
17713
|
}
|
|
@@ -17256,7 +17732,7 @@ function detectCanonicalSourcePaths(pluginDir) {
|
|
|
17256
17732
|
for (const bucket of Object.keys(CANONICAL_SOURCE_CANDIDATES)) {
|
|
17257
17733
|
for (const candidate of CANONICAL_SOURCE_CANDIDATES[bucket]) {
|
|
17258
17734
|
const normalized = stripRelativePrefix(candidate);
|
|
17259
|
-
if (!
|
|
17735
|
+
if (!existsSync25(resolve18(pluginDir, normalized))) continue;
|
|
17260
17736
|
result[bucket] = normalizeRelativeDir(normalized);
|
|
17261
17737
|
break;
|
|
17262
17738
|
}
|
|
@@ -17272,7 +17748,7 @@ function detectPassthroughDirs(pluginDir, mcp) {
|
|
|
17272
17748
|
if (!match?.[1]) continue;
|
|
17273
17749
|
const dirName = match[1];
|
|
17274
17750
|
const dirPath = resolve18(pluginDir, dirName);
|
|
17275
|
-
if (
|
|
17751
|
+
if (existsSync25(dirPath)) {
|
|
17276
17752
|
passthrough.add(`./${dirName}/`);
|
|
17277
17753
|
}
|
|
17278
17754
|
}
|
|
@@ -17373,7 +17849,7 @@ function inferPermissionsFromMigratedSkills(pluginDir, sourcePaths) {
|
|
|
17373
17849
|
for (const entry of entries) {
|
|
17374
17850
|
if (!entry.isDirectory()) continue;
|
|
17375
17851
|
const skillPath = resolve18(skillsDir, entry.name, "SKILL.md");
|
|
17376
|
-
if (!
|
|
17852
|
+
if (!existsSync25(skillPath)) continue;
|
|
17377
17853
|
const content = readFileSync12(skillPath, "utf-8");
|
|
17378
17854
|
const inferredRules = extractAllowedTools(content).map(normalizeMigratedAllowedTool).filter((rule) => Boolean(rule));
|
|
17379
17855
|
if (inferredRules.length === 0) continue;
|
|
@@ -17418,7 +17894,7 @@ function readMigratedSkills(pluginDir, sourcePaths) {
|
|
|
17418
17894
|
const skillPath = resolve18(skillsDir, dirName, "SKILL.md");
|
|
17419
17895
|
let title = titleCaseFromDirName(dirName);
|
|
17420
17896
|
let description;
|
|
17421
|
-
if (
|
|
17897
|
+
if (existsSync25(skillPath)) {
|
|
17422
17898
|
const content = readFileSync12(skillPath, "utf-8");
|
|
17423
17899
|
title = extractFrontmatterField(content, "name") ?? firstHeading3(content) ?? title;
|
|
17424
17900
|
description = extractFrontmatterField(content, "description");
|
|
@@ -17452,13 +17928,13 @@ var HOST_NATIVE_SKILL_FRONTMATTER_KEYS = /* @__PURE__ */ new Set([
|
|
|
17452
17928
|
"allowed-tools"
|
|
17453
17929
|
]);
|
|
17454
17930
|
function sanitizeMigratedSkillFrontmatter(outputDir) {
|
|
17455
|
-
if (!
|
|
17931
|
+
if (!existsSync25(resolve18(outputDir, "skills"))) return;
|
|
17456
17932
|
const skillsDir = resolve18(outputDir, "skills");
|
|
17457
17933
|
const entries = readdirSync10(skillsDir, { withFileTypes: true });
|
|
17458
17934
|
for (const entry of entries) {
|
|
17459
17935
|
if (!entry.isDirectory()) continue;
|
|
17460
17936
|
const skillPath = resolve18(skillsDir, entry.name, "SKILL.md");
|
|
17461
|
-
if (!
|
|
17937
|
+
if (!existsSync25(skillPath)) continue;
|
|
17462
17938
|
const content = readFileSync12(skillPath, "utf-8");
|
|
17463
17939
|
const lines = content.split(/\r?\n/);
|
|
17464
17940
|
if (lines[0]?.trim() !== "---") continue;
|
|
@@ -17621,11 +18097,11 @@ function walkMarkdownFiles3(dir) {
|
|
|
17621
18097
|
return files;
|
|
17622
18098
|
}
|
|
17623
18099
|
function normalizeMigratedOpenCodeAgents(destDir) {
|
|
17624
|
-
if (!
|
|
18100
|
+
if (!existsSync25(destDir)) return [];
|
|
17625
18101
|
const normalized = [];
|
|
17626
18102
|
for (const filePath of walkMarkdownFiles3(destDir)) {
|
|
17627
18103
|
if (normalizeMigratedOpenCodeAgentFile(filePath)) {
|
|
17628
|
-
normalized.push(
|
|
18104
|
+
normalized.push(relative11(destDir, filePath).replace(/\\/g, "/"));
|
|
17629
18105
|
}
|
|
17630
18106
|
}
|
|
17631
18107
|
return normalized.sort();
|
|
@@ -17710,7 +18186,7 @@ function buildMigratedScaffoldMetadata(result, outputDir) {
|
|
|
17710
18186
|
if (!result.sourcePaths[dir]) return [];
|
|
17711
18187
|
const baseDir = dir;
|
|
17712
18188
|
const dirPath = resolve18(outputDir, baseDir);
|
|
17713
|
-
if (!
|
|
18189
|
+
if (!existsSync25(dirPath)) return [];
|
|
17714
18190
|
const entries = readdirSync10(dirPath, { withFileTypes: true });
|
|
17715
18191
|
const files = [];
|
|
17716
18192
|
for (const entry of entries) {
|
|
@@ -17777,7 +18253,7 @@ function copyDirectories(pluginDir, outputDir, sourcePaths, passthrough) {
|
|
|
17777
18253
|
const normalizedSource = stripRelativePrefix(sourcePath);
|
|
17778
18254
|
const src = resolve18(pluginDir, normalizedSource);
|
|
17779
18255
|
const dest = resolve18(outputDir, dir);
|
|
17780
|
-
if (
|
|
18256
|
+
if (existsSync25(dest)) {
|
|
17781
18257
|
console.log(` skip ./${dir}/ (already exists)`);
|
|
17782
18258
|
continue;
|
|
17783
18259
|
}
|
|
@@ -17794,7 +18270,7 @@ function copyDirectories(pluginDir, outputDir, sourcePaths, passthrough) {
|
|
|
17794
18270
|
const normalized = entry.replace(/^\.\//, "").replace(/\/$/, "");
|
|
17795
18271
|
const src = resolve18(pluginDir, normalized);
|
|
17796
18272
|
const dest = resolve18(outputDir, normalized);
|
|
17797
|
-
if (
|
|
18273
|
+
if (existsSync25(dest)) {
|
|
17798
18274
|
console.log(` skip ./${normalized}/ (already exists)`);
|
|
17799
18275
|
continue;
|
|
17800
18276
|
}
|
|
@@ -17940,7 +18416,7 @@ function quote(s) {
|
|
|
17940
18416
|
async function migrate(inputPath) {
|
|
17941
18417
|
const pluginDir = resolve18(inputPath);
|
|
17942
18418
|
const outputDir = process.cwd();
|
|
17943
|
-
if (!
|
|
18419
|
+
if (!existsSync25(pluginDir)) {
|
|
17944
18420
|
console.error(`Error: Path does not exist: ${pluginDir}`);
|
|
17945
18421
|
process.exit(1);
|
|
17946
18422
|
}
|
|
@@ -18007,7 +18483,7 @@ async function migrate(inputPath) {
|
|
|
18007
18483
|
};
|
|
18008
18484
|
const configContent = generateConfigTs(result);
|
|
18009
18485
|
const configPath = resolve18(outputDir, "pluxx.config.ts");
|
|
18010
|
-
if (
|
|
18486
|
+
if (existsSync25(configPath)) {
|
|
18011
18487
|
console.error(`
|
|
18012
18488
|
Error: pluxx.config.ts already exists in ${outputDir}`);
|
|
18013
18489
|
console.error("Remove it first or run from a different directory.");
|
|
@@ -18024,7 +18500,7 @@ Generated pluxx.config.ts`);
|
|
|
18024
18500
|
if (instructions) {
|
|
18025
18501
|
const srcInstr = resolve18(pluginDir, instructions);
|
|
18026
18502
|
const destInstr = resolve18(outputDir, instructions);
|
|
18027
|
-
if (!
|
|
18503
|
+
if (!existsSync25(destInstr)) {
|
|
18028
18504
|
const content = readFileSync12(srcInstr, "utf-8");
|
|
18029
18505
|
await writeTextFile(destInstr, content);
|
|
18030
18506
|
console.log(`Copied: ${instructions}`);
|
|
@@ -18060,7 +18536,7 @@ Generated pluxx.config.ts`);
|
|
|
18060
18536
|
|
|
18061
18537
|
// src/cli/mcp-proxy.ts
|
|
18062
18538
|
import { mkdirSync as mkdirSync6, readFileSync as readFileSync13 } from "fs";
|
|
18063
|
-
import { dirname as
|
|
18539
|
+
import { dirname as dirname8, resolve as resolve19 } from "path";
|
|
18064
18540
|
import * as readline3 from "readline";
|
|
18065
18541
|
function usage() {
|
|
18066
18542
|
return [
|
|
@@ -18137,7 +18613,7 @@ async function loadReplayTape(filepath) {
|
|
|
18137
18613
|
}
|
|
18138
18614
|
async function writeTape(filepath, tape) {
|
|
18139
18615
|
const absolutePath = resolve19(process.cwd(), filepath);
|
|
18140
|
-
mkdirSync6(
|
|
18616
|
+
mkdirSync6(dirname8(absolutePath), { recursive: true });
|
|
18141
18617
|
await writeTextFile(absolutePath, `${JSON.stringify(tape, null, 2)}
|
|
18142
18618
|
`);
|
|
18143
18619
|
}
|
|
@@ -18308,7 +18784,7 @@ var PromptCancelledError = class extends Error {
|
|
|
18308
18784
|
}
|
|
18309
18785
|
};
|
|
18310
18786
|
function ask(question) {
|
|
18311
|
-
return new Promise((
|
|
18787
|
+
return new Promise((resolve25, reject) => {
|
|
18312
18788
|
const rl = readline4.createInterface({
|
|
18313
18789
|
input: process.stdin,
|
|
18314
18790
|
output: process.stdout
|
|
@@ -18336,7 +18812,7 @@ function ask(question) {
|
|
|
18336
18812
|
rl.once("close", onClose);
|
|
18337
18813
|
rl.question(question, (answer) => {
|
|
18338
18814
|
settle(() => {
|
|
18339
|
-
|
|
18815
|
+
resolve25(answer);
|
|
18340
18816
|
rl.close();
|
|
18341
18817
|
});
|
|
18342
18818
|
});
|
|
@@ -19311,13 +19787,13 @@ ${c2}
|
|
|
19311
19787
|
} }).prompt();
|
|
19312
19788
|
|
|
19313
19789
|
// src/cli/index.ts
|
|
19314
|
-
import { basename as basename8, resolve as
|
|
19790
|
+
import { basename as basename8, resolve as resolve24 } from "path";
|
|
19315
19791
|
import { mkdir as mkdir4, mkdtemp as mkdtemp3, rm as rm4 } from "fs/promises";
|
|
19316
19792
|
import { tmpdir as tmpdir5 } from "os";
|
|
19317
19793
|
import { spawn as spawn4, spawnSync as spawnSync3 } from "child_process";
|
|
19318
19794
|
|
|
19319
19795
|
// src/cli/publish.ts
|
|
19320
|
-
import { chmodSync, existsSync as
|
|
19796
|
+
import { chmodSync, existsSync as existsSync26, mkdtempSync as mkdtempSync2, readFileSync as readFileSync14, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "fs";
|
|
19321
19797
|
import { createHash } from "crypto";
|
|
19322
19798
|
import { resolve as resolve20 } from "path";
|
|
19323
19799
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
@@ -19352,7 +19828,7 @@ function resolveRequestedChannels(options) {
|
|
|
19352
19828
|
}
|
|
19353
19829
|
function getBuiltTargets(rootDir, config) {
|
|
19354
19830
|
return config.targets.filter(
|
|
19355
|
-
(platform) =>
|
|
19831
|
+
(platform) => existsSync26(resolve20(rootDir, config.outDir, platform))
|
|
19356
19832
|
);
|
|
19357
19833
|
}
|
|
19358
19834
|
function getArchiveAssetName(pluginName, platform, version, variant) {
|
|
@@ -19411,7 +19887,7 @@ function buildReleaseAssets(rootDir, config, version, targets) {
|
|
|
19411
19887
|
function readNpmPackageName(rootDir, config) {
|
|
19412
19888
|
const packageDir = resolve20(rootDir, config.outDir, "opencode");
|
|
19413
19889
|
const packageJsonPath = resolve20(packageDir, "package.json");
|
|
19414
|
-
if (!
|
|
19890
|
+
if (!existsSync26(packageJsonPath)) {
|
|
19415
19891
|
return {};
|
|
19416
19892
|
}
|
|
19417
19893
|
try {
|
|
@@ -19464,7 +19940,7 @@ function collectChecks(args2) {
|
|
|
19464
19940
|
if (args2.npmEnabled) {
|
|
19465
19941
|
checks.push({
|
|
19466
19942
|
name: "npm-package-ready",
|
|
19467
|
-
ok: Boolean(args2.packageDir &&
|
|
19943
|
+
ok: Boolean(args2.packageDir && existsSync26(resolve20(args2.packageDir, "package.json")) && args2.packageName),
|
|
19468
19944
|
code: "npm-package-ready",
|
|
19469
19945
|
detail: args2.packageDir ? `OpenCode package dir: ${args2.packageDir}` : "No npm-backed target package found."
|
|
19470
19946
|
});
|
|
@@ -19620,6 +20096,175 @@ echo
|
|
|
19620
20096
|
echo "Installed __DISPLAY_NAME__ across ${installerTargets.join(", ")}."
|
|
19621
20097
|
`.replaceAll("__REPO__", "REPO_PLACEHOLDER").replaceAll("__DISPLAY_NAME__", "DISPLAY_PLACEHOLDER");
|
|
19622
20098
|
}
|
|
20099
|
+
function renderInstallerUserConfigSnippet(config, platform, installDirVariable) {
|
|
20100
|
+
const entries = collectUserConfigEntries(config, [platform]).map((entry) => ({
|
|
20101
|
+
key: entry.key,
|
|
20102
|
+
title: entry.title,
|
|
20103
|
+
type: entry.type ?? "string",
|
|
20104
|
+
required: entry.required !== false,
|
|
20105
|
+
envVar: entry.envVar ?? defaultUserConfigEnvVar(entry.key)
|
|
20106
|
+
}));
|
|
20107
|
+
if (entries.length === 0) return "";
|
|
20108
|
+
const promptLines = entries.map((entry) => {
|
|
20109
|
+
const functionName = entry.type === "secret" ? "pluxx_prompt_secret_config" : "pluxx_prompt_text_config";
|
|
20110
|
+
return `${functionName} ${JSON.stringify(entry.envVar)} ${JSON.stringify(entry.title)} ${entry.required ? "1" : "0"}`;
|
|
20111
|
+
});
|
|
20112
|
+
return `
|
|
20113
|
+
PLUXX_USER_CONFIG_SPEC="$(cat <<'PLUXX_USER_CONFIG_JSON'
|
|
20114
|
+
${JSON.stringify(entries)}
|
|
20115
|
+
PLUXX_USER_CONFIG_JSON
|
|
20116
|
+
)"
|
|
20117
|
+
|
|
20118
|
+
pluxx_is_placeholder_secret() {
|
|
20119
|
+
case "$1" in
|
|
20120
|
+
*dummy*|*Dummy*|*DUMMY*|*placeholder*|*Placeholder*|*PLACEHOLDER*|*changeme*|*CHANGE_ME*|*replace*me*|*Replace*Me*|*your*key*|*YOUR*KEY*|*api*key*here*|*API*KEY*HERE*|*token*here*|*TOKEN*HERE*)
|
|
20121
|
+
return 0
|
|
20122
|
+
;;
|
|
20123
|
+
*)
|
|
20124
|
+
return 1
|
|
20125
|
+
;;
|
|
20126
|
+
esac
|
|
20127
|
+
}
|
|
20128
|
+
|
|
20129
|
+
pluxx_prompt_secret_config() {
|
|
20130
|
+
local env_var="$1"
|
|
20131
|
+
local label="$2"
|
|
20132
|
+
local required="$3"
|
|
20133
|
+
local current_value="\${!env_var:-}"
|
|
20134
|
+
|
|
20135
|
+
if [[ -z "$current_value" && "$required" == "1" ]]; then
|
|
20136
|
+
if [[ -t 0 || -r /dev/tty ]]; then
|
|
20137
|
+
read -r -s -p "$label [$env_var]: " current_value </dev/tty
|
|
20138
|
+
echo >/dev/tty
|
|
20139
|
+
else
|
|
20140
|
+
echo "Missing required config: export $env_var before running this installer." >&2
|
|
20141
|
+
exit 1
|
|
20142
|
+
fi
|
|
20143
|
+
fi
|
|
20144
|
+
|
|
20145
|
+
if [[ -n "$current_value" ]] && pluxx_is_placeholder_secret "$current_value"; then
|
|
20146
|
+
echo "Refusing placeholder-looking secret for $env_var. Set a real value and rerun the installer." >&2
|
|
20147
|
+
exit 1
|
|
20148
|
+
fi
|
|
20149
|
+
|
|
20150
|
+
export "$env_var=$current_value"
|
|
20151
|
+
}
|
|
20152
|
+
|
|
20153
|
+
pluxx_prompt_text_config() {
|
|
20154
|
+
local env_var="$1"
|
|
20155
|
+
local label="$2"
|
|
20156
|
+
local required="$3"
|
|
20157
|
+
local current_value="\${!env_var:-}"
|
|
20158
|
+
|
|
20159
|
+
if [[ -z "$current_value" && "$required" == "1" ]]; then
|
|
20160
|
+
if [[ -t 0 || -r /dev/tty ]]; then
|
|
20161
|
+
read -r -p "$label [$env_var]: " current_value </dev/tty
|
|
20162
|
+
else
|
|
20163
|
+
echo "Missing required config: export $env_var before running this installer." >&2
|
|
20164
|
+
exit 1
|
|
20165
|
+
fi
|
|
20166
|
+
fi
|
|
20167
|
+
|
|
20168
|
+
export "$env_var=$current_value"
|
|
20169
|
+
}
|
|
20170
|
+
|
|
20171
|
+
${promptLines.join("\n")}
|
|
20172
|
+
|
|
20173
|
+
export PLUXX_USER_CONFIG_SPEC
|
|
20174
|
+
export PLUXX_INSTALL_DIR="${installDirVariable}"
|
|
20175
|
+
|
|
20176
|
+
node <<'NODE'
|
|
20177
|
+
const fs = require('fs')
|
|
20178
|
+
const path = require('path')
|
|
20179
|
+
|
|
20180
|
+
const installDir = process.env.PLUXX_INSTALL_DIR
|
|
20181
|
+
const spec = JSON.parse(process.env.PLUXX_USER_CONFIG_SPEC || '[]')
|
|
20182
|
+
|
|
20183
|
+
if (installDir && spec.length > 0) {
|
|
20184
|
+
const env = {}
|
|
20185
|
+
const values = {}
|
|
20186
|
+
|
|
20187
|
+
for (const entry of spec) {
|
|
20188
|
+
const value = process.env[entry.envVar]
|
|
20189
|
+
if (value === undefined || value === '') continue
|
|
20190
|
+
values[entry.key] = value
|
|
20191
|
+
env[entry.envVar] = value
|
|
20192
|
+
}
|
|
20193
|
+
|
|
20194
|
+
fs.writeFileSync(
|
|
20195
|
+
path.join(installDir, '.pluxx-user.json'),
|
|
20196
|
+
JSON.stringify({ values, env }, null, 2) + '\\n',
|
|
20197
|
+
)
|
|
20198
|
+
|
|
20199
|
+
const envScriptPath = path.join(installDir, 'scripts/check-env.sh')
|
|
20200
|
+
if (fs.existsSync(envScriptPath)) {
|
|
20201
|
+
fs.writeFileSync(
|
|
20202
|
+
envScriptPath,
|
|
20203
|
+
'#!/usr/bin/env bash\\nset -euo pipefail\\n# pluxx install materialized required config for this local plugin install.\\nexit 0\\n',
|
|
20204
|
+
)
|
|
20205
|
+
}
|
|
20206
|
+
|
|
20207
|
+
const materialize = (value) =>
|
|
20208
|
+
typeof value === 'string'
|
|
20209
|
+
? value.replace(/\\$\\{([A-Za-z_][A-Za-z0-9_]*)\\}/g, (_match, name) => env[name] || '${" + name + "}')
|
|
20210
|
+
: value
|
|
20211
|
+
|
|
20212
|
+
const materializeRecord = (record) => {
|
|
20213
|
+
if (!record || typeof record !== 'object') return record
|
|
20214
|
+
const next = {}
|
|
20215
|
+
for (const [key, value] of Object.entries(record)) {
|
|
20216
|
+
next[key] = materialize(value)
|
|
20217
|
+
}
|
|
20218
|
+
return next
|
|
20219
|
+
}
|
|
20220
|
+
|
|
20221
|
+
for (const relativePath of ['.mcp.json', 'mcp.json']) {
|
|
20222
|
+
const filepath = path.join(installDir, relativePath)
|
|
20223
|
+
if (!fs.existsSync(filepath)) continue
|
|
20224
|
+
|
|
20225
|
+
const payload = JSON.parse(fs.readFileSync(filepath, 'utf8'))
|
|
20226
|
+
for (const server of Object.values(payload.mcpServers || {})) {
|
|
20227
|
+
if (!server || typeof server !== 'object') continue
|
|
20228
|
+
|
|
20229
|
+
if (server.env) {
|
|
20230
|
+
server.env = materializeRecord(server.env)
|
|
20231
|
+
}
|
|
20232
|
+
|
|
20233
|
+
if (server.bearer_token_env_var && env[server.bearer_token_env_var]) {
|
|
20234
|
+
server.http_headers = {
|
|
20235
|
+
...(server.http_headers || {}),
|
|
20236
|
+
Authorization: 'Bearer ' + env[server.bearer_token_env_var],
|
|
20237
|
+
}
|
|
20238
|
+
delete server.bearer_token_env_var
|
|
20239
|
+
}
|
|
20240
|
+
|
|
20241
|
+
if (server.env_http_headers && typeof server.env_http_headers === 'object') {
|
|
20242
|
+
server.http_headers = {
|
|
20243
|
+
...(server.http_headers || {}),
|
|
20244
|
+
}
|
|
20245
|
+
for (const [headerName, envVar] of Object.entries(server.env_http_headers)) {
|
|
20246
|
+
if (env[envVar]) server.http_headers[headerName] = env[envVar]
|
|
20247
|
+
}
|
|
20248
|
+
delete server.env_http_headers
|
|
20249
|
+
}
|
|
20250
|
+
|
|
20251
|
+
if (server.headers) {
|
|
20252
|
+
server.headers = materializeRecord(server.headers)
|
|
20253
|
+
}
|
|
20254
|
+
if (server.http_headers) {
|
|
20255
|
+
server.http_headers = materializeRecord(server.http_headers)
|
|
20256
|
+
}
|
|
20257
|
+
}
|
|
20258
|
+
|
|
20259
|
+
fs.writeFileSync(filepath, JSON.stringify(payload, null, 2) + '\\n')
|
|
20260
|
+
}
|
|
20261
|
+
}
|
|
20262
|
+
NODE
|
|
20263
|
+
`;
|
|
20264
|
+
}
|
|
20265
|
+
function hasInstallerUserConfig(config, platform) {
|
|
20266
|
+
return collectUserConfigEntries(config, [platform]).length > 0;
|
|
20267
|
+
}
|
|
19623
20268
|
function renderInstallClaudeCodeScript(config) {
|
|
19624
20269
|
return `#!/usr/bin/env bash
|
|
19625
20270
|
set -euo pipefail
|
|
@@ -19646,6 +20291,7 @@ need_cmd tar
|
|
|
19646
20291
|
need_cmd mktemp
|
|
19647
20292
|
need_cmd grep
|
|
19648
20293
|
need_cmd sed
|
|
20294
|
+
${hasInstallerUserConfig(config, "claude-code") ? "need_cmd node" : ""}
|
|
19649
20295
|
|
|
19650
20296
|
if [[ "$SKIP_INSTALL" != "1" ]]; then
|
|
19651
20297
|
need_cmd curl
|
|
@@ -19682,6 +20328,7 @@ DESCRIPTION="$(grep -E '"description"' "$PLUGIN_MANIFEST" | head -n1 | sed -E 's
|
|
|
19682
20328
|
mkdir -p "$INSTALL_ROOT/.claude-plugin" "$INSTALL_ROOT/plugins"
|
|
19683
20329
|
rm -rf "$INSTALL_ROOT/plugins/$PLUGIN_NAME"
|
|
19684
20330
|
cp -R "$BUNDLE_DIR" "$INSTALL_ROOT/plugins/$PLUGIN_NAME"
|
|
20331
|
+
${renderInstallerUserConfigSnippet(config, "claude-code", "$INSTALL_ROOT/plugins/$PLUGIN_NAME")}
|
|
19685
20332
|
|
|
19686
20333
|
cat > "$INSTALL_ROOT/.claude-plugin/marketplace.json" <<JSON
|
|
19687
20334
|
{
|
|
@@ -19745,6 +20392,7 @@ need_cmd() {
|
|
|
19745
20392
|
need_cmd tar
|
|
19746
20393
|
need_cmd mktemp
|
|
19747
20394
|
need_cmd curl
|
|
20395
|
+
${hasInstallerUserConfig(config, "cursor") ? "need_cmd node" : ""}
|
|
19748
20396
|
|
|
19749
20397
|
TMP_DIR="$(mktemp -d)"
|
|
19750
20398
|
cleanup() {
|
|
@@ -19773,6 +20421,7 @@ fi
|
|
|
19773
20421
|
mkdir -p "$(dirname "$INSTALL_DIR")"
|
|
19774
20422
|
rm -rf "$INSTALL_DIR"
|
|
19775
20423
|
cp -R "$BUNDLE_DIR" "$INSTALL_DIR"
|
|
20424
|
+
${renderInstallerUserConfigSnippet(config, "cursor", "$INSTALL_DIR")}
|
|
19776
20425
|
|
|
19777
20426
|
echo "Installed $PLUGIN_NAME to $INSTALL_DIR"
|
|
19778
20427
|
echo "If Cursor is already open, use Developer: Reload Window or restart Cursor so the plugin is picked up."
|
|
@@ -19830,6 +20479,7 @@ fi
|
|
|
19830
20479
|
mkdir -p "$(dirname "$INSTALL_DIR")"
|
|
19831
20480
|
rm -rf "$INSTALL_DIR"
|
|
19832
20481
|
cp -R "$BUNDLE_DIR" "$INSTALL_DIR"
|
|
20482
|
+
${renderInstallerUserConfigSnippet(config, "codex", "$INSTALL_DIR")}
|
|
19833
20483
|
|
|
19834
20484
|
mkdir -p "$(dirname "$MARKETPLACE_PATH")"
|
|
19835
20485
|
|
|
@@ -19944,6 +20594,7 @@ fi
|
|
|
19944
20594
|
mkdir -p "$(dirname "$INSTALL_DIR")" "$SKILLS_ROOT"
|
|
19945
20595
|
rm -rf "$INSTALL_DIR"
|
|
19946
20596
|
cp -R "$BUNDLE_DIR" "$INSTALL_DIR"
|
|
20597
|
+
${renderInstallerUserConfigSnippet(config, "opencode", "$INSTALL_DIR")}
|
|
19947
20598
|
|
|
19948
20599
|
export ENTRY_PATH
|
|
19949
20600
|
export PLUGIN_NAME
|
|
@@ -20271,22 +20922,107 @@ function printJson(value) {
|
|
|
20271
20922
|
}
|
|
20272
20923
|
|
|
20273
20924
|
// src/cli/verify-install.ts
|
|
20274
|
-
import { existsSync as
|
|
20925
|
+
import { existsSync as existsSync27, lstatSync as lstatSync4, readdirSync as readdirSync11, readFileSync as readFileSync15, readlinkSync, realpathSync, statSync as statSync5 } from "fs";
|
|
20275
20926
|
import { resolve as resolve21 } from "path";
|
|
20276
20927
|
function buildCheckFromReport(target, pluginName, report) {
|
|
20277
20928
|
const consumerPath = resolveInstalledConsumerPath(target, pluginName);
|
|
20929
|
+
const staleReason = target.built && existsSync27(consumerPath) ? detectStaleInstall(target, pluginName, consumerPath) : void 0;
|
|
20930
|
+
const stale = staleReason !== void 0;
|
|
20278
20931
|
return {
|
|
20279
20932
|
platform: target.platform,
|
|
20280
20933
|
installPath: target.pluginDir,
|
|
20281
20934
|
consumerPath,
|
|
20282
20935
|
built: target.built,
|
|
20283
|
-
installed:
|
|
20284
|
-
|
|
20285
|
-
|
|
20936
|
+
installed: existsSync27(consumerPath),
|
|
20937
|
+
stale,
|
|
20938
|
+
...staleReason ? { staleReason } : {},
|
|
20939
|
+
ok: report.errors === 0 && !stale,
|
|
20940
|
+
errors: report.errors + (stale ? 1 : 0),
|
|
20286
20941
|
warnings: report.warnings,
|
|
20287
20942
|
infos: report.infos
|
|
20288
20943
|
};
|
|
20289
20944
|
}
|
|
20945
|
+
function manifestPathForPlatform2(platform) {
|
|
20946
|
+
switch (platform) {
|
|
20947
|
+
case "claude-code":
|
|
20948
|
+
return ".claude-plugin/plugin.json";
|
|
20949
|
+
case "cursor":
|
|
20950
|
+
return ".cursor-plugin/plugin.json";
|
|
20951
|
+
case "codex":
|
|
20952
|
+
return ".codex-plugin/plugin.json";
|
|
20953
|
+
case "opencode":
|
|
20954
|
+
return "package.json";
|
|
20955
|
+
default:
|
|
20956
|
+
return void 0;
|
|
20957
|
+
}
|
|
20958
|
+
}
|
|
20959
|
+
function readInstalledManifestVersion(rootDir, platform) {
|
|
20960
|
+
const manifestPath = manifestPathForPlatform2(platform);
|
|
20961
|
+
if (!manifestPath) return void 0;
|
|
20962
|
+
const filepath = resolve21(rootDir, manifestPath);
|
|
20963
|
+
if (!existsSync27(filepath)) return void 0;
|
|
20964
|
+
try {
|
|
20965
|
+
const manifest = JSON.parse(readFileSync15(filepath, "utf-8"));
|
|
20966
|
+
return typeof manifest.version === "string" ? manifest.version : void 0;
|
|
20967
|
+
} catch {
|
|
20968
|
+
return void 0;
|
|
20969
|
+
}
|
|
20970
|
+
}
|
|
20971
|
+
function findCodexCacheCandidates(pluginName) {
|
|
20972
|
+
const home = process.env.HOME ?? "~";
|
|
20973
|
+
const cacheRoot = resolve21(home, ".codex/plugins/cache");
|
|
20974
|
+
if (!existsSync27(cacheRoot)) return [];
|
|
20975
|
+
const candidates = [];
|
|
20976
|
+
for (const marketplace of readdirSync11(cacheRoot)) {
|
|
20977
|
+
const pluginRoot = resolve21(cacheRoot, marketplace, pluginName);
|
|
20978
|
+
if (!existsSync27(pluginRoot)) continue;
|
|
20979
|
+
for (const versionDir of readdirSync11(pluginRoot)) {
|
|
20980
|
+
const candidatePath = resolve21(pluginRoot, versionDir);
|
|
20981
|
+
try {
|
|
20982
|
+
const stats = statSync5(candidatePath);
|
|
20983
|
+
if (!stats.isDirectory()) continue;
|
|
20984
|
+
candidates.push({
|
|
20985
|
+
path: candidatePath,
|
|
20986
|
+
version: readInstalledManifestVersion(candidatePath, "codex") ?? versionDir,
|
|
20987
|
+
mtimeMs: stats.mtimeMs
|
|
20988
|
+
});
|
|
20989
|
+
} catch {
|
|
20990
|
+
}
|
|
20991
|
+
}
|
|
20992
|
+
}
|
|
20993
|
+
return candidates.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
20994
|
+
}
|
|
20995
|
+
function detectCodexCacheStaleness(pluginName, builtVersion) {
|
|
20996
|
+
if (!builtVersion) return void 0;
|
|
20997
|
+
const candidates = findCodexCacheCandidates(pluginName);
|
|
20998
|
+
if (candidates.length === 0) return void 0;
|
|
20999
|
+
if (candidates.some((candidate) => candidate.version === builtVersion)) return void 0;
|
|
21000
|
+
const newest = candidates[0];
|
|
21001
|
+
return `Codex active cache appears stale at ${newest.path}; cached version ${newest.version ?? "unknown"} does not match built version ${builtVersion}. Use Plugins > Refresh if available, or restart/reinstall Codex to load the current plugin bundle.`;
|
|
21002
|
+
}
|
|
21003
|
+
function detectStaleInstall(target, pluginName, consumerPath) {
|
|
21004
|
+
try {
|
|
21005
|
+
const details = lstatSync4(consumerPath);
|
|
21006
|
+
if (details.isSymbolicLink()) {
|
|
21007
|
+
const installedRealPath = realpathSync(consumerPath);
|
|
21008
|
+
const builtRealPath = realpathSync(target.sourceDir);
|
|
21009
|
+
if (installedRealPath !== builtRealPath) {
|
|
21010
|
+
return `installed symlink points to ${readlinkSync(consumerPath)}, not the current build at ${target.sourceDir}`;
|
|
21011
|
+
}
|
|
21012
|
+
}
|
|
21013
|
+
} catch {
|
|
21014
|
+
return void 0;
|
|
21015
|
+
}
|
|
21016
|
+
const builtVersion = readInstalledManifestVersion(target.sourceDir, target.platform);
|
|
21017
|
+
const installedVersion = readInstalledManifestVersion(consumerPath, target.platform);
|
|
21018
|
+
if (builtVersion && installedVersion && builtVersion !== installedVersion) {
|
|
21019
|
+
return `installed version ${installedVersion} does not match built version ${builtVersion}`;
|
|
21020
|
+
}
|
|
21021
|
+
if (target.platform === "codex") {
|
|
21022
|
+
return detectCodexCacheStaleness(pluginName, builtVersion);
|
|
21023
|
+
}
|
|
21024
|
+
return void 0;
|
|
21025
|
+
}
|
|
20290
21026
|
async function verifyInstall(config, options = {}) {
|
|
20291
21027
|
const rootDir = options.rootDir ?? process.cwd();
|
|
20292
21028
|
const distDir = resolve21(rootDir, config.outDir);
|
|
@@ -20314,12 +21050,39 @@ function printVerifyInstallResult(result) {
|
|
|
20314
21050
|
console.log(`${prefix} ${check.platform}: ${check.consumerPath}`);
|
|
20315
21051
|
console.log(` install path: ${check.installPath}`);
|
|
20316
21052
|
console.log(` built: ${check.built ? "yes" : "no"}; installed: ${check.installed ? "yes" : "no"}; errors: ${check.errors}; warnings: ${check.warnings}; infos: ${check.infos}`);
|
|
21053
|
+
if (check.stale) {
|
|
21054
|
+
console.log(` stale install: ${check.staleReason}`);
|
|
21055
|
+
}
|
|
21056
|
+
if (!check.ok) {
|
|
21057
|
+
for (const action of getVerifyInstallRecoveryActions(check)) {
|
|
21058
|
+
console.log(` fix: ${action}`);
|
|
21059
|
+
}
|
|
21060
|
+
}
|
|
20317
21061
|
}
|
|
20318
21062
|
console.log(result.ok ? "pluxx verify-install passed." : "pluxx verify-install failed.");
|
|
20319
21063
|
}
|
|
21064
|
+
function getVerifyInstallRecoveryActions(check) {
|
|
21065
|
+
const actions = [];
|
|
21066
|
+
if (!check.built) {
|
|
21067
|
+
actions.push(`run pluxx build --target ${check.platform}`);
|
|
21068
|
+
}
|
|
21069
|
+
if (check.built && !check.installed) {
|
|
21070
|
+
actions.push(`run pluxx install --target ${check.platform}${check.platform === "claude-code" ? " and accept/trust any hook prompt if expected" : ""}`);
|
|
21071
|
+
}
|
|
21072
|
+
if (check.stale) {
|
|
21073
|
+
actions.push(`rerun pluxx install --target ${check.platform} to replace the stale local install`);
|
|
21074
|
+
if (check.platform === "codex") {
|
|
21075
|
+
actions.push("in Codex, use Plugins > Refresh if available, or restart Codex so the plugin cache reloads");
|
|
21076
|
+
}
|
|
21077
|
+
}
|
|
21078
|
+
if (check.errors > 0 && actions.length === 0) {
|
|
21079
|
+
actions.push(`run pluxx doctor --consumer "${check.consumerPath}" for the detailed host-specific failure`);
|
|
21080
|
+
}
|
|
21081
|
+
return actions;
|
|
21082
|
+
}
|
|
20320
21083
|
|
|
20321
21084
|
// src/cli/behavioral.ts
|
|
20322
|
-
import { existsSync as
|
|
21085
|
+
import { existsSync as existsSync28, readFileSync as readFileSync16 } from "fs";
|
|
20323
21086
|
import { mkdtemp as mkdtemp2, rm as rm3 } from "fs/promises";
|
|
20324
21087
|
import { tmpdir as tmpdir4 } from "os";
|
|
20325
21088
|
import { resolve as resolve22 } from "path";
|
|
@@ -20356,12 +21119,12 @@ function loadBehavioralCases(rootDir, targets, promptOverride) {
|
|
|
20356
21119
|
}];
|
|
20357
21120
|
}
|
|
20358
21121
|
const filePath = resolve22(rootDir, BEHAVIORAL_CONFIG_PATH);
|
|
20359
|
-
if (!
|
|
21122
|
+
if (!existsSync28(filePath)) {
|
|
20360
21123
|
throw new Error(
|
|
20361
21124
|
`No behavioral smoke config found at ${BEHAVIORAL_CONFIG_PATH}. Add that file or pass --behavioral-prompt to define a real example query.`
|
|
20362
21125
|
);
|
|
20363
21126
|
}
|
|
20364
|
-
const parsed = JSON.parse(
|
|
21127
|
+
const parsed = JSON.parse(readFileSync16(filePath, "utf-8"));
|
|
20365
21128
|
if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.cases) || parsed.cases.length === 0) {
|
|
20366
21129
|
throw new Error(`${BEHAVIORAL_CONFIG_PATH} must contain a non-empty "cases" array.`);
|
|
20367
21130
|
}
|
|
@@ -20471,7 +21234,7 @@ async function executeBehavioralCommand(platform, command2, cwd) {
|
|
|
20471
21234
|
child.on("close", (code) => {
|
|
20472
21235
|
const stdout = Buffer.concat(stdoutChunks).toString("utf-8");
|
|
20473
21236
|
const stderr = Buffer.concat(stderrChunks).toString("utf-8");
|
|
20474
|
-
const codexMessage = codexLastMessagePath &&
|
|
21237
|
+
const codexMessage = codexLastMessagePath && existsSync28(codexLastMessagePath) ? readFileSync16(codexLastMessagePath, "utf-8") : "";
|
|
20475
21238
|
resolvePromise({
|
|
20476
21239
|
exitCode: code ?? 1,
|
|
20477
21240
|
response: codexMessage.trim() || stdout.trim() || stderr.trim()
|
|
@@ -20533,6 +21296,396 @@ function shellQuote2(value) {
|
|
|
20533
21296
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
20534
21297
|
}
|
|
20535
21298
|
|
|
21299
|
+
// src/cli/discover-installed-mcp.ts
|
|
21300
|
+
import { existsSync as existsSync29, readFileSync as readFileSync17 } from "fs";
|
|
21301
|
+
import { homedir as homedir2 } from "os";
|
|
21302
|
+
import { resolve as resolve23 } from "path";
|
|
21303
|
+
var INSTALLED_MCP_HOSTS = ["claude-code", "cursor", "codex", "opencode"];
|
|
21304
|
+
function discoverInstalledMcpServers(options = {}) {
|
|
21305
|
+
const rootDir = options.rootDir ?? process.cwd();
|
|
21306
|
+
const homeDir = options.homeDir ?? homedir2();
|
|
21307
|
+
const hostSet = new Set(options.hosts ?? INSTALLED_MCP_HOSTS);
|
|
21308
|
+
const results = [];
|
|
21309
|
+
const seen = /* @__PURE__ */ new Set();
|
|
21310
|
+
for (const candidate of installedMcpFileCandidates(rootDir, homeDir)) {
|
|
21311
|
+
if (!hostSet.has(candidate.host) || !existsSync29(candidate.path)) continue;
|
|
21312
|
+
const parsed = candidate.parser === "json" ? parseJsonMcpFile(candidate.path, candidate.host) : parseCodexTomlMcpFile(candidate.path);
|
|
21313
|
+
for (const discovered of parsed) {
|
|
21314
|
+
const key = `${discovered.host}:${discovered.serverName}:${discovered.sourcePath}`;
|
|
21315
|
+
if (seen.has(key)) continue;
|
|
21316
|
+
seen.add(key);
|
|
21317
|
+
results.push(discovered);
|
|
21318
|
+
}
|
|
21319
|
+
}
|
|
21320
|
+
return results.sort((a, b) => {
|
|
21321
|
+
const hostOrder = INSTALLED_MCP_HOSTS.indexOf(a.host) - INSTALLED_MCP_HOSTS.indexOf(b.host);
|
|
21322
|
+
if (hostOrder !== 0) return hostOrder;
|
|
21323
|
+
return a.serverName.localeCompare(b.serverName);
|
|
21324
|
+
});
|
|
21325
|
+
}
|
|
21326
|
+
function resolveInstalledMcpSelector(selector, candidates) {
|
|
21327
|
+
const trimmed = selector.trim();
|
|
21328
|
+
if (!trimmed) {
|
|
21329
|
+
throw new Error("Provide an installed MCP selector such as `exa` or `codex:exa`.");
|
|
21330
|
+
}
|
|
21331
|
+
const exactId = candidates.filter((candidate) => candidate.id === trimmed);
|
|
21332
|
+
if (exactId.length === 1) return exactId[0];
|
|
21333
|
+
const hostMatch = trimmed.match(/^([^:]+):(.+)$/);
|
|
21334
|
+
if (hostMatch) {
|
|
21335
|
+
const host = normalizeInstalledMcpHost(hostMatch[1]);
|
|
21336
|
+
const serverName = hostMatch[2];
|
|
21337
|
+
const matches = candidates.filter((candidate) => candidate.host === host && candidate.serverName === serverName);
|
|
21338
|
+
if (matches.length === 1) return matches[0];
|
|
21339
|
+
if (matches.length > 1) {
|
|
21340
|
+
throw new Error(`Installed MCP selector "${trimmed}" matched multiple config files. Use one of: ${matches.map((match) => match.id).join(", ")}`);
|
|
21341
|
+
}
|
|
21342
|
+
}
|
|
21343
|
+
const nameMatches = candidates.filter((candidate) => candidate.serverName === trimmed);
|
|
21344
|
+
if (nameMatches.length === 1) return nameMatches[0];
|
|
21345
|
+
if (nameMatches.length > 1) {
|
|
21346
|
+
throw new Error(`Installed MCP selector "${trimmed}" is ambiguous. Use one of: ${nameMatches.map((match) => match.id).join(", ")}`);
|
|
21347
|
+
}
|
|
21348
|
+
const available = candidates.map((candidate) => candidate.id).join(", ") || "none";
|
|
21349
|
+
throw new Error(`No installed MCP named "${trimmed}" was found. Available installed MCPs: ${available}`);
|
|
21350
|
+
}
|
|
21351
|
+
function normalizeInstalledMcpHost(value) {
|
|
21352
|
+
const normalized = value.trim().toLowerCase();
|
|
21353
|
+
const aliases = {
|
|
21354
|
+
claude: "claude-code",
|
|
21355
|
+
"claude-code": "claude-code",
|
|
21356
|
+
cursor: "cursor",
|
|
21357
|
+
codex: "codex",
|
|
21358
|
+
open: "opencode",
|
|
21359
|
+
opencode: "opencode"
|
|
21360
|
+
};
|
|
21361
|
+
const host = aliases[normalized];
|
|
21362
|
+
if (!host) {
|
|
21363
|
+
throw new Error(`Installed MCP host must be one of: ${INSTALLED_MCP_HOSTS.join(", ")}`);
|
|
21364
|
+
}
|
|
21365
|
+
return host;
|
|
21366
|
+
}
|
|
21367
|
+
function formatInstalledMcpSource(discovered) {
|
|
21368
|
+
const transport = discovered.server.transport;
|
|
21369
|
+
const endpoint = transport === "stdio" ? [discovered.server.command, ...discovered.server.args ?? []].join(" ") : discovered.server.url;
|
|
21370
|
+
return `${discovered.id} (${transport}: ${endpoint})`;
|
|
21371
|
+
}
|
|
21372
|
+
function installedMcpFileCandidates(rootDir, homeDir) {
|
|
21373
|
+
return [
|
|
21374
|
+
{ host: "claude-code", path: resolve23(rootDir, ".mcp.json"), parser: "json" },
|
|
21375
|
+
{ host: "claude-code", path: resolve23(rootDir, ".claude-plugin/plugin.json"), parser: "json" },
|
|
21376
|
+
{ host: "claude-code", path: resolve23(rootDir, ".claude/settings.json"), parser: "json" },
|
|
21377
|
+
{ host: "claude-code", path: resolve23(rootDir, ".claude/settings.local.json"), parser: "json" },
|
|
21378
|
+
{ host: "claude-code", path: resolve23(homeDir, ".claude/settings.json"), parser: "json" },
|
|
21379
|
+
{ host: "claude-code", path: resolve23(homeDir, ".claude/settings.local.json"), parser: "json" },
|
|
21380
|
+
{ host: "claude-code", path: resolve23(homeDir, ".claude.json"), parser: "json" },
|
|
21381
|
+
{ host: "cursor", path: resolve23(rootDir, "mcp.json"), parser: "json" },
|
|
21382
|
+
{ host: "cursor", path: resolve23(rootDir, ".cursor/mcp.json"), parser: "json" },
|
|
21383
|
+
{ host: "cursor", path: resolve23(homeDir, ".cursor/mcp.json"), parser: "json" },
|
|
21384
|
+
{ host: "codex", path: resolve23(rootDir, ".mcp.json"), parser: "json" },
|
|
21385
|
+
{ host: "codex", path: resolve23(rootDir, ".codex/config.toml"), parser: "toml" },
|
|
21386
|
+
{ host: "codex", path: resolve23(homeDir, ".codex/config.toml"), parser: "toml" },
|
|
21387
|
+
{ host: "opencode", path: resolve23(rootDir, "opencode.json"), parser: "json" },
|
|
21388
|
+
{ host: "opencode", path: resolve23(rootDir, ".opencode.json"), parser: "json" },
|
|
21389
|
+
{ host: "opencode", path: resolve23(homeDir, ".config/opencode/opencode.json"), parser: "json" }
|
|
21390
|
+
];
|
|
21391
|
+
}
|
|
21392
|
+
function parseJsonMcpFile(path, host) {
|
|
21393
|
+
try {
|
|
21394
|
+
const raw = JSON.parse(readFileSync17(path, "utf-8"));
|
|
21395
|
+
const servers = extractJsonMcpServers(raw, path, host);
|
|
21396
|
+
return Object.entries(servers).flatMap(([serverName, config]) => {
|
|
21397
|
+
const normalized = normalizeCommonMcpServer(config, host);
|
|
21398
|
+
if (!normalized) return [];
|
|
21399
|
+
return [toDiscovered(host, serverName, path, normalized.server, normalized.warnings)];
|
|
21400
|
+
});
|
|
21401
|
+
} catch {
|
|
21402
|
+
return [];
|
|
21403
|
+
}
|
|
21404
|
+
}
|
|
21405
|
+
function extractJsonMcpServers(raw, path, host) {
|
|
21406
|
+
if (host === "opencode" && raw.mcp && typeof raw.mcp === "object") {
|
|
21407
|
+
return raw.mcp;
|
|
21408
|
+
}
|
|
21409
|
+
if (raw.mcpServers && typeof raw.mcpServers === "object") {
|
|
21410
|
+
return raw.mcpServers;
|
|
21411
|
+
}
|
|
21412
|
+
if (host === "claude-code" && raw.projects && typeof raw.projects === "object") {
|
|
21413
|
+
const projectServers = {};
|
|
21414
|
+
for (const projectConfig of Object.values(raw.projects)) {
|
|
21415
|
+
if (!projectConfig || typeof projectConfig !== "object") continue;
|
|
21416
|
+
const mcpServers = projectConfig.mcpServers;
|
|
21417
|
+
if (!mcpServers || typeof mcpServers !== "object") continue;
|
|
21418
|
+
Object.assign(projectServers, mcpServers);
|
|
21419
|
+
}
|
|
21420
|
+
return projectServers;
|
|
21421
|
+
}
|
|
21422
|
+
if (typeof raw.mcpServers === "string") return {};
|
|
21423
|
+
if (path.endsWith("mcp.json") || path.endsWith(".mcp.json")) {
|
|
21424
|
+
return raw;
|
|
21425
|
+
}
|
|
21426
|
+
return {};
|
|
21427
|
+
}
|
|
21428
|
+
function parseCodexTomlMcpFile(path) {
|
|
21429
|
+
const text = readFileSync17(path, "utf-8");
|
|
21430
|
+
const servers = {};
|
|
21431
|
+
let currentServer;
|
|
21432
|
+
let currentSubtable;
|
|
21433
|
+
for (const rawLine of text.split(/\r?\n/)) {
|
|
21434
|
+
const line = stripTomlComment(rawLine).trim();
|
|
21435
|
+
if (!line) continue;
|
|
21436
|
+
const section = line.match(/^\[mcp_servers\.([A-Za-z0-9_.-]+)(?:\.([A-Za-z0-9_.-]+))?\]$/);
|
|
21437
|
+
if (section) {
|
|
21438
|
+
currentServer = section[1];
|
|
21439
|
+
currentSubtable = section[2];
|
|
21440
|
+
servers[currentServer] ??= {};
|
|
21441
|
+
if (currentSubtable) {
|
|
21442
|
+
const existing = servers[currentServer][currentSubtable];
|
|
21443
|
+
if (!existing || typeof existing !== "object" || Array.isArray(existing)) {
|
|
21444
|
+
servers[currentServer][currentSubtable] = {};
|
|
21445
|
+
}
|
|
21446
|
+
}
|
|
21447
|
+
continue;
|
|
21448
|
+
}
|
|
21449
|
+
if (!currentServer) continue;
|
|
21450
|
+
const assignment = line.match(/^([A-Za-z0-9_-]+)\s*=\s*(.+)$/);
|
|
21451
|
+
if (!assignment) continue;
|
|
21452
|
+
const key = assignment[1];
|
|
21453
|
+
const value = parseTomlValue(assignment[2].trim());
|
|
21454
|
+
if (currentSubtable) {
|
|
21455
|
+
const table = servers[currentServer][currentSubtable];
|
|
21456
|
+
table[key] = value;
|
|
21457
|
+
} else {
|
|
21458
|
+
servers[currentServer][key] = value;
|
|
21459
|
+
}
|
|
21460
|
+
}
|
|
21461
|
+
return Object.entries(servers).flatMap(([serverName, config]) => {
|
|
21462
|
+
const normalized = normalizeCommonMcpServer(config, "codex");
|
|
21463
|
+
if (!normalized) return [];
|
|
21464
|
+
return [toDiscovered("codex", serverName, path, normalized.server, normalized.warnings)];
|
|
21465
|
+
});
|
|
21466
|
+
}
|
|
21467
|
+
function normalizeCommonMcpServer(config, host) {
|
|
21468
|
+
if (!config || typeof config !== "object") return null;
|
|
21469
|
+
const cfg = config;
|
|
21470
|
+
const warnings = [];
|
|
21471
|
+
const auth = inferAuth(cfg, warnings);
|
|
21472
|
+
const remoteUrl = firstString(cfg.url, cfg.endpoint);
|
|
21473
|
+
if (remoteUrl) {
|
|
21474
|
+
const server = cfg.type === "sse" || cfg.transport === "sse" ? {
|
|
21475
|
+
transport: "sse",
|
|
21476
|
+
url: remoteUrl,
|
|
21477
|
+
...auth ? { auth } : {}
|
|
21478
|
+
} : {
|
|
21479
|
+
transport: "http",
|
|
21480
|
+
url: remoteUrl,
|
|
21481
|
+
...auth ? { auth } : {}
|
|
21482
|
+
};
|
|
21483
|
+
return { server, warnings };
|
|
21484
|
+
}
|
|
21485
|
+
const command2 = normalizeCommand(cfg.command, host);
|
|
21486
|
+
if (!command2) return null;
|
|
21487
|
+
const args2 = normalizeArgs(cfg.command, cfg.args, host);
|
|
21488
|
+
const env = normalizeEnv(cfg.env, warnings);
|
|
21489
|
+
return {
|
|
21490
|
+
server: {
|
|
21491
|
+
transport: "stdio",
|
|
21492
|
+
command: command2,
|
|
21493
|
+
...args2.length > 0 ? { args: args2 } : {},
|
|
21494
|
+
...Object.keys(env).length > 0 ? { env } : {},
|
|
21495
|
+
...auth ? { auth } : {}
|
|
21496
|
+
},
|
|
21497
|
+
warnings
|
|
21498
|
+
};
|
|
21499
|
+
}
|
|
21500
|
+
function normalizeCommand(value, host) {
|
|
21501
|
+
if (typeof value === "string") return value;
|
|
21502
|
+
if (host === "opencode" && Array.isArray(value) && typeof value[0] === "string") {
|
|
21503
|
+
return value[0];
|
|
21504
|
+
}
|
|
21505
|
+
return void 0;
|
|
21506
|
+
}
|
|
21507
|
+
function normalizeArgs(command2, args2, host) {
|
|
21508
|
+
if (host === "opencode" && Array.isArray(command2)) {
|
|
21509
|
+
return command2.slice(1).map(String);
|
|
21510
|
+
}
|
|
21511
|
+
return normalizeStringArray(args2);
|
|
21512
|
+
}
|
|
21513
|
+
function normalizeStringArray(value) {
|
|
21514
|
+
if (!Array.isArray(value)) return [];
|
|
21515
|
+
return value.map(String);
|
|
21516
|
+
}
|
|
21517
|
+
function normalizeEnv(value, warnings) {
|
|
21518
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
21519
|
+
const env = {};
|
|
21520
|
+
for (const [key, rawValue] of Object.entries(value)) {
|
|
21521
|
+
if (typeof rawValue !== "string") continue;
|
|
21522
|
+
const placeholder = envPlaceholder(rawValue);
|
|
21523
|
+
if (placeholder) {
|
|
21524
|
+
env[key] = placeholder;
|
|
21525
|
+
continue;
|
|
21526
|
+
}
|
|
21527
|
+
if (looksSecretKey(key)) {
|
|
21528
|
+
env[key] = `\${${key}}`;
|
|
21529
|
+
warnings.push(`Literal secret-like env value for ${key} was replaced with \${${key}}.`);
|
|
21530
|
+
continue;
|
|
21531
|
+
}
|
|
21532
|
+
env[key] = rawValue;
|
|
21533
|
+
}
|
|
21534
|
+
return env;
|
|
21535
|
+
}
|
|
21536
|
+
function inferAuth(cfg, warnings) {
|
|
21537
|
+
const bearerTokenEnv = firstString(cfg.bearer_token_env_var, cfg.bearerTokenEnvVar);
|
|
21538
|
+
if (bearerTokenEnv) {
|
|
21539
|
+
return {
|
|
21540
|
+
type: "bearer",
|
|
21541
|
+
envVar: bearerTokenEnv,
|
|
21542
|
+
headerName: "Authorization",
|
|
21543
|
+
headerTemplate: "Bearer ${value}"
|
|
21544
|
+
};
|
|
21545
|
+
}
|
|
21546
|
+
const envHttpHeaders = cfg.env_http_headers ?? cfg.envHttpHeaders;
|
|
21547
|
+
if (envHttpHeaders && typeof envHttpHeaders === "object" && !Array.isArray(envHttpHeaders)) {
|
|
21548
|
+
const [headerName, envVar] = Object.entries(envHttpHeaders).find(([, value]) => typeof value === "string") ?? [];
|
|
21549
|
+
if (typeof headerName === "string" && typeof envVar === "string") {
|
|
21550
|
+
return {
|
|
21551
|
+
type: headerName.toLowerCase() === "authorization" ? "bearer" : "header",
|
|
21552
|
+
envVar,
|
|
21553
|
+
headerName,
|
|
21554
|
+
headerTemplate: headerName.toLowerCase() === "authorization" ? "Bearer ${value}" : "${value}"
|
|
21555
|
+
};
|
|
21556
|
+
}
|
|
21557
|
+
}
|
|
21558
|
+
const headers = cfg.headers ?? cfg.http_headers ?? cfg.httpHeaders;
|
|
21559
|
+
if (!headers || typeof headers !== "object" || Array.isArray(headers)) return void 0;
|
|
21560
|
+
const headerEntries = Object.entries(headers);
|
|
21561
|
+
for (const [headerName, rawValue] of headerEntries) {
|
|
21562
|
+
if (typeof rawValue !== "string") continue;
|
|
21563
|
+
const envVar = extractEnvVar(rawValue);
|
|
21564
|
+
if (envVar) {
|
|
21565
|
+
return {
|
|
21566
|
+
type: headerName.toLowerCase() === "authorization" ? "bearer" : "header",
|
|
21567
|
+
envVar,
|
|
21568
|
+
headerName,
|
|
21569
|
+
headerTemplate: rawValue.replace(envReferencePattern(envVar), "${value}")
|
|
21570
|
+
};
|
|
21571
|
+
}
|
|
21572
|
+
if (looksSecretValue(rawValue)) {
|
|
21573
|
+
warnings.push(`Literal ${headerName} header value was not copied. Re-run init with --auth-env if this server needs auth.`);
|
|
21574
|
+
}
|
|
21575
|
+
}
|
|
21576
|
+
return void 0;
|
|
21577
|
+
}
|
|
21578
|
+
function toDiscovered(host, serverName, sourcePath, server, warnings) {
|
|
21579
|
+
return {
|
|
21580
|
+
id: `${host}:${serverName}`,
|
|
21581
|
+
host,
|
|
21582
|
+
serverName,
|
|
21583
|
+
sourcePath,
|
|
21584
|
+
server,
|
|
21585
|
+
warnings
|
|
21586
|
+
};
|
|
21587
|
+
}
|
|
21588
|
+
function firstString(...values) {
|
|
21589
|
+
for (const value of values) {
|
|
21590
|
+
if (typeof value === "string" && value.trim()) return value.trim();
|
|
21591
|
+
}
|
|
21592
|
+
return void 0;
|
|
21593
|
+
}
|
|
21594
|
+
function envPlaceholder(value) {
|
|
21595
|
+
const envVar = extractEnvVar(value);
|
|
21596
|
+
return envVar ? `\${${envVar}}` : void 0;
|
|
21597
|
+
}
|
|
21598
|
+
function extractEnvVar(value) {
|
|
21599
|
+
const match = value.match(/\$\{(?:env:)?([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*)/);
|
|
21600
|
+
return match?.[1] ?? match?.[2];
|
|
21601
|
+
}
|
|
21602
|
+
function envReferencePattern(envVar) {
|
|
21603
|
+
return new RegExp(`\\$\\{(?:env:)?${escapeRegExp2(envVar)}\\}|\\$${escapeRegExp2(envVar)}`);
|
|
21604
|
+
}
|
|
21605
|
+
function escapeRegExp2(value) {
|
|
21606
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
21607
|
+
}
|
|
21608
|
+
function looksSecretKey(value) {
|
|
21609
|
+
return /(api|auth|secret|token|key|password|credential)/i.test(value);
|
|
21610
|
+
}
|
|
21611
|
+
function looksSecretValue(value) {
|
|
21612
|
+
return value.length >= 16 && !extractEnvVar(value);
|
|
21613
|
+
}
|
|
21614
|
+
function stripTomlComment(line) {
|
|
21615
|
+
let inString = false;
|
|
21616
|
+
let quote2 = "";
|
|
21617
|
+
for (let i = 0; i < line.length; i += 1) {
|
|
21618
|
+
const char = line[i];
|
|
21619
|
+
if ((char === '"' || char === "'") && line[i - 1] !== "\\") {
|
|
21620
|
+
if (!inString) {
|
|
21621
|
+
inString = true;
|
|
21622
|
+
quote2 = char;
|
|
21623
|
+
} else if (quote2 === char) {
|
|
21624
|
+
inString = false;
|
|
21625
|
+
quote2 = "";
|
|
21626
|
+
}
|
|
21627
|
+
continue;
|
|
21628
|
+
}
|
|
21629
|
+
if (char === "#" && !inString) return line.slice(0, i);
|
|
21630
|
+
}
|
|
21631
|
+
return line;
|
|
21632
|
+
}
|
|
21633
|
+
function parseTomlValue(value) {
|
|
21634
|
+
if (value.startsWith('"') && value.endsWith('"')) return unquoteTomlString(value);
|
|
21635
|
+
if (value.startsWith("'") && value.endsWith("'")) return value.slice(1, -1);
|
|
21636
|
+
if (value.startsWith("[") && value.endsWith("]")) {
|
|
21637
|
+
const inner = value.slice(1, -1).trim();
|
|
21638
|
+
if (!inner) return [];
|
|
21639
|
+
return splitTomlList(inner).map((part) => parseTomlValue(part.trim()));
|
|
21640
|
+
}
|
|
21641
|
+
if (value.startsWith("{") && value.endsWith("}")) {
|
|
21642
|
+
const inner = value.slice(1, -1).trim();
|
|
21643
|
+
const result = {};
|
|
21644
|
+
if (!inner) return result;
|
|
21645
|
+
for (const part of splitTomlList(inner)) {
|
|
21646
|
+
const assignment = part.match(/^([A-Za-z0-9_-]+)\s*=\s*(.+)$/);
|
|
21647
|
+
if (!assignment) continue;
|
|
21648
|
+
result[assignment[1]] = parseTomlValue(assignment[2].trim());
|
|
21649
|
+
}
|
|
21650
|
+
return result;
|
|
21651
|
+
}
|
|
21652
|
+
if (value === "true") return true;
|
|
21653
|
+
if (value === "false") return false;
|
|
21654
|
+
return value;
|
|
21655
|
+
}
|
|
21656
|
+
function splitTomlList(value) {
|
|
21657
|
+
const parts = [];
|
|
21658
|
+
let current = "";
|
|
21659
|
+
let inString = false;
|
|
21660
|
+
let quote2 = "";
|
|
21661
|
+
let braceDepth = 0;
|
|
21662
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
21663
|
+
const char = value[i];
|
|
21664
|
+
if ((char === '"' || char === "'") && value[i - 1] !== "\\") {
|
|
21665
|
+
if (!inString) {
|
|
21666
|
+
inString = true;
|
|
21667
|
+
quote2 = char;
|
|
21668
|
+
} else if (quote2 === char) {
|
|
21669
|
+
inString = false;
|
|
21670
|
+
quote2 = "";
|
|
21671
|
+
}
|
|
21672
|
+
}
|
|
21673
|
+
if (!inString && char === "{") braceDepth += 1;
|
|
21674
|
+
if (!inString && char === "}") braceDepth -= 1;
|
|
21675
|
+
if (!inString && braceDepth === 0 && char === ",") {
|
|
21676
|
+
parts.push(current);
|
|
21677
|
+
current = "";
|
|
21678
|
+
continue;
|
|
21679
|
+
}
|
|
21680
|
+
current += char;
|
|
21681
|
+
}
|
|
21682
|
+
if (current.trim()) parts.push(current);
|
|
21683
|
+
return parts;
|
|
21684
|
+
}
|
|
21685
|
+
function unquoteTomlString(value) {
|
|
21686
|
+
return value.slice(1, -1).replace(/\\"/g, '"').replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\\\/g, "\\");
|
|
21687
|
+
}
|
|
21688
|
+
|
|
20536
21689
|
// src/cli/index.ts
|
|
20537
21690
|
var CLI_PACKAGE_NAME = "@orchid-labs/pluxx";
|
|
20538
21691
|
var rawArgs = process.argv.slice(2);
|
|
@@ -20583,6 +21736,9 @@ async function main() {
|
|
|
20583
21736
|
case "mcp":
|
|
20584
21737
|
await runMcp();
|
|
20585
21738
|
break;
|
|
21739
|
+
case "discover-mcp":
|
|
21740
|
+
await runDiscoverMcp();
|
|
21741
|
+
break;
|
|
20586
21742
|
case "autopilot":
|
|
20587
21743
|
await runAutopilot();
|
|
20588
21744
|
break;
|
|
@@ -20596,6 +21752,10 @@ async function main() {
|
|
|
20596
21752
|
await runVerifyInstall();
|
|
20597
21753
|
break;
|
|
20598
21754
|
case "publish":
|
|
21755
|
+
if (isHelpRequested(args.slice(1))) {
|
|
21756
|
+
printPublishHelp();
|
|
21757
|
+
break;
|
|
21758
|
+
}
|
|
20599
21759
|
await runPublishCommand();
|
|
20600
21760
|
break;
|
|
20601
21761
|
case "uninstall":
|
|
@@ -20634,9 +21794,12 @@ function normalizeTopLevelArgs(input) {
|
|
|
20634
21794
|
}
|
|
20635
21795
|
return input;
|
|
20636
21796
|
}
|
|
21797
|
+
function isHelpRequested(input) {
|
|
21798
|
+
return input.includes("--help") || input.includes("-h");
|
|
21799
|
+
}
|
|
20637
21800
|
function getCliPackageVersion() {
|
|
20638
21801
|
const packageJsonPath = new URL("../../package.json", import.meta.url);
|
|
20639
|
-
const raw = JSON.parse(
|
|
21802
|
+
const raw = JSON.parse(readFileSync18(packageJsonPath, "utf-8"));
|
|
20640
21803
|
if (typeof raw.version !== "string" || raw.version.trim() === "") {
|
|
20641
21804
|
throw new Error("Unable to determine the installed pluxx version from package.json.");
|
|
20642
21805
|
}
|
|
@@ -20748,20 +21911,21 @@ function planAutopilotPasses(input) {
|
|
|
20748
21911
|
};
|
|
20749
21912
|
}
|
|
20750
21913
|
function countAutopilotSteps(input) {
|
|
20751
|
-
return 2 + Number(input.taxonomy.enabled) + Number(input.instructions.enabled) + Number(input.review.enabled) + Number(input.verify);
|
|
21914
|
+
return 2 + Number(input.taxonomy.enabled) + Number(input.instructions.enabled) + Number(input.review.enabled) + Number(input.verify) + Number(input.install) + Number(input.behavioral);
|
|
20752
21915
|
}
|
|
20753
21916
|
function formatAutopilotPassLine(label, decision) {
|
|
20754
21917
|
return `${label}: ${decision.enabled ? "run" : "skip"} (${decision.reason})`;
|
|
20755
21918
|
}
|
|
20756
21919
|
function summarizeAutopilotWorkload(input) {
|
|
20757
21920
|
const agentPassCount = Number(input.taxonomy.enabled) + Number(input.instructions.enabled) + Number(input.review.enabled);
|
|
21921
|
+
const suffix = `${input.verify ? " + verification" : ""}${input.install ? " + install" : ""}${input.behavioral ? " + behavioral smoke" : ""}`;
|
|
20758
21922
|
if (agentPassCount === 0 && !input.verify) {
|
|
20759
|
-
return
|
|
21923
|
+
return `deterministic scaffold${input.install ? " + install" : ""}${input.behavioral ? " + behavioral smoke" : ""}`;
|
|
20760
21924
|
}
|
|
20761
21925
|
if (agentPassCount === 0) {
|
|
20762
|
-
return
|
|
21926
|
+
return `deterministic scaffold${suffix}`;
|
|
20763
21927
|
}
|
|
20764
|
-
return `${agentPassCount} agent pass${agentPassCount === 1 ? "" : "es"}${
|
|
21928
|
+
return `${agentPassCount} agent pass${agentPassCount === 1 ? "" : "es"}${suffix}`;
|
|
20765
21929
|
}
|
|
20766
21930
|
function formatDuration(durationMs) {
|
|
20767
21931
|
if (durationMs === void 0) {
|
|
@@ -21004,6 +22168,16 @@ function parseTargetFlagValues(rawArgs2) {
|
|
|
21004
22168
|
if (!values) return void 0;
|
|
21005
22169
|
return parseTargetPlatforms(values.join(","));
|
|
21006
22170
|
}
|
|
22171
|
+
function parseInstallTargetFlag(rawArgs2, targets) {
|
|
22172
|
+
const raw = readOption2(rawArgs2, "--install-target");
|
|
22173
|
+
if (!raw) return [targets[0]];
|
|
22174
|
+
const installTargets = parseTargetPlatforms(raw);
|
|
22175
|
+
const unsupported = installTargets.filter((target) => !targets.includes(target));
|
|
22176
|
+
if (unsupported.length > 0) {
|
|
22177
|
+
throw new Error(`--install-target must be one of the configured autopilot targets: ${targets.join(", ")}`);
|
|
22178
|
+
}
|
|
22179
|
+
return installTargets;
|
|
22180
|
+
}
|
|
21007
22181
|
function defaultHookMode(source) {
|
|
21008
22182
|
if (source.auth?.type && source.auth.type !== "none" && source.auth.envVar) {
|
|
21009
22183
|
return "safe";
|
|
@@ -21296,7 +22470,7 @@ async function planInitContextArtifactFiles(rootDir, contextPack) {
|
|
|
21296
22470
|
return plannedFiles;
|
|
21297
22471
|
}
|
|
21298
22472
|
async function planAuxiliaryFile(rootDir, relativePath, content) {
|
|
21299
|
-
const filePath =
|
|
22473
|
+
const filePath = resolve24(rootDir, relativePath);
|
|
21300
22474
|
const action = await planTextFileAction(filePath, content);
|
|
21301
22475
|
return {
|
|
21302
22476
|
relativePath,
|
|
@@ -21326,6 +22500,7 @@ function formatMcpDiscoverySummary(introspection) {
|
|
|
21326
22500
|
function parseInitFromMcpOptions(rawArgs2, initialName, initialSource) {
|
|
21327
22501
|
return {
|
|
21328
22502
|
source: initialSource ?? readOption2(rawArgs2, "--from-mcp"),
|
|
22503
|
+
installedMcp: readOption2(rawArgs2, "--from-installed-mcp"),
|
|
21329
22504
|
assumeDefaults: rawArgs2.includes("--yes"),
|
|
21330
22505
|
name: readOption2(rawArgs2, "--name") ?? initialName,
|
|
21331
22506
|
author: readOption2(rawArgs2, "--author"),
|
|
@@ -21354,11 +22529,49 @@ function toKebabCase3(value) {
|
|
|
21354
22529
|
function toTsString(value) {
|
|
21355
22530
|
return JSON.stringify(value);
|
|
21356
22531
|
}
|
|
22532
|
+
function parseInstalledMcpHosts(rawArgs2) {
|
|
22533
|
+
const hostValues = readMultiValueOption(rawArgs2, "--host") ?? readMultiValueOption(rawArgs2, "--hosts");
|
|
22534
|
+
if (!hostValues) return void 0;
|
|
22535
|
+
return hostValues.flatMap((value) => value.split(",")).map((value) => value.trim()).filter(Boolean).map(normalizeInstalledMcpHost);
|
|
22536
|
+
}
|
|
22537
|
+
function resolveInstalledMcpForInit(selector, hosts) {
|
|
22538
|
+
const discovered = discoverInstalledMcpServers({ hosts });
|
|
22539
|
+
return resolveInstalledMcpSelector(selector, discovered);
|
|
22540
|
+
}
|
|
22541
|
+
async function runDiscoverMcp() {
|
|
22542
|
+
const hosts = parseInstalledMcpHosts(args);
|
|
22543
|
+
const discovered = discoverInstalledMcpServers({ hosts });
|
|
22544
|
+
if (runtime.jsonOutput) {
|
|
22545
|
+
printJson({
|
|
22546
|
+
count: discovered.length,
|
|
22547
|
+
servers: discovered
|
|
22548
|
+
});
|
|
22549
|
+
return;
|
|
22550
|
+
}
|
|
22551
|
+
if (discovered.length === 0) {
|
|
22552
|
+
console.log("No installed MCP servers found.");
|
|
22553
|
+
console.log(`Searched hosts: ${(hosts ?? INSTALLED_MCP_HOSTS).join(", ")}`);
|
|
22554
|
+
return;
|
|
22555
|
+
}
|
|
22556
|
+
console.log("Installed MCP servers:");
|
|
22557
|
+
for (const server of discovered) {
|
|
22558
|
+
console.log(` ${server.id}`);
|
|
22559
|
+
console.log(` source: ${server.sourcePath}`);
|
|
22560
|
+
console.log(` server: ${formatInstalledMcpSource(server)}`);
|
|
22561
|
+
for (const warning of server.warnings) {
|
|
22562
|
+
console.log(` warning: ${warning}`);
|
|
22563
|
+
}
|
|
22564
|
+
}
|
|
22565
|
+
console.log("");
|
|
22566
|
+
console.log("Import one:");
|
|
22567
|
+
console.log(` pluxx init --from-installed-mcp ${discovered[0].id} --yes`);
|
|
22568
|
+
}
|
|
21357
22569
|
async function runInit() {
|
|
21358
22570
|
const positionalName = args[1] && !args[1].startsWith("-") ? args[1] : void 0;
|
|
21359
22571
|
const fromMcpFlag = args.indexOf("--from-mcp");
|
|
22572
|
+
const fromInstalledMcpFlag = args.indexOf("--from-installed-mcp");
|
|
21360
22573
|
const fromMcpInput = fromMcpFlag !== -1 && args[fromMcpFlag + 1] && !args[fromMcpFlag + 1].startsWith("-") ? args[fromMcpFlag + 1] : void 0;
|
|
21361
|
-
if (fromMcpFlag !== -1) {
|
|
22574
|
+
if (fromMcpFlag !== -1 || fromInstalledMcpFlag !== -1) {
|
|
21362
22575
|
await runInitFromMcp(positionalName, fromMcpInput);
|
|
21363
22576
|
return;
|
|
21364
22577
|
}
|
|
@@ -21442,7 +22655,7 @@ ${mcpBlock}${brandBlock}
|
|
|
21442
22655
|
targets: [${targetsList}],
|
|
21443
22656
|
})
|
|
21444
22657
|
`;
|
|
21445
|
-
await writeTextFile(
|
|
22658
|
+
await writeTextFile(resolve24(process.cwd(), "pluxx.config.ts"), template);
|
|
21446
22659
|
const skillDir = `skills/${skillName}`;
|
|
21447
22660
|
await mkdir4(skillDir, { recursive: true });
|
|
21448
22661
|
const skillContent = `---
|
|
@@ -21464,7 +22677,7 @@ Describe how agents should use this skill.
|
|
|
21464
22677
|
Example prompt or command here
|
|
21465
22678
|
\`\`\`
|
|
21466
22679
|
`;
|
|
21467
|
-
await writeTextFile(
|
|
22680
|
+
await writeTextFile(resolve24(process.cwd(), `${skillDir}/SKILL.md`), skillContent);
|
|
21468
22681
|
console.log("");
|
|
21469
22682
|
console.log(" Created:");
|
|
21470
22683
|
console.log(" pluxx.config.ts");
|
|
@@ -21493,14 +22706,15 @@ async function runInitFromMcp(initialName, initialSource) {
|
|
|
21493
22706
|
const contextPaths = options.contextPaths ?? [];
|
|
21494
22707
|
const ingestProvider = options.ingestProvider ? parseChoiceOption(options.ingestProvider, AGENT_INGEST_PROVIDERS, "Ingestion provider") : void 0;
|
|
21495
22708
|
if (!options.jsonOutput && !runtime.quiet) {
|
|
21496
|
-
mt("pluxx init --from-mcp");
|
|
22709
|
+
mt(options.installedMcp ? "pluxx init --from-installed-mcp" : "pluxx init --from-mcp");
|
|
21497
22710
|
}
|
|
21498
22711
|
try {
|
|
21499
|
-
const
|
|
22712
|
+
const installedMcpSource = options.installedMcp ? resolveInstalledMcpForInit(options.installedMcp, parseInstalledMcpHosts(args)) : void 0;
|
|
22713
|
+
const rawSource = installedMcpSource ? formatInstalledMcpSource(installedMcpSource) : options.source ?? (interactive ? await clackText("MCP server URL or local command") : "");
|
|
21500
22714
|
if (!rawSource) {
|
|
21501
|
-
throw new Error("Provide an MCP server URL or
|
|
22715
|
+
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
22716
|
}
|
|
21503
|
-
let source = parseMcpSourceInput(rawSource, options.transport);
|
|
22717
|
+
let source = installedMcpSource?.server ?? parseMcpSourceInput(rawSource, options.transport);
|
|
21504
22718
|
let introspectionSource = source;
|
|
21505
22719
|
const configuredRemoteAuth = source.transport === "stdio" ? void 0 : buildRemoteAuthConfig(options);
|
|
21506
22720
|
if (configuredRemoteAuth && !source.auth) {
|
|
@@ -21653,7 +22867,7 @@ ${formatAuthRequiredMessage("init", retryError, source)}`);
|
|
|
21653
22867
|
if (!options.jsonOutput && !runtime.quiet) {
|
|
21654
22868
|
O2.step("Step 2/4 \xB7 Plugin identity");
|
|
21655
22869
|
}
|
|
21656
|
-
const defaultPluginName = options.name ? toKebabCase3(options.name) : derivePluginName(introspection, source);
|
|
22870
|
+
const defaultPluginName = options.name ? toKebabCase3(options.name) : installedMcpSource?.serverName ? toKebabCase3(installedMcpSource.serverName) : derivePluginName(introspection, source);
|
|
21657
22871
|
const pluginName = toKebabCase3(
|
|
21658
22872
|
options.name ?? (interactive ? await clackText("Plugin name", defaultPluginName) : defaultPluginName)
|
|
21659
22873
|
);
|
|
@@ -21687,6 +22901,7 @@ ${formatAuthRequiredMessage("init", retryError, source)}`);
|
|
|
21687
22901
|
source,
|
|
21688
22902
|
runtimeAuthMode,
|
|
21689
22903
|
introspection,
|
|
22904
|
+
serverName: installedMcpSource?.serverName,
|
|
21690
22905
|
displayName,
|
|
21691
22906
|
description: sourcedContextPack?.docsContext?.shortDescription,
|
|
21692
22907
|
websiteUrl,
|
|
@@ -21708,7 +22923,7 @@ ${formatAuthRequiredMessage("init", retryError, source)}`);
|
|
|
21708
22923
|
if (!runtime.dryRun) {
|
|
21709
22924
|
await applyMcpScaffoldPlan(process.cwd(), plan);
|
|
21710
22925
|
for (const file of contextArtifactFiles) {
|
|
21711
|
-
await writeTextFile(
|
|
22926
|
+
await writeTextFile(resolve24(process.cwd(), file.relativePath), file.content);
|
|
21712
22927
|
}
|
|
21713
22928
|
}
|
|
21714
22929
|
const lintResult = runtime.dryRun ? { errors: 0, warnings: 0, issues: [] } : await lintProject(process.cwd());
|
|
@@ -21778,6 +22993,11 @@ ${formatAuthRequiredMessage("init", retryError, source)}`);
|
|
|
21778
22993
|
O2.info(n);
|
|
21779
22994
|
}
|
|
21780
22995
|
}
|
|
22996
|
+
if (installedMcpSource && installedMcpSource.warnings.length > 0) {
|
|
22997
|
+
for (const warning of installedMcpSource.warnings) {
|
|
22998
|
+
O2.warn(`Installed MCP import: ${warning}`);
|
|
22999
|
+
}
|
|
23000
|
+
}
|
|
21781
23001
|
if (summary.quality.issues.length > 0) {
|
|
21782
23002
|
for (const line of formatMcpQualityLines(summary.quality)) {
|
|
21783
23003
|
O2.info(line);
|
|
@@ -21871,7 +23091,7 @@ async function runSync() {
|
|
|
21871
23091
|
async function runDoctor() {
|
|
21872
23092
|
const consumerMode = readFlag(args, "--consumer");
|
|
21873
23093
|
const doctorPath = args.slice(1).find((value) => !value.startsWith("-"));
|
|
21874
|
-
const rootDir = doctorPath ?
|
|
23094
|
+
const rootDir = doctorPath ? resolve24(process.cwd(), doctorPath) : process.cwd();
|
|
21875
23095
|
const report = consumerMode ? await doctorConsumer(rootDir) : await doctorProject(rootDir);
|
|
21876
23096
|
if (runtime.jsonOutput) {
|
|
21877
23097
|
printJson(report);
|
|
@@ -22056,6 +23276,9 @@ async function runAutopilot() {
|
|
|
22056
23276
|
const attach = readOption2(args, "--attach");
|
|
22057
23277
|
const reviewRequested = args.includes("--review");
|
|
22058
23278
|
const verify = !args.includes("--no-verify");
|
|
23279
|
+
const installRequested = args.includes("--install");
|
|
23280
|
+
const behavioralRequested = args.includes("--behavioral");
|
|
23281
|
+
const behavioralPrompt = readOption2(args, "--behavioral-prompt");
|
|
22059
23282
|
const verboseRunner = args.includes("--verbose-runner");
|
|
22060
23283
|
const interactive = !runtime.jsonOutput && runtime.isInteractive && !initOptions.assumeDefaults;
|
|
22061
23284
|
let authEnv = initOptions.authEnv;
|
|
@@ -22064,17 +23287,23 @@ async function runAutopilot() {
|
|
|
22064
23287
|
let authTemplate = initOptions.authTemplate;
|
|
22065
23288
|
let runtimeAuthMode = resolveRuntimeAuthMode(initOptions.runtimeAuth);
|
|
22066
23289
|
if (!initOptions.source && !interactive) {
|
|
22067
|
-
console.error(`Usage: pluxx autopilot --from-mcp <source> --runner <${AGENT_RUNNERS.join("|")}> [--mode <${AUTOPILOT_MODES.join("|")}>] [--name NAME] [--display-name NAME] [--author NAME] [--targets <platforms>] [--grouping workflow|tool] [--hooks none|safe] [--approve-mcp-tools] [--auth-env ENV] [--auth-type bearer|header|platform] [--auth-header NAME] [--auth-template TEMPLATE] [--runtime-auth inline|platform] [--oauth-wrapper] [--website URL] [--docs URL] [--ingest-provider <${AGENT_INGEST_PROVIDERS.join("|")}>] [--context <files...>] [--review] [--no-verify] [--verbose-runner] [--json] [--dry-run] [--quiet]`);
|
|
23290
|
+
console.error(`Usage: pluxx autopilot --from-mcp <source> --runner <${AGENT_RUNNERS.join("|")}> [--mode <${AUTOPILOT_MODES.join("|")}>] [--name NAME] [--display-name NAME] [--author NAME] [--targets <platforms>] [--grouping workflow|tool] [--hooks none|safe] [--approve-mcp-tools] [--auth-env ENV] [--auth-type bearer|header|platform] [--auth-header NAME] [--auth-template TEMPLATE] [--runtime-auth inline|platform] [--oauth-wrapper] [--website URL] [--docs URL] [--ingest-provider <${AGENT_INGEST_PROVIDERS.join("|")}>] [--context <files...>] [--review] [--install] [--install-target <platform>] [--trust] [--behavioral] [--behavioral-prompt TEXT] [--no-verify] [--verbose-runner] [--json] [--dry-run] [--quiet]`);
|
|
22068
23291
|
process.exit(1);
|
|
22069
23292
|
}
|
|
22070
23293
|
if ((!runnerRaw || !AGENT_RUNNERS.includes(runnerRaw)) && !interactive) {
|
|
22071
|
-
console.error(`Usage: pluxx autopilot --from-mcp <source> --runner <${AGENT_RUNNERS.join("|")}> [--mode <${AUTOPILOT_MODES.join("|")}>] [--name NAME] [--display-name NAME] [--author NAME] [--targets <platforms>] [--grouping workflow|tool] [--hooks none|safe] [--approve-mcp-tools] [--auth-env ENV] [--auth-type bearer|header|platform] [--auth-header NAME] [--auth-template TEMPLATE] [--runtime-auth inline|platform] [--oauth-wrapper] [--website URL] [--docs URL] [--ingest-provider <${AGENT_INGEST_PROVIDERS.join("|")}>] [--context <files...>] [--review] [--no-verify] [--verbose-runner] [--json] [--dry-run] [--quiet]`);
|
|
23294
|
+
console.error(`Usage: pluxx autopilot --from-mcp <source> --runner <${AGENT_RUNNERS.join("|")}> [--mode <${AUTOPILOT_MODES.join("|")}>] [--name NAME] [--display-name NAME] [--author NAME] [--targets <platforms>] [--grouping workflow|tool] [--hooks none|safe] [--approve-mcp-tools] [--auth-env ENV] [--auth-type bearer|header|platform] [--auth-header NAME] [--auth-template TEMPLATE] [--runtime-auth inline|platform] [--oauth-wrapper] [--website URL] [--docs URL] [--ingest-provider <${AGENT_INGEST_PROVIDERS.join("|")}>] [--context <files...>] [--review] [--install] [--install-target <platform>] [--trust] [--behavioral] [--behavioral-prompt TEXT] [--no-verify] [--verbose-runner] [--json] [--dry-run] [--quiet]`);
|
|
22072
23295
|
process.exit(1);
|
|
22073
23296
|
}
|
|
22074
23297
|
if (modeRaw && !AUTOPILOT_MODES.includes(modeRaw)) {
|
|
22075
23298
|
console.error(`Autopilot mode must be one of: ${AUTOPILOT_MODES.join(", ")}`);
|
|
22076
23299
|
process.exit(1);
|
|
22077
23300
|
}
|
|
23301
|
+
if (installRequested && !verify) {
|
|
23302
|
+
throw new Error("--install requires verification so autopilot can build outputs before installing. Remove --no-verify or omit --install.");
|
|
23303
|
+
}
|
|
23304
|
+
if (behavioralRequested && !installRequested) {
|
|
23305
|
+
throw new Error("--behavioral requires --install so the selected host CLI can see the installed plugin bundle.");
|
|
23306
|
+
}
|
|
22078
23307
|
let tempDir;
|
|
22079
23308
|
try {
|
|
22080
23309
|
if (!runtime.jsonOutput && !runtime.quiet && interactive) {
|
|
@@ -22242,6 +23471,7 @@ ${formatAuthRequiredMessage("autopilot", retryError, source)}`);
|
|
|
22242
23471
|
const authorName = initOptions.author ?? (interactive ? await clackText("Author name", defaultAuthorName) : defaultAuthorName);
|
|
22243
23472
|
const targetsRaw = initOptions.targets ?? (interactive ? await clackText("Platforms (comma-separated)", DEFAULT_INIT_TARGETS.join(",")) : DEFAULT_INIT_TARGETS.join(","));
|
|
22244
23473
|
const targets = parseTargetPlatforms(targetsRaw);
|
|
23474
|
+
const installTargets = installRequested ? parseInstallTargetFlag(args, targets) : [];
|
|
22245
23475
|
const grouping = initOptions.grouping ? parseChoiceOption(initOptions.grouping, MCP_SKILL_GROUPINGS, "Skill grouping") : interactive ? await clackSelect("Skill grouping", [
|
|
22246
23476
|
{ value: "workflow", label: "workflow", hint: "Group related tools into workflow skills" },
|
|
22247
23477
|
{ value: "tool", label: "tool", hint: "One skill per tool" }
|
|
@@ -22271,7 +23501,9 @@ ${formatAuthRequiredMessage("autopilot", retryError, source)}`);
|
|
|
22271
23501
|
taxonomy: passDecisions.taxonomy,
|
|
22272
23502
|
instructions: passDecisions.instructions,
|
|
22273
23503
|
review: passDecisions.review,
|
|
22274
|
-
verify
|
|
23504
|
+
verify,
|
|
23505
|
+
install: installRequested,
|
|
23506
|
+
behavioral: behavioralRequested
|
|
22275
23507
|
});
|
|
22276
23508
|
const workspaceRoot = runtime.dryRun ? await mkdtemp3(`${tmpdir5()}/pluxx-autopilot-`) : process.cwd();
|
|
22277
23509
|
tempDir = runtime.dryRun ? workspaceRoot : void 0;
|
|
@@ -22357,6 +23589,12 @@ ${formatAuthRequiredMessage("autopilot", retryError, source)}`);
|
|
|
22357
23589
|
quality,
|
|
22358
23590
|
review: passDecisions.review.enabled,
|
|
22359
23591
|
verify,
|
|
23592
|
+
install: installRequested ? {
|
|
23593
|
+
enabled: true,
|
|
23594
|
+
platforms: installTargets,
|
|
23595
|
+
notes: getInstallFollowupNotes(installTargets),
|
|
23596
|
+
installTargets: []
|
|
23597
|
+
} : void 0,
|
|
22360
23598
|
steps: totalSteps,
|
|
22361
23599
|
init: {
|
|
22362
23600
|
createdFiles: initCreatedFiles,
|
|
@@ -22404,7 +23642,9 @@ ${formatAuthRequiredMessage("autopilot", retryError, source)}`);
|
|
|
22404
23642
|
taxonomy: passDecisions.taxonomy,
|
|
22405
23643
|
instructions: passDecisions.instructions,
|
|
22406
23644
|
review: passDecisions.review,
|
|
22407
|
-
verify
|
|
23645
|
+
verify,
|
|
23646
|
+
install: installRequested,
|
|
23647
|
+
behavioral: behavioralRequested
|
|
22408
23648
|
})}`);
|
|
22409
23649
|
console.log(` Quality: ${quality.warnings} warning(s), ${quality.infos} info message(s)`);
|
|
22410
23650
|
console.log(` Scaffold create/update: ${[...initCreatedFiles, ...initUpdatedFiles].join(", ") || "none"}`);
|
|
@@ -22425,6 +23665,9 @@ ${formatAuthRequiredMessage("autopilot", retryError, source)}`);
|
|
|
22425
23665
|
} else {
|
|
22426
23666
|
console.log(" Verification: skipped (--no-verify)");
|
|
22427
23667
|
}
|
|
23668
|
+
if (installRequested) {
|
|
23669
|
+
console.log(` Install: ${installTargets.join(", ")} after verification`);
|
|
23670
|
+
}
|
|
22428
23671
|
}
|
|
22429
23672
|
return;
|
|
22430
23673
|
}
|
|
@@ -22504,9 +23747,13 @@ ${formatAuthRequiredMessage("autopilot", retryError, source)}`);
|
|
|
22504
23747
|
});
|
|
22505
23748
|
return result;
|
|
22506
23749
|
})() : void 0;
|
|
22507
|
-
const
|
|
22508
|
-
const
|
|
22509
|
-
const
|
|
23750
|
+
const preInstallOk = (taxonomyResult?.ok ?? true) && (instructionsResult?.ok ?? true) && (reviewResult?.ok ?? true) && (verification?.ok ?? true);
|
|
23751
|
+
const installedConfig = installRequested && preInstallOk ? await loadConfig() : void 0;
|
|
23752
|
+
const install = installedConfig ? await maybeInstallBuiltOutputs(installedConfig, installTargets, { verifyConsumers: true }) : void 0;
|
|
23753
|
+
const behavioralResult = install && install.verification?.ok && behavioralRequested ? await runBehavioralSuite(process.cwd(), installedConfig, installTargets, { promptOverride: behavioralPrompt }) : void 0;
|
|
23754
|
+
const ok = preInstallOk && (install?.verification?.ok ?? true) && (behavioralResult?.ok ?? true);
|
|
23755
|
+
const failureStage = taxonomyResult && taxonomyResult.runnerExitCode !== 0 ? "runner" : instructionsResult && instructionsResult.runnerExitCode !== 0 ? "runner" : reviewResult && reviewResult.runnerExitCode !== 0 ? "runner" : verification && !verification.ok ? "verification" : install?.verification && !install.verification.ok ? "verification" : behavioralResult && !behavioralResult.ok ? "verification" : void 0;
|
|
23756
|
+
const failureMessage = failureStage === "runner" ? "A headless runner command failed. Re-run with --verbose-runner to stream full runner output." : failureStage === "verification" ? "Verification, install, or behavioral smoke failed after scaffold/refinement. Run `pluxx test --install` for details." : void 0;
|
|
22510
23757
|
const summary = {
|
|
22511
23758
|
ok,
|
|
22512
23759
|
pluginName,
|
|
@@ -22568,6 +23815,8 @@ ${formatAuthRequiredMessage("autopilot", retryError, source)}`);
|
|
|
22568
23815
|
},
|
|
22569
23816
|
verification,
|
|
22570
23817
|
verificationDurationMs,
|
|
23818
|
+
install,
|
|
23819
|
+
behavioral: behavioralResult,
|
|
22571
23820
|
failureStage,
|
|
22572
23821
|
failureMessage
|
|
22573
23822
|
};
|
|
@@ -22583,7 +23832,9 @@ ${formatAuthRequiredMessage("autopilot", retryError, source)}`);
|
|
|
22583
23832
|
taxonomy: passDecisions.taxonomy,
|
|
22584
23833
|
instructions: passDecisions.instructions,
|
|
22585
23834
|
review: passDecisions.review,
|
|
22586
|
-
verify
|
|
23835
|
+
verify,
|
|
23836
|
+
install: installRequested,
|
|
23837
|
+
behavioral: behavioralRequested
|
|
22587
23838
|
})}`);
|
|
22588
23839
|
console.log(` Quality: ${quality.warnings} warning(s), ${quality.infos} info message(s)`);
|
|
22589
23840
|
if (!verboseRunner) {
|
|
@@ -22606,6 +23857,23 @@ ${formatAuthRequiredMessage("autopilot", retryError, source)}`);
|
|
|
22606
23857
|
} else {
|
|
22607
23858
|
console.log(" Verification: skipped (--no-verify)");
|
|
22608
23859
|
}
|
|
23860
|
+
if (install) {
|
|
23861
|
+
console.log(` Install: ${install.verification?.ok === false ? "failed" : "passed"} for ${install.platforms.join(", ")}`);
|
|
23862
|
+
for (const target of install.installTargets) {
|
|
23863
|
+
console.log(` ${target.platform}: ${target.consumerPath}`);
|
|
23864
|
+
}
|
|
23865
|
+
if (install.verification) {
|
|
23866
|
+
console.log(` Verify-install: ${install.verification.ok ? "passed" : "failed"}`);
|
|
23867
|
+
}
|
|
23868
|
+
for (const note of install.notes) {
|
|
23869
|
+
console.log(` ${note}`);
|
|
23870
|
+
}
|
|
23871
|
+
} else if (installRequested) {
|
|
23872
|
+
console.log(` Install: skipped because verification did not produce a passing build`);
|
|
23873
|
+
}
|
|
23874
|
+
if (behavioralResult) {
|
|
23875
|
+
console.log(` Behavioral: ${behavioralResult.ok ? "passed" : "failed"}`);
|
|
23876
|
+
}
|
|
22609
23877
|
if (failureStage && failureMessage) {
|
|
22610
23878
|
console.log(` Failure stage: ${failureStage}`);
|
|
22611
23879
|
console.log(` Failure detail: ${failureMessage}`);
|
|
@@ -22614,6 +23882,9 @@ ${formatAuthRequiredMessage("autopilot", retryError, source)}`);
|
|
|
22614
23882
|
console.log(" 1. Review INSTRUCTIONS.md and skills/");
|
|
22615
23883
|
console.log(` 2. Run: pluxx build${mode === "quick" && !passDecisions.taxonomy.enabled && !passDecisions.instructions.enabled && !passDecisions.review.enabled ? " (agent refinement was skipped; only do this if the deterministic scaffold already looks good)" : ""}`);
|
|
22616
23884
|
console.log(` 3. Run: pluxx install${scaffoldPlan.generatedHookMode === "safe" ? " --trust" : ""} --target ${targets[0]}`);
|
|
23885
|
+
if (!installRequested) {
|
|
23886
|
+
console.log(` Or rerun onboarding: pluxx autopilot --from-mcp "${rawSource}" --runner ${runner} --mode ${mode} --install --install-target ${targets[0]}${scaffoldPlan.generatedHookMode === "safe" ? " --trust" : ""}`);
|
|
23887
|
+
}
|
|
22617
23888
|
}
|
|
22618
23889
|
if (!ok) {
|
|
22619
23890
|
process.exit(1);
|
|
@@ -22828,7 +24099,7 @@ async function runVerifyInstall() {
|
|
|
22828
24099
|
const targets = parseTargetFlagValues(args);
|
|
22829
24100
|
const config = await loadConfig();
|
|
22830
24101
|
if (runtime.dryRun) {
|
|
22831
|
-
const distDir =
|
|
24102
|
+
const distDir = resolve24(process.cwd(), config.outDir);
|
|
22832
24103
|
const plan = planInstallPlugin(distDir, config.name, targets ?? config.targets);
|
|
22833
24104
|
const summary = {
|
|
22834
24105
|
dryRun: true,
|
|
@@ -22915,9 +24186,10 @@ Usage:
|
|
|
22915
24186
|
pluxx agent prepare Generate agent context + boundary files for host agents
|
|
22916
24187
|
pluxx agent prompt <kind> Generate a prompt pack (taxonomy, instructions, review)
|
|
22917
24188
|
pluxx agent run <kind> --runner <id> Execute a prompt pack via Claude, Cursor, Codex, or OpenCode headlessly
|
|
24189
|
+
pluxx discover-mcp [--host <hosts...>] List installed MCP servers from local host configs
|
|
22918
24190
|
pluxx mcp proxy ... Run a local MCP proxy with optional record/replay tapes
|
|
22919
24191
|
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
|
|
24192
|
+
pluxx init [name] [--from-mcp <source>|--from-installed-mcp <selector>] Create a new pluxx.config.ts
|
|
22921
24193
|
pluxx sync [--from-mcp <source>] Refresh MCP-derived scaffold files
|
|
22922
24194
|
pluxx migrate <path> Import an existing plugin into pluxx
|
|
22923
24195
|
pluxx test [--target <platforms...>] [--install] [--behavioral] Run config, lint, eval, build, and smoke checks
|
|
@@ -22937,6 +24209,10 @@ Common flags:
|
|
|
22937
24209
|
--mode quick|standard|thorough Control how much agent refinement autopilot performs
|
|
22938
24210
|
--approve-mcp-tools Preapprove all tools from the imported MCP in canonical permissions
|
|
22939
24211
|
--ingest-provider auto|local|firecrawl Choose the docs/website ingestion backend for agent prepare/autopilot
|
|
24212
|
+
--install Install autopilot's verified build into one selected host
|
|
24213
|
+
--install-target <platform> Host to install after autopilot verification; defaults to the first target
|
|
24214
|
+
--trust Trust local hook commands during install/test flows
|
|
24215
|
+
--behavioral Run installed headless example-query smoke checks
|
|
22940
24216
|
|
|
22941
24217
|
Targets:
|
|
22942
24218
|
claude-code, cursor, codex, opencode, github-copilot, openhands,
|
|
@@ -22952,6 +24228,9 @@ Examples:
|
|
|
22952
24228
|
pluxx init my-plugin Scaffold a new plugin config
|
|
22953
24229
|
pluxx init --from-mcp https://example.com/mcp Scaffold from a remote MCP server
|
|
22954
24230
|
pluxx init --from-mcp "npx -y -p @acme/mcp acme-mcp" Scaffold from a local MCP command
|
|
24231
|
+
pluxx discover-mcp List installed MCP servers in Claude, Cursor, Codex, and OpenCode config
|
|
24232
|
+
pluxx discover-mcp --host codex opencode --json
|
|
24233
|
+
pluxx init --from-installed-mcp codex:acme --yes Scaffold from an already installed MCP config
|
|
22955
24234
|
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
24235
|
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
24236
|
pluxx init --from-mcp https://example.com/mcp --yes --auth-type platform --runtime-auth platform
|
|
@@ -22976,6 +24255,8 @@ Examples:
|
|
|
22976
24255
|
--attach is only supported for the opencode runner
|
|
22977
24256
|
pluxx autopilot --from-mcp https://example.com/mcp --runner codex --mode quick --yes
|
|
22978
24257
|
pluxx autopilot --from-mcp https://example.com/mcp --runner codex --mode standard --yes --name acme --display-name "Acme"
|
|
24258
|
+
pluxx autopilot --from-mcp https://example.com/mcp --runner codex --mode standard --install --install-target codex --trust
|
|
24259
|
+
pluxx autopilot --from-mcp https://example.com/mcp --runner codex --mode standard --auth-env API_KEY --auth-type bearer --install --install-target codex --trust
|
|
22979
24260
|
pluxx autopilot --from-mcp https://example.com/mcp --runner codex --yes --approve-mcp-tools
|
|
22980
24261
|
pluxx autopilot --from-mcp https://example.com/mcp --runner codex --mode thorough --yes --verbose-runner
|
|
22981
24262
|
pluxx autopilot --from-mcp https://mcp.linear.app/mcp --runner codex --yes --oauth-wrapper
|
|
@@ -22992,6 +24273,39 @@ Examples:
|
|
|
22992
24273
|
pluxx verify-install --target codex Verify the installed Codex bundle in its native local path
|
|
22993
24274
|
pluxx install --dry-run Preview local install paths and trust implications
|
|
22994
24275
|
pluxx install --trust Install without hook trust confirmation
|
|
24276
|
+
pluxx publish --dry-run Preview npm/GitHub release publish checks
|
|
24277
|
+
pluxx publish --github-release --version 1.0.0 Create release installers and a GitHub release
|
|
24278
|
+
pluxx publish --npm --tag next Publish the npm package under a non-latest dist-tag
|
|
24279
|
+
`);
|
|
24280
|
+
}
|
|
24281
|
+
function printPublishHelp() {
|
|
24282
|
+
console.log(`
|
|
24283
|
+
pluxx publish \u2014 package and release a built plugin or CLI
|
|
24284
|
+
|
|
24285
|
+
Usage:
|
|
24286
|
+
pluxx publish [--npm] [--github-release] [--allow-dirty] [--dry-run] [--json] [--tag latest] [--version x.y.z]
|
|
24287
|
+
|
|
24288
|
+
Behavior:
|
|
24289
|
+
- loads the current pluxx.config.* project
|
|
24290
|
+
- plans npm publish and/or GitHub release work
|
|
24291
|
+
- runs clean-working-tree checks unless --allow-dirty is passed
|
|
24292
|
+
- writes release assets such as install scripts and release-manifest.json when applicable
|
|
24293
|
+
|
|
24294
|
+
Flags:
|
|
24295
|
+
--npm Publish to npm using the configured package metadata
|
|
24296
|
+
--github-release Create or update GitHub release assets for the requested version
|
|
24297
|
+
--version x.y.z Override the release version for the publish plan
|
|
24298
|
+
--tag <name> Set the npm dist-tag (default: latest)
|
|
24299
|
+
--allow-dirty Skip the clean-working-tree check
|
|
24300
|
+
--dry-run Show the publish plan without writing or uploading
|
|
24301
|
+
--json Print the publish plan or result as JSON
|
|
24302
|
+
--help, -h Show this command help
|
|
24303
|
+
|
|
24304
|
+
Examples:
|
|
24305
|
+
pluxx publish --dry-run
|
|
24306
|
+
pluxx publish --github-release --version 1.0.0
|
|
24307
|
+
pluxx publish --npm --version 0.1.7
|
|
24308
|
+
pluxx publish --npm --github-release --version 1.0.0
|
|
22995
24309
|
`);
|
|
22996
24310
|
}
|
|
22997
24311
|
if (import.meta.main) {
|