@orchid-labs/pluxx 0.1.3 → 0.1.5
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 +64 -3
- package/dist/cli/behavioral.d.ts +27 -0
- package/dist/cli/behavioral.d.ts.map +1 -0
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +1486 -396
- package/dist/cli/init-from-mcp.d.ts.map +1 -1
- package/dist/cli/install.d.ts.map +1 -1
- package/dist/cli/lint.d.ts.map +1 -1
- package/dist/cli/migrate.d.ts.map +1 -1
- package/dist/cli/primitive-summary.d.ts.map +1 -1
- package/dist/cli/publish.d.ts +1 -0
- package/dist/cli/publish.d.ts.map +1 -1
- package/dist/commands.d.ts +3 -0
- package/dist/commands.d.ts.map +1 -1
- package/dist/generators/claude-code/index.d.ts +2 -0
- package/dist/generators/claude-code/index.d.ts.map +1 -1
- package/dist/generators/codex/index.d.ts +1 -0
- package/dist/generators/codex/index.d.ts.map +1 -1
- package/dist/generators/cursor/index.d.ts.map +1 -1
- package/dist/generators/index.d.ts.map +1 -1
- package/dist/generators/opencode/index.d.ts +1 -0
- package/dist/generators/opencode/index.d.ts.map +1 -1
- package/dist/generators/shared/claude-family.d.ts +1 -0
- package/dist/generators/shared/claude-family.d.ts.map +1 -1
- package/dist/index.js +200 -80
- package/dist/permissions.d.ts +1 -1
- package/dist/permissions.d.ts.map +1 -1
- package/dist/schema.d.ts +707 -707
- package/dist/validation/platform-rules.d.ts.map +1 -1
- package/package.json +4 -3
package/dist/cli/index.js
CHANGED
|
@@ -4176,27 +4176,67 @@ function mergeAction(current, next) {
|
|
|
4176
4176
|
return ACTION_PRIORITY[next] > ACTION_PRIORITY[current] ? next : current;
|
|
4177
4177
|
}
|
|
4178
4178
|
function permissionRulesNeedToolLevelDowngrade(permissions) {
|
|
4179
|
-
return collectPermissionRules(permissions).some((rule) => rule.
|
|
4179
|
+
return collectPermissionRules(permissions).some((rule) => rule.kind === "MCP");
|
|
4180
4180
|
}
|
|
4181
4181
|
function buildOpenCodePermissionMap(permissions) {
|
|
4182
4182
|
const rules = collectPermissionRules(permissions);
|
|
4183
4183
|
const output = {};
|
|
4184
|
-
const toolAliases = {
|
|
4185
|
-
Bash: ["bash", "shell"],
|
|
4186
|
-
Edit: ["edit", "write"],
|
|
4187
|
-
Read: ["read"],
|
|
4188
|
-
MCP: ["mcp"],
|
|
4189
|
-
// OpenCode's native permission surface is tool-level and does not expose
|
|
4190
|
-
// a dedicated skill permission key.
|
|
4191
|
-
Skill: []
|
|
4192
|
-
};
|
|
4193
4184
|
for (const rule of rules) {
|
|
4194
|
-
|
|
4195
|
-
|
|
4185
|
+
if (rule.kind === "MCP") {
|
|
4186
|
+
const toolName = translateCanonicalMcpPermission(rule.pattern);
|
|
4187
|
+
if (!toolName) continue;
|
|
4188
|
+
output[toolName] = mergeScalarPermission(output[toolName], rule.action);
|
|
4189
|
+
continue;
|
|
4196
4190
|
}
|
|
4191
|
+
const tool = toOpenCodePermissionTool(rule.kind);
|
|
4192
|
+
if (!tool) continue;
|
|
4193
|
+
output[tool] = mergePatternPermission(output[tool], rule.pattern, rule.action);
|
|
4197
4194
|
}
|
|
4198
4195
|
return output;
|
|
4199
4196
|
}
|
|
4197
|
+
function toOpenCodePermissionTool(kind) {
|
|
4198
|
+
switch (kind) {
|
|
4199
|
+
case "Bash":
|
|
4200
|
+
return "bash";
|
|
4201
|
+
case "Edit":
|
|
4202
|
+
return "edit";
|
|
4203
|
+
case "Read":
|
|
4204
|
+
return "read";
|
|
4205
|
+
case "Skill":
|
|
4206
|
+
return "skill";
|
|
4207
|
+
case "MCP":
|
|
4208
|
+
return null;
|
|
4209
|
+
}
|
|
4210
|
+
}
|
|
4211
|
+
function mergeScalarPermission(current, next) {
|
|
4212
|
+
if (!current) return next;
|
|
4213
|
+
if (typeof current === "string") {
|
|
4214
|
+
return mergeAction(current, next);
|
|
4215
|
+
}
|
|
4216
|
+
const merged = { ...current };
|
|
4217
|
+
merged["*"] = mergeAction(merged["*"], next);
|
|
4218
|
+
return merged;
|
|
4219
|
+
}
|
|
4220
|
+
function mergePatternPermission(current, pattern, next) {
|
|
4221
|
+
if (pattern === "*") {
|
|
4222
|
+
return mergeScalarPermission(current, next);
|
|
4223
|
+
}
|
|
4224
|
+
const merged = typeof current === "string" ? { "*": current } : { ...current ?? {} };
|
|
4225
|
+
merged[pattern] = mergeAction(merged[pattern], next);
|
|
4226
|
+
return merged;
|
|
4227
|
+
}
|
|
4228
|
+
function translateCanonicalMcpPermission(pattern) {
|
|
4229
|
+
const trimmed = pattern.trim();
|
|
4230
|
+
if (!trimmed || trimmed === "*") return null;
|
|
4231
|
+
const dot = trimmed.indexOf(".");
|
|
4232
|
+
if (dot === -1) {
|
|
4233
|
+
return `${trimmed}_*`;
|
|
4234
|
+
}
|
|
4235
|
+
const server = trimmed.slice(0, dot).trim();
|
|
4236
|
+
const tool = trimmed.slice(dot + 1).trim();
|
|
4237
|
+
if (!server || !tool) return null;
|
|
4238
|
+
return `${server}_${tool.replace(/\./g, "_")}`;
|
|
4239
|
+
}
|
|
4200
4240
|
function buildGeneratedPermissionHookScript(permissions) {
|
|
4201
4241
|
const rules = collectPermissionRules(permissions);
|
|
4202
4242
|
if (rules.length === 0) return null;
|
|
@@ -4334,6 +4374,7 @@ function claudeResponse(match) {
|
|
|
4334
4374
|
if (!match) return {};
|
|
4335
4375
|
return {
|
|
4336
4376
|
hookSpecificOutput: {
|
|
4377
|
+
hookEventName: "PreToolUse",
|
|
4337
4378
|
permissionDecision: match.action,
|
|
4338
4379
|
permissionDecisionReason: "Pluxx permissions matched " + match.rule.raw,
|
|
4339
4380
|
},
|
|
@@ -4726,7 +4767,7 @@ async function loadConfig(dir = process.cwd()) {
|
|
|
4726
4767
|
}
|
|
4727
4768
|
|
|
4728
4769
|
// src/generators/index.ts
|
|
4729
|
-
import { rmSync, mkdirSync as
|
|
4770
|
+
import { rmSync, mkdirSync as mkdirSync3 } from "fs";
|
|
4730
4771
|
import { resolve as resolve8, relative as relative5 } from "path";
|
|
4731
4772
|
|
|
4732
4773
|
// src/generators/base.ts
|
|
@@ -4866,9 +4907,9 @@ var Generator = class {
|
|
|
4866
4907
|
for (const configPath of this.config.passthrough ?? []) {
|
|
4867
4908
|
const src = this.resolveConfigPath(configPath, "passthrough");
|
|
4868
4909
|
if (!existsSync4(src)) continue;
|
|
4869
|
-
const
|
|
4870
|
-
if (!
|
|
4871
|
-
this.copyDir(configPath, `${
|
|
4910
|
+
const basename9 = src.split("/").filter(Boolean).pop();
|
|
4911
|
+
if (!basename9) continue;
|
|
4912
|
+
this.copyDir(configPath, `${basename9}/`, "passthrough");
|
|
4872
4913
|
}
|
|
4873
4914
|
}
|
|
4874
4915
|
/** Build canonical MCP server configs for target-specific output shaping. */
|
|
@@ -4945,8 +4986,8 @@ var Generator = class {
|
|
|
4945
4986
|
};
|
|
4946
4987
|
|
|
4947
4988
|
// src/generators/shared/claude-family.ts
|
|
4948
|
-
import { existsSync as
|
|
4949
|
-
import { resolve as
|
|
4989
|
+
import { existsSync as existsSync6 } from "fs";
|
|
4990
|
+
import { resolve as resolve5 } from "path";
|
|
4950
4991
|
|
|
4951
4992
|
// src/generators/hooks-warning.ts
|
|
4952
4993
|
var MATCHER_PASSTHROUGH_PLATFORMS = /* @__PURE__ */ new Set([
|
|
@@ -5019,6 +5060,118 @@ function mapHookEventToPascalCase(event) {
|
|
|
5019
5060
|
return PASCAL_CASE_HOOK_ALIASES[event] ?? event.charAt(0).toUpperCase() + event.slice(1);
|
|
5020
5061
|
}
|
|
5021
5062
|
|
|
5063
|
+
// src/agents.ts
|
|
5064
|
+
import { existsSync as existsSync5, readdirSync, readFileSync as readFileSync2, statSync } from "fs";
|
|
5065
|
+
import { basename, resolve as resolve4 } from "path";
|
|
5066
|
+
function firstHeading(content) {
|
|
5067
|
+
const lines = content.split(/\r?\n/);
|
|
5068
|
+
for (const line of lines) {
|
|
5069
|
+
const match = line.match(/^#\s+(.*)$/);
|
|
5070
|
+
if (match?.[1]?.trim()) return match[1].trim();
|
|
5071
|
+
}
|
|
5072
|
+
return void 0;
|
|
5073
|
+
}
|
|
5074
|
+
function splitMarkdownFrontmatter(content) {
|
|
5075
|
+
const lines = content.split(/\r?\n/);
|
|
5076
|
+
if (lines[0]?.trim() !== "---") {
|
|
5077
|
+
return {
|
|
5078
|
+
frontmatterLines: [],
|
|
5079
|
+
body: content
|
|
5080
|
+
};
|
|
5081
|
+
}
|
|
5082
|
+
let endIndex = -1;
|
|
5083
|
+
for (let i = 1; i < lines.length; i += 1) {
|
|
5084
|
+
if (lines[i].trim() === "---") {
|
|
5085
|
+
endIndex = i;
|
|
5086
|
+
break;
|
|
5087
|
+
}
|
|
5088
|
+
}
|
|
5089
|
+
if (endIndex === -1) {
|
|
5090
|
+
return {
|
|
5091
|
+
frontmatterLines: [],
|
|
5092
|
+
body: content
|
|
5093
|
+
};
|
|
5094
|
+
}
|
|
5095
|
+
return {
|
|
5096
|
+
frontmatterLines: lines.slice(1, endIndex),
|
|
5097
|
+
body: lines.slice(endIndex + 1).join("\n")
|
|
5098
|
+
};
|
|
5099
|
+
}
|
|
5100
|
+
function parseScalarValue(raw) {
|
|
5101
|
+
const trimmed = raw.trim();
|
|
5102
|
+
if (trimmed === "true") return true;
|
|
5103
|
+
if (trimmed === "false") return false;
|
|
5104
|
+
if (/^-?\d+(?:\.\d+)?$/.test(trimmed)) return Number(trimmed);
|
|
5105
|
+
if (trimmed.length >= 2) {
|
|
5106
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
5107
|
+
return trimmed.slice(1, -1);
|
|
5108
|
+
}
|
|
5109
|
+
}
|
|
5110
|
+
return trimmed;
|
|
5111
|
+
}
|
|
5112
|
+
function parseAgentFrontmatter(frontmatterLines) {
|
|
5113
|
+
const root = {};
|
|
5114
|
+
const stack = [
|
|
5115
|
+
{ indent: -1, target: root }
|
|
5116
|
+
];
|
|
5117
|
+
for (const line of frontmatterLines) {
|
|
5118
|
+
if (!line.trim() || line.trim().startsWith("#")) continue;
|
|
5119
|
+
const match = line.match(/^(\s*)(?:"([^"]+)"|'([^']+)'|([A-Za-z0-9_.-]+))\s*:\s*(.*)$/);
|
|
5120
|
+
if (!match) continue;
|
|
5121
|
+
const indent = match[1].length;
|
|
5122
|
+
const key = match[2] ?? match[3] ?? match[4];
|
|
5123
|
+
const rawValue = match[5].trim();
|
|
5124
|
+
while (stack.length > 1 && indent <= stack[stack.length - 1].indent) {
|
|
5125
|
+
stack.pop();
|
|
5126
|
+
}
|
|
5127
|
+
const parent = stack[stack.length - 1].target;
|
|
5128
|
+
if (!rawValue) {
|
|
5129
|
+
const nested = {};
|
|
5130
|
+
parent[key] = nested;
|
|
5131
|
+
stack.push({ indent, target: nested });
|
|
5132
|
+
continue;
|
|
5133
|
+
}
|
|
5134
|
+
parent[key] = parseScalarValue(rawValue);
|
|
5135
|
+
}
|
|
5136
|
+
return root;
|
|
5137
|
+
}
|
|
5138
|
+
function walkMarkdownFiles(dir) {
|
|
5139
|
+
const entries = readdirSync(dir);
|
|
5140
|
+
const files = [];
|
|
5141
|
+
for (const entry of entries) {
|
|
5142
|
+
const fullPath = resolve4(dir, entry);
|
|
5143
|
+
const stat = statSync(fullPath);
|
|
5144
|
+
if (stat.isDirectory()) {
|
|
5145
|
+
files.push(...walkMarkdownFiles(fullPath));
|
|
5146
|
+
continue;
|
|
5147
|
+
}
|
|
5148
|
+
if (stat.isFile() && entry.endsWith(".md")) {
|
|
5149
|
+
files.push(fullPath);
|
|
5150
|
+
}
|
|
5151
|
+
}
|
|
5152
|
+
return files;
|
|
5153
|
+
}
|
|
5154
|
+
function parseCanonicalAgentFile(agentPath) {
|
|
5155
|
+
const content = readFileSync2(agentPath, "utf-8");
|
|
5156
|
+
const { frontmatterLines, body } = splitMarkdownFrontmatter(content);
|
|
5157
|
+
const frontmatter = parseAgentFrontmatter(frontmatterLines);
|
|
5158
|
+
const fileStem = basename(agentPath, ".md");
|
|
5159
|
+
const name = typeof frontmatter.name === "string" && frontmatter.name ? frontmatter.name : fileStem;
|
|
5160
|
+
const description = typeof frontmatter.description === "string" && frontmatter.description ? frontmatter.description : firstHeading(body);
|
|
5161
|
+
return {
|
|
5162
|
+
filePath: agentPath,
|
|
5163
|
+
fileStem,
|
|
5164
|
+
name,
|
|
5165
|
+
description,
|
|
5166
|
+
body: body.trim(),
|
|
5167
|
+
frontmatter
|
|
5168
|
+
};
|
|
5169
|
+
}
|
|
5170
|
+
function readCanonicalAgentFiles(agentsDir) {
|
|
5171
|
+
if (!agentsDir || !existsSync5(agentsDir)) return [];
|
|
5172
|
+
return walkMarkdownFiles(agentsDir).sort((a, b) => a.localeCompare(b)).map(parseCanonicalAgentFile);
|
|
5173
|
+
}
|
|
5174
|
+
|
|
5022
5175
|
// src/generators/shared/claude-family.ts
|
|
5023
5176
|
async function generateClaudeFamilyOutputs(args2) {
|
|
5024
5177
|
const {
|
|
@@ -5030,13 +5183,13 @@ async function generateClaudeFamilyOutputs(args2) {
|
|
|
5030
5183
|
writeFile: writeFile3
|
|
5031
5184
|
} = args2;
|
|
5032
5185
|
await Promise.all([
|
|
5033
|
-
writeManifest(config, options, writeJson),
|
|
5186
|
+
writeManifest(config, rootDir, options, writeJson),
|
|
5034
5187
|
writeMcpConfig(config, platform, writeJson),
|
|
5035
5188
|
writeHooks(config, platform, options, writeJson, writeFile3),
|
|
5036
5189
|
writeInstructions(config, rootDir, options, writeFile3)
|
|
5037
5190
|
]);
|
|
5038
5191
|
}
|
|
5039
|
-
async function writeManifest(config, options, writeJson) {
|
|
5192
|
+
async function writeManifest(config, rootDir, options, writeJson) {
|
|
5040
5193
|
const manifest = {
|
|
5041
5194
|
name: config.name,
|
|
5042
5195
|
version: config.version,
|
|
@@ -5053,8 +5206,15 @@ async function writeManifest(config, options, writeJson) {
|
|
|
5053
5206
|
if (config.commands) {
|
|
5054
5207
|
manifest.commands = "./commands/";
|
|
5055
5208
|
}
|
|
5056
|
-
|
|
5209
|
+
const agentsManifestMode = options.agentsManifestMode ?? "directory";
|
|
5210
|
+
if (config.agents && agentsManifestMode === "directory") {
|
|
5057
5211
|
manifest.agents = "./agents/";
|
|
5212
|
+
} else if (config.agents && agentsManifestMode === "files") {
|
|
5213
|
+
const agentsDir = resolve5(rootDir, config.agents);
|
|
5214
|
+
const agents = readCanonicalAgentFiles(agentsDir);
|
|
5215
|
+
if (agents.length > 0) {
|
|
5216
|
+
manifest.agents = agents.map((agent) => `./agents/${agent.fileStem}.md`);
|
|
5217
|
+
}
|
|
5058
5218
|
}
|
|
5059
5219
|
manifest.skills = "./skills/";
|
|
5060
5220
|
if ((config.hooks || config.permissions) && options.includeStandardHooksManifest !== false) {
|
|
@@ -5149,8 +5309,8 @@ async function writeHooks(config, platform, options, writeJson, writeFile3) {
|
|
|
5149
5309
|
}
|
|
5150
5310
|
async function writeInstructions(config, rootDir, options, writeFile3) {
|
|
5151
5311
|
if (!config.instructions) return;
|
|
5152
|
-
const srcPath =
|
|
5153
|
-
if (!
|
|
5312
|
+
const srcPath = resolve5(rootDir, config.instructions);
|
|
5313
|
+
if (!existsSync6(srcPath)) return;
|
|
5154
5314
|
const content = await readTextFile(srcPath);
|
|
5155
5315
|
const titleSuffix = options.titleSuffix ?? "Plugin";
|
|
5156
5316
|
const instructions = [
|
|
@@ -5167,8 +5327,49 @@ function defaultMapEventName(event) {
|
|
|
5167
5327
|
}
|
|
5168
5328
|
|
|
5169
5329
|
// src/generators/claude-code/index.ts
|
|
5170
|
-
import { existsSync as
|
|
5171
|
-
import { basename, join as join2 } from "path";
|
|
5330
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync3, readdirSync as readdirSync2, writeFileSync } from "fs";
|
|
5331
|
+
import { basename as basename2, join as join2 } from "path";
|
|
5332
|
+
|
|
5333
|
+
// src/delegation.ts
|
|
5334
|
+
function getPortableDelegationProfile(frontmatter) {
|
|
5335
|
+
const permission = asMap(frontmatter.permission);
|
|
5336
|
+
const bash = asMap(permission?.bash);
|
|
5337
|
+
const task = asMap(permission?.task);
|
|
5338
|
+
return {
|
|
5339
|
+
mode: typeof frontmatter.mode === "string" ? frontmatter.mode : void 0,
|
|
5340
|
+
hidden: frontmatter.hidden === true,
|
|
5341
|
+
editPolicy: typeof permission?.edit === "string" ? permission.edit : void 0,
|
|
5342
|
+
bashPolicy: typeof bash?.["*"] === "string" ? bash["*"] : void 0,
|
|
5343
|
+
taskPolicy: typeof task?.["*"] === "string" ? task["*"] : void 0
|
|
5344
|
+
};
|
|
5345
|
+
}
|
|
5346
|
+
function buildDelegationBehaviorNotes(frontmatter) {
|
|
5347
|
+
const profile = getPortableDelegationProfile(frontmatter);
|
|
5348
|
+
const notes = [];
|
|
5349
|
+
if (profile.mode === "subagent" || profile.hidden) {
|
|
5350
|
+
notes.push("This specialist is intended primarily for delegated use rather than as the default top-level worker.");
|
|
5351
|
+
}
|
|
5352
|
+
if (profile.editPolicy === "deny") {
|
|
5353
|
+
notes.push("Stay read-only unless the parent task explicitly asks for file edits.");
|
|
5354
|
+
}
|
|
5355
|
+
if (profile.bashPolicy === "deny") {
|
|
5356
|
+
notes.push("Avoid shell commands unless the parent task explicitly requires them.");
|
|
5357
|
+
} else if (profile.bashPolicy === "ask") {
|
|
5358
|
+
notes.push("Use shell commands sparingly and only when they are clearly necessary to complete the task.");
|
|
5359
|
+
}
|
|
5360
|
+
if (profile.taskPolicy === "deny") {
|
|
5361
|
+
notes.push("Do not delegate further subtasks unless the parent task explicitly asks for additional specialist work.");
|
|
5362
|
+
} else if (profile.taskPolicy === "ask") {
|
|
5363
|
+
notes.push("Only delegate further subtasks when the work clearly benefits from another specialist.");
|
|
5364
|
+
}
|
|
5365
|
+
return notes;
|
|
5366
|
+
}
|
|
5367
|
+
function asMap(value) {
|
|
5368
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
5369
|
+
return value;
|
|
5370
|
+
}
|
|
5371
|
+
|
|
5372
|
+
// src/generators/claude-code/index.ts
|
|
5172
5373
|
var ClaudeCodeGenerator = class extends Generator {
|
|
5173
5374
|
platform = "claude-code";
|
|
5174
5375
|
async generate() {
|
|
@@ -5180,7 +5381,8 @@ var ClaudeCodeGenerator = class extends Generator {
|
|
|
5180
5381
|
manifestPath: ".claude-plugin/plugin.json",
|
|
5181
5382
|
instructionsFile: "CLAUDE.md",
|
|
5182
5383
|
pluginRootVar: "CLAUDE_PLUGIN_ROOT",
|
|
5183
|
-
includeStandardHooksManifest: false
|
|
5384
|
+
includeStandardHooksManifest: false,
|
|
5385
|
+
agentsManifestMode: "files"
|
|
5184
5386
|
},
|
|
5185
5387
|
writeJson: (relativePath, data) => this.writeJson(relativePath, data),
|
|
5186
5388
|
writeFile: (relativePath, content) => this.writeFile(relativePath, content)
|
|
@@ -5195,29 +5397,108 @@ var ClaudeCodeGenerator = class extends Generator {
|
|
|
5195
5397
|
copySkills() {
|
|
5196
5398
|
super.copySkills();
|
|
5197
5399
|
const collidingSkills = this.collectCollidingSkills();
|
|
5400
|
+
const wrappedSkills = this.collectCommandWrappedSkills();
|
|
5198
5401
|
for (const skill of collidingSkills) {
|
|
5199
5402
|
const outputPath = join2(this.outDir, "skills", skill.dirName, "SKILL.md");
|
|
5200
|
-
if (!
|
|
5201
|
-
const current =
|
|
5403
|
+
if (!existsSync7(outputPath)) continue;
|
|
5404
|
+
const current = readFileSync3(outputPath, "utf-8");
|
|
5202
5405
|
const hiddenName = buildHiddenSkillName(skill.effectiveName);
|
|
5203
|
-
const rewritten =
|
|
5406
|
+
const rewritten = rewriteClaudeSkillVisibility(current, {
|
|
5407
|
+
nameOverride: hiddenName,
|
|
5408
|
+
userInvocable: false
|
|
5409
|
+
});
|
|
5410
|
+
if (rewritten !== current) {
|
|
5411
|
+
writeFileSync(outputPath, rewritten, "utf-8");
|
|
5412
|
+
}
|
|
5413
|
+
}
|
|
5414
|
+
for (const skill of wrappedSkills) {
|
|
5415
|
+
if (collidingSkills.some((entry) => entry.dirName === skill.dirName)) continue;
|
|
5416
|
+
const outputPath = join2(this.outDir, "skills", skill.dirName, "SKILL.md");
|
|
5417
|
+
if (!existsSync7(outputPath)) continue;
|
|
5418
|
+
const current = readFileSync3(outputPath, "utf-8");
|
|
5419
|
+
const rewritten = rewriteClaudeSkillVisibility(current, {
|
|
5420
|
+
userInvocable: false
|
|
5421
|
+
});
|
|
5204
5422
|
if (rewritten !== current) {
|
|
5205
5423
|
writeFileSync(outputPath, rewritten, "utf-8");
|
|
5206
5424
|
}
|
|
5207
5425
|
}
|
|
5208
5426
|
}
|
|
5427
|
+
copyAgents() {
|
|
5428
|
+
if (!this.config.agents) return;
|
|
5429
|
+
const agentsDir = this.resolveConfigPath(this.config.agents, "agents");
|
|
5430
|
+
const agents = readCanonicalAgentFiles(agentsDir);
|
|
5431
|
+
if (agents.length === 0) return;
|
|
5432
|
+
mkdirSync2(join2(this.outDir, "agents"), { recursive: true });
|
|
5433
|
+
for (const agent of agents) {
|
|
5434
|
+
const frontmatter = [
|
|
5435
|
+
"---",
|
|
5436
|
+
`name: ${JSON.stringify(agent.name)}`,
|
|
5437
|
+
`description: ${JSON.stringify(agent.description ?? `${agent.name} specialist.`)}`
|
|
5438
|
+
];
|
|
5439
|
+
if (typeof agent.frontmatter.model === "string" && agent.frontmatter.model) {
|
|
5440
|
+
frontmatter.push(`model: ${JSON.stringify(agent.frontmatter.model)}`);
|
|
5441
|
+
}
|
|
5442
|
+
const effort = typeof agent.frontmatter.model_reasoning_effort === "string" && agent.frontmatter.model_reasoning_effort ? agent.frontmatter.model_reasoning_effort : typeof agent.frontmatter.effort === "string" && agent.frontmatter.effort ? agent.frontmatter.effort : void 0;
|
|
5443
|
+
if (effort) {
|
|
5444
|
+
frontmatter.push(`effort: ${JSON.stringify(effort)}`);
|
|
5445
|
+
}
|
|
5446
|
+
const maxTurns = typeof agent.frontmatter.maxTurns === "number" ? agent.frontmatter.maxTurns : typeof agent.frontmatter.steps === "number" ? agent.frontmatter.steps : typeof agent.frontmatter.maxSteps === "number" ? agent.frontmatter.maxSteps : void 0;
|
|
5447
|
+
if (typeof maxTurns === "number") {
|
|
5448
|
+
frontmatter.push(`maxTurns: ${maxTurns}`);
|
|
5449
|
+
}
|
|
5450
|
+
const claudeTools = selectClaudeToolsField(agent.frontmatter);
|
|
5451
|
+
if (claudeTools) {
|
|
5452
|
+
frontmatter.push(`tools: ${claudeTools}`);
|
|
5453
|
+
}
|
|
5454
|
+
const disallowedTools = buildClaudeDisallowedTools(agent.frontmatter);
|
|
5455
|
+
if (disallowedTools.length > 0) {
|
|
5456
|
+
frontmatter.push(`disallowedTools: ${disallowedTools.join(", ")}`);
|
|
5457
|
+
}
|
|
5458
|
+
if (typeof agent.frontmatter.skills === "string" && agent.frontmatter.skills.trim()) {
|
|
5459
|
+
frontmatter.push(`skills: ${agent.frontmatter.skills}`);
|
|
5460
|
+
}
|
|
5461
|
+
if (typeof agent.frontmatter.memory === "string" && agent.frontmatter.memory.trim()) {
|
|
5462
|
+
frontmatter.push(`memory: ${JSON.stringify(agent.frontmatter.memory)}`);
|
|
5463
|
+
}
|
|
5464
|
+
if (typeof agent.frontmatter.background === "boolean") {
|
|
5465
|
+
frontmatter.push(`background: ${agent.frontmatter.background}`);
|
|
5466
|
+
}
|
|
5467
|
+
if (typeof agent.frontmatter.isolation === "string" && agent.frontmatter.isolation.trim()) {
|
|
5468
|
+
frontmatter.push(`isolation: ${JSON.stringify(agent.frontmatter.isolation)}`);
|
|
5469
|
+
}
|
|
5470
|
+
if (typeof agent.frontmatter.color === "string" && agent.frontmatter.color.trim()) {
|
|
5471
|
+
frontmatter.push(`color: ${JSON.stringify(agent.frontmatter.color)}`);
|
|
5472
|
+
}
|
|
5473
|
+
frontmatter.push("---");
|
|
5474
|
+
const delegationNotes = buildDelegationBehaviorNotes(agent.frontmatter);
|
|
5475
|
+
const bodyParts = [
|
|
5476
|
+
...delegationNotes.length > 0 ? [
|
|
5477
|
+
"Delegation contract:",
|
|
5478
|
+
...delegationNotes.map((note) => `- ${note}`),
|
|
5479
|
+
""
|
|
5480
|
+
] : [],
|
|
5481
|
+
agent.body
|
|
5482
|
+
].filter(Boolean);
|
|
5483
|
+
const outputPath = join2(this.outDir, "agents", `${agent.fileStem}.md`);
|
|
5484
|
+
writeFileSync(outputPath, `${frontmatter.join("\n")}
|
|
5485
|
+
|
|
5486
|
+
${bodyParts.join("\n").trim()}
|
|
5487
|
+
`, "utf-8");
|
|
5488
|
+
}
|
|
5489
|
+
}
|
|
5209
5490
|
collectCollidingSkills() {
|
|
5210
5491
|
if (!this.config.commands) return [];
|
|
5211
5492
|
const commandsSrc = this.resolveConfigPath(this.config.commands, "commands");
|
|
5212
5493
|
const skillsSrc = this.resolveConfigPath(this.config.skills, "skills");
|
|
5213
|
-
if (!
|
|
5494
|
+
if (!existsSync7(commandsSrc) || !existsSync7(skillsSrc)) return [];
|
|
5214
5495
|
const commandNames = collectTopLevelCommandNames(commandsSrc);
|
|
5215
5496
|
const collidingSkills = [];
|
|
5216
|
-
for (const entry of
|
|
5497
|
+
for (const entry of readdirSync2(skillsSrc, { withFileTypes: true })) {
|
|
5217
5498
|
if (!entry.isDirectory()) continue;
|
|
5218
5499
|
const skillFile = join2(skillsSrc, entry.name, "SKILL.md");
|
|
5219
|
-
if (!
|
|
5220
|
-
const content =
|
|
5500
|
+
if (!existsSync7(skillFile)) continue;
|
|
5501
|
+
const content = readFileSync3(skillFile, "utf-8");
|
|
5221
5502
|
const effectiveName = getEffectiveSkillName(content, entry.name);
|
|
5222
5503
|
if (commandNames.has(effectiveName)) {
|
|
5223
5504
|
collidingSkills.push({ dirName: entry.name, effectiveName });
|
|
@@ -5225,16 +5506,48 @@ var ClaudeCodeGenerator = class extends Generator {
|
|
|
5225
5506
|
}
|
|
5226
5507
|
return collidingSkills;
|
|
5227
5508
|
}
|
|
5509
|
+
collectCommandWrappedSkills() {
|
|
5510
|
+
if (!this.config.commands) return [];
|
|
5511
|
+
const commandsSrc = this.resolveConfigPath(this.config.commands, "commands");
|
|
5512
|
+
const skillsSrc = this.resolveConfigPath(this.config.skills, "skills");
|
|
5513
|
+
if (!existsSync7(commandsSrc) || !existsSync7(skillsSrc)) return [];
|
|
5514
|
+
const referencedSkills = collectWrappedSkillNames(commandsSrc);
|
|
5515
|
+
if (referencedSkills.size === 0) return [];
|
|
5516
|
+
const wrappedSkills = [];
|
|
5517
|
+
for (const entry of readdirSync2(skillsSrc, { withFileTypes: true })) {
|
|
5518
|
+
if (!entry.isDirectory()) continue;
|
|
5519
|
+
const skillFile = join2(skillsSrc, entry.name, "SKILL.md");
|
|
5520
|
+
if (!existsSync7(skillFile)) continue;
|
|
5521
|
+
const content = readFileSync3(skillFile, "utf-8");
|
|
5522
|
+
const effectiveName = getEffectiveSkillName(content, entry.name);
|
|
5523
|
+
if (referencedSkills.has(effectiveName)) {
|
|
5524
|
+
wrappedSkills.push({ dirName: entry.name, effectiveName });
|
|
5525
|
+
}
|
|
5526
|
+
}
|
|
5527
|
+
return wrappedSkills;
|
|
5528
|
+
}
|
|
5228
5529
|
};
|
|
5229
5530
|
function collectTopLevelCommandNames(commandsRoot) {
|
|
5230
5531
|
const commandNames = /* @__PURE__ */ new Set();
|
|
5231
|
-
for (const entry of
|
|
5532
|
+
for (const entry of readdirSync2(commandsRoot, { withFileTypes: true })) {
|
|
5232
5533
|
if (entry.isFile() && entry.name.toLowerCase().endsWith(".md")) {
|
|
5233
|
-
commandNames.add(
|
|
5534
|
+
commandNames.add(basename2(entry.name, ".md"));
|
|
5234
5535
|
}
|
|
5235
5536
|
}
|
|
5236
5537
|
return commandNames;
|
|
5237
5538
|
}
|
|
5539
|
+
function collectWrappedSkillNames(commandsRoot) {
|
|
5540
|
+
const wrappedSkills = /* @__PURE__ */ new Set();
|
|
5541
|
+
for (const entry of readdirSync2(commandsRoot, { withFileTypes: true })) {
|
|
5542
|
+
if (!entry.isFile() || !entry.name.toLowerCase().endsWith(".md")) continue;
|
|
5543
|
+
const content = readFileSync3(join2(commandsRoot, entry.name), "utf-8");
|
|
5544
|
+
for (const match of content.matchAll(/Use the `([^`]+)` skill\./g)) {
|
|
5545
|
+
const skillName = match[1]?.trim();
|
|
5546
|
+
if (skillName) wrappedSkills.add(skillName);
|
|
5547
|
+
}
|
|
5548
|
+
}
|
|
5549
|
+
return wrappedSkills;
|
|
5550
|
+
}
|
|
5238
5551
|
function getEffectiveSkillName(content, fallback) {
|
|
5239
5552
|
const frontmatter = extractFrontmatterLines(content);
|
|
5240
5553
|
if (!frontmatter) return fallback;
|
|
@@ -5251,13 +5564,14 @@ function buildHiddenSkillName(name) {
|
|
|
5251
5564
|
const trimmed = name.length > maxBaseLength ? name.slice(0, maxBaseLength) : name;
|
|
5252
5565
|
return `${trimmed}-skill`;
|
|
5253
5566
|
}
|
|
5254
|
-
function
|
|
5567
|
+
function rewriteClaudeSkillVisibility(content, options) {
|
|
5255
5568
|
const frontmatter = extractFrontmatterLines(content);
|
|
5256
5569
|
if (!frontmatter) {
|
|
5570
|
+
const generatedFrontmatter = ["---"];
|
|
5571
|
+
if (options.nameOverride) generatedFrontmatter.push(`name: ${options.nameOverride}`);
|
|
5572
|
+
if (options.userInvocable === false) generatedFrontmatter.push("user-invocable: false");
|
|
5257
5573
|
return [
|
|
5258
|
-
|
|
5259
|
-
`name: ${hiddenName}`,
|
|
5260
|
-
"user-invocable: false",
|
|
5574
|
+
...generatedFrontmatter,
|
|
5261
5575
|
"---",
|
|
5262
5576
|
"",
|
|
5263
5577
|
content.trimStart()
|
|
@@ -5268,18 +5582,18 @@ function rewriteClaudeCollidingSkill(content, hiddenName) {
|
|
|
5268
5582
|
let sawUserInvocable = false;
|
|
5269
5583
|
for (let index = 0; index < rewritten.length; index += 1) {
|
|
5270
5584
|
const trimmed = rewritten[index].trim();
|
|
5271
|
-
if (/^name:\s*/i.test(trimmed)) {
|
|
5272
|
-
rewritten[index] = `name: ${
|
|
5585
|
+
if (options.nameOverride && /^name:\s*/i.test(trimmed)) {
|
|
5586
|
+
rewritten[index] = `name: ${options.nameOverride}`;
|
|
5273
5587
|
sawName = true;
|
|
5274
5588
|
continue;
|
|
5275
5589
|
}
|
|
5276
|
-
if (/^user-invocable:\s*/i.test(trimmed)) {
|
|
5590
|
+
if (options.userInvocable === false && /^user-invocable:\s*/i.test(trimmed)) {
|
|
5277
5591
|
rewritten[index] = "user-invocable: false";
|
|
5278
5592
|
sawUserInvocable = true;
|
|
5279
5593
|
}
|
|
5280
5594
|
}
|
|
5281
|
-
if (!sawName) rewritten.push(`name: ${
|
|
5282
|
-
if (!sawUserInvocable) rewritten.push("user-invocable: false");
|
|
5595
|
+
if (options.nameOverride && !sawName) rewritten.push(`name: ${options.nameOverride}`);
|
|
5596
|
+
if (options.userInvocable === false && !sawUserInvocable) rewritten.push("user-invocable: false");
|
|
5283
5597
|
const lines = content.split("\n");
|
|
5284
5598
|
const endIndex = findFrontmatterEndIndex(lines);
|
|
5285
5599
|
const body = endIndex === -1 ? content : lines.slice(endIndex + 1).join("\n");
|
|
@@ -5308,162 +5622,51 @@ function stripYamlScalar(value) {
|
|
|
5308
5622
|
}
|
|
5309
5623
|
return trimmed;
|
|
5310
5624
|
}
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
|
|
5318
|
-
|
|
5319
|
-
|
|
5320
|
-
for (const line of lines) {
|
|
5321
|
-
const match = line.match(/^#\s+(.*)$/);
|
|
5322
|
-
if (match?.[1]?.trim()) return match[1].trim();
|
|
5323
|
-
}
|
|
5324
|
-
return void 0;
|
|
5325
|
-
}
|
|
5326
|
-
function splitMarkdownFrontmatter(content) {
|
|
5327
|
-
const lines = content.split(/\r?\n/);
|
|
5328
|
-
if (lines[0]?.trim() !== "---") {
|
|
5329
|
-
return {
|
|
5330
|
-
frontmatterLines: [],
|
|
5331
|
-
body: content
|
|
5332
|
-
};
|
|
5333
|
-
}
|
|
5334
|
-
let endIndex = -1;
|
|
5335
|
-
for (let i = 1; i < lines.length; i += 1) {
|
|
5336
|
-
if (lines[i].trim() === "---") {
|
|
5337
|
-
endIndex = i;
|
|
5338
|
-
break;
|
|
5339
|
-
}
|
|
5625
|
+
function buildClaudeDisallowedTools(frontmatter) {
|
|
5626
|
+
const tools = /* @__PURE__ */ new Set();
|
|
5627
|
+
const permission = asMap2(frontmatter.permission);
|
|
5628
|
+
const bash = asMap2(permission?.bash);
|
|
5629
|
+
const legacyTools = asMap2(frontmatter.tools);
|
|
5630
|
+
if (permission?.edit === "deny") {
|
|
5631
|
+
tools.add("Write");
|
|
5632
|
+
tools.add("Edit");
|
|
5633
|
+
tools.add("MultiEdit");
|
|
5340
5634
|
}
|
|
5341
|
-
if (
|
|
5342
|
-
|
|
5343
|
-
frontmatterLines: [],
|
|
5344
|
-
body: content
|
|
5345
|
-
};
|
|
5635
|
+
if (permission?.bash === "deny" || bash?.["*"] === "deny") {
|
|
5636
|
+
tools.add("Bash");
|
|
5346
5637
|
}
|
|
5347
|
-
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
}
|
|
5352
|
-
function parseScalarValue(raw) {
|
|
5353
|
-
const trimmed = raw.trim();
|
|
5354
|
-
if (trimmed === "true") return true;
|
|
5355
|
-
if (trimmed === "false") return false;
|
|
5356
|
-
if (/^-?\d+(?:\.\d+)?$/.test(trimmed)) return Number(trimmed);
|
|
5357
|
-
if (trimmed.length >= 2) {
|
|
5358
|
-
if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
5359
|
-
return trimmed.slice(1, -1);
|
|
5360
|
-
}
|
|
5638
|
+
if (legacyTools?.write === false || legacyTools?.edit === false || legacyTools?.patch === false || legacyTools?.multiedit === false) {
|
|
5639
|
+
tools.add("Write");
|
|
5640
|
+
tools.add("Edit");
|
|
5641
|
+
tools.add("MultiEdit");
|
|
5361
5642
|
}
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
function parseAgentFrontmatter(frontmatterLines) {
|
|
5365
|
-
const root = {};
|
|
5366
|
-
const stack = [
|
|
5367
|
-
{ indent: -1, target: root }
|
|
5368
|
-
];
|
|
5369
|
-
for (const line of frontmatterLines) {
|
|
5370
|
-
if (!line.trim() || line.trim().startsWith("#")) continue;
|
|
5371
|
-
const match = line.match(/^(\s*)(?:"([^"]+)"|'([^']+)'|([A-Za-z0-9_.-]+))\s*:\s*(.*)$/);
|
|
5372
|
-
if (!match) continue;
|
|
5373
|
-
const indent = match[1].length;
|
|
5374
|
-
const key = match[2] ?? match[3] ?? match[4];
|
|
5375
|
-
const rawValue = match[5].trim();
|
|
5376
|
-
while (stack.length > 1 && indent <= stack[stack.length - 1].indent) {
|
|
5377
|
-
stack.pop();
|
|
5378
|
-
}
|
|
5379
|
-
const parent = stack[stack.length - 1].target;
|
|
5380
|
-
if (!rawValue) {
|
|
5381
|
-
const nested = {};
|
|
5382
|
-
parent[key] = nested;
|
|
5383
|
-
stack.push({ indent, target: nested });
|
|
5384
|
-
continue;
|
|
5385
|
-
}
|
|
5386
|
-
parent[key] = parseScalarValue(rawValue);
|
|
5643
|
+
if (legacyTools?.bash === false || legacyTools?.shell === false) {
|
|
5644
|
+
tools.add("Bash");
|
|
5387
5645
|
}
|
|
5388
|
-
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
const files = [];
|
|
5393
|
-
for (const entry of entries) {
|
|
5394
|
-
const fullPath = resolve5(dir, entry);
|
|
5395
|
-
const stat = statSync(fullPath);
|
|
5396
|
-
if (stat.isDirectory()) {
|
|
5397
|
-
files.push(...walkMarkdownFiles(fullPath));
|
|
5398
|
-
continue;
|
|
5399
|
-
}
|
|
5400
|
-
if (stat.isFile() && entry.endsWith(".md")) {
|
|
5401
|
-
files.push(fullPath);
|
|
5646
|
+
if (typeof frontmatter.disallowedTools === "string") {
|
|
5647
|
+
for (const token of frontmatter.disallowedTools.split(",")) {
|
|
5648
|
+
const trimmed = token.trim();
|
|
5649
|
+
if (trimmed) tools.add(trimmed);
|
|
5402
5650
|
}
|
|
5403
5651
|
}
|
|
5404
|
-
return
|
|
5405
|
-
}
|
|
5406
|
-
function parseCanonicalAgentFile(agentPath) {
|
|
5407
|
-
const content = readFileSync3(agentPath, "utf-8");
|
|
5408
|
-
const { frontmatterLines, body } = splitMarkdownFrontmatter(content);
|
|
5409
|
-
const frontmatter = parseAgentFrontmatter(frontmatterLines);
|
|
5410
|
-
const fileStem = basename2(agentPath, ".md");
|
|
5411
|
-
const name = typeof frontmatter.name === "string" && frontmatter.name ? frontmatter.name : fileStem;
|
|
5412
|
-
const description = typeof frontmatter.description === "string" && frontmatter.description ? frontmatter.description : firstHeading(body);
|
|
5413
|
-
return {
|
|
5414
|
-
filePath: agentPath,
|
|
5415
|
-
fileStem,
|
|
5416
|
-
name,
|
|
5417
|
-
description,
|
|
5418
|
-
body: body.trim(),
|
|
5419
|
-
frontmatter
|
|
5420
|
-
};
|
|
5421
|
-
}
|
|
5422
|
-
function readCanonicalAgentFiles(agentsDir) {
|
|
5423
|
-
if (!agentsDir || !existsSync7(agentsDir)) return [];
|
|
5424
|
-
return walkMarkdownFiles(agentsDir).sort((a, b) => a.localeCompare(b)).map(parseCanonicalAgentFile);
|
|
5425
|
-
}
|
|
5426
|
-
|
|
5427
|
-
// src/delegation.ts
|
|
5428
|
-
function getPortableDelegationProfile(frontmatter) {
|
|
5429
|
-
const permission = asMap(frontmatter.permission);
|
|
5430
|
-
const bash = asMap(permission?.bash);
|
|
5431
|
-
const task = asMap(permission?.task);
|
|
5432
|
-
return {
|
|
5433
|
-
mode: typeof frontmatter.mode === "string" ? frontmatter.mode : void 0,
|
|
5434
|
-
hidden: frontmatter.hidden === true,
|
|
5435
|
-
editPolicy: typeof permission?.edit === "string" ? permission.edit : void 0,
|
|
5436
|
-
bashPolicy: typeof bash?.["*"] === "string" ? bash["*"] : void 0,
|
|
5437
|
-
taskPolicy: typeof task?.["*"] === "string" ? task["*"] : void 0
|
|
5438
|
-
};
|
|
5652
|
+
return Array.from(tools);
|
|
5439
5653
|
}
|
|
5440
|
-
function
|
|
5441
|
-
|
|
5442
|
-
const
|
|
5443
|
-
if (
|
|
5444
|
-
|
|
5445
|
-
|
|
5446
|
-
if (profile.editPolicy === "deny") {
|
|
5447
|
-
notes.push("Stay read-only unless the parent task explicitly asks for file edits.");
|
|
5448
|
-
}
|
|
5449
|
-
if (profile.bashPolicy === "deny") {
|
|
5450
|
-
notes.push("Avoid shell commands unless the parent task explicitly requires them.");
|
|
5451
|
-
} else if (profile.bashPolicy === "ask") {
|
|
5452
|
-
notes.push("Use shell commands sparingly and only when they are clearly necessary to complete the task.");
|
|
5453
|
-
}
|
|
5454
|
-
if (profile.taskPolicy === "deny") {
|
|
5455
|
-
notes.push("Do not delegate further subtasks unless the parent task explicitly asks for additional specialist work.");
|
|
5456
|
-
} else if (profile.taskPolicy === "ask") {
|
|
5457
|
-
notes.push("Only delegate further subtasks when the work clearly benefits from another specialist.");
|
|
5654
|
+
function selectClaudeToolsField(frontmatter) {
|
|
5655
|
+
if (typeof frontmatter.tools !== "string") return null;
|
|
5656
|
+
const tools = frontmatter.tools.split(",").map((token) => token.trim()).filter(Boolean);
|
|
5657
|
+
if (tools.length === 0) return null;
|
|
5658
|
+
if (tools.some((token) => token.startsWith("mcp__"))) {
|
|
5659
|
+
return null;
|
|
5458
5660
|
}
|
|
5459
|
-
return
|
|
5661
|
+
return tools.join(", ");
|
|
5460
5662
|
}
|
|
5461
|
-
function
|
|
5663
|
+
function asMap2(value) {
|
|
5462
5664
|
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
5463
5665
|
return value;
|
|
5464
5666
|
}
|
|
5465
5667
|
|
|
5466
5668
|
// src/generators/cursor/index.ts
|
|
5669
|
+
import { existsSync as existsSync8 } from "fs";
|
|
5467
5670
|
var CursorGenerator = class extends Generator {
|
|
5468
5671
|
platform = "cursor";
|
|
5469
5672
|
async generate() {
|
|
@@ -5548,7 +5751,9 @@ var CursorGenerator = class extends Generator {
|
|
|
5548
5751
|
if (entry.timeout) hookDef.timeout = entry.timeout;
|
|
5549
5752
|
if (entry.matcher) hookDef.matcher = entry.matcher;
|
|
5550
5753
|
if (entry.failClosed) hookDef.failClosed = entry.failClosed;
|
|
5551
|
-
if (entry.loop_limit !== void 0
|
|
5754
|
+
if (entry.loop_limit !== void 0 && CURSOR_LOOP_LIMIT_HOOK_EVENTS.includes(event)) {
|
|
5755
|
+
hookDef.loop_limit = entry.loop_limit;
|
|
5756
|
+
}
|
|
5552
5757
|
return hookDef;
|
|
5553
5758
|
})
|
|
5554
5759
|
];
|
|
@@ -5682,6 +5887,23 @@ function parseCommandFrontmatterDescription(frontmatterLines) {
|
|
|
5682
5887
|
}
|
|
5683
5888
|
return void 0;
|
|
5684
5889
|
}
|
|
5890
|
+
function parseCommandFrontmatterString(frontmatterLines, key) {
|
|
5891
|
+
const pattern = new RegExp(`^${key}:\\s*(.+)\\s*$`, "i");
|
|
5892
|
+
for (const line of frontmatterLines) {
|
|
5893
|
+
const match = pattern.exec(line.trim());
|
|
5894
|
+
if (match?.[1]) {
|
|
5895
|
+
return stripYamlScalar2(match[1]);
|
|
5896
|
+
}
|
|
5897
|
+
}
|
|
5898
|
+
return void 0;
|
|
5899
|
+
}
|
|
5900
|
+
function parseCommandFrontmatterBoolean(frontmatterLines, key) {
|
|
5901
|
+
const value = parseCommandFrontmatterString(frontmatterLines, key);
|
|
5902
|
+
if (!value) return void 0;
|
|
5903
|
+
if (/^true$/i.test(value)) return true;
|
|
5904
|
+
if (/^false$/i.test(value)) return false;
|
|
5905
|
+
return void 0;
|
|
5906
|
+
}
|
|
5685
5907
|
function walkMarkdownFiles2(dir) {
|
|
5686
5908
|
const entries = readdirSync3(dir);
|
|
5687
5909
|
const files = [];
|
|
@@ -5712,6 +5934,9 @@ function readCanonicalCommandFiles(commandsDir) {
|
|
|
5712
5934
|
commandId,
|
|
5713
5935
|
title,
|
|
5714
5936
|
description: parseCommandFrontmatterDescription(frontmatterLines),
|
|
5937
|
+
agent: parseCommandFrontmatterString(frontmatterLines, "agent"),
|
|
5938
|
+
subtask: parseCommandFrontmatterBoolean(frontmatterLines, "subtask"),
|
|
5939
|
+
model: parseCommandFrontmatterString(frontmatterLines, "model"),
|
|
5715
5940
|
body: body.trim()
|
|
5716
5941
|
};
|
|
5717
5942
|
});
|
|
@@ -5730,6 +5955,7 @@ var CodexGenerator = class extends Generator {
|
|
|
5730
5955
|
async generate() {
|
|
5731
5956
|
await Promise.all([
|
|
5732
5957
|
this.generateManifest(),
|
|
5958
|
+
this.generateAppConfig(),
|
|
5733
5959
|
this.generateMcpConfig(".mcp.json", {
|
|
5734
5960
|
includeDefaultAuthHeaders: false,
|
|
5735
5961
|
transformRemoteEntry: ({ name, server }) => {
|
|
@@ -5824,6 +6050,11 @@ var CodexGenerator = class extends Generator {
|
|
|
5824
6050
|
}
|
|
5825
6051
|
await this.writeJson(".codex-plugin/plugin.json", manifest);
|
|
5826
6052
|
}
|
|
6053
|
+
async generateAppConfig() {
|
|
6054
|
+
const appConfig = this.config.platforms?.codex?.app;
|
|
6055
|
+
if (!appConfig || typeof appConfig !== "object" || Array.isArray(appConfig)) return;
|
|
6056
|
+
await this.writeJson(".app.json", appConfig);
|
|
6057
|
+
}
|
|
5827
6058
|
async generatePermissionsCompanion() {
|
|
5828
6059
|
const compilerIntent = this.getCompilerIntent();
|
|
5829
6060
|
const rules = collectPermissionRules(this.config.permissions);
|
|
@@ -5961,8 +6192,8 @@ var CodexGenerator = class extends Generator {
|
|
|
5961
6192
|
};
|
|
5962
6193
|
|
|
5963
6194
|
// src/generators/opencode/index.ts
|
|
5964
|
-
import { existsSync as existsSync11, readdirSync as readdirSync4, readFileSync as readFileSync5, statSync as statSync3 } from "fs";
|
|
5965
|
-
import { resolve as resolve7 } from "path";
|
|
6195
|
+
import { existsSync as existsSync11, readdirSync as readdirSync4, readFileSync as readFileSync5, statSync as statSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
6196
|
+
import { basename as basename3, resolve as resolve7 } from "path";
|
|
5966
6197
|
var OpenCodeGenerator = class extends Generator {
|
|
5967
6198
|
platform = "opencode";
|
|
5968
6199
|
async generate() {
|
|
@@ -5972,9 +6203,11 @@ var OpenCodeGenerator = class extends Generator {
|
|
|
5972
6203
|
]);
|
|
5973
6204
|
this.copySkills();
|
|
5974
6205
|
this.copyCommands();
|
|
6206
|
+
this.copyAgents();
|
|
5975
6207
|
this.copyScripts();
|
|
5976
6208
|
this.copyAssets();
|
|
5977
6209
|
this.copyPassthrough();
|
|
6210
|
+
this.rewriteOpenCodeSkillAgentMentions();
|
|
5978
6211
|
}
|
|
5979
6212
|
async generatePackageJson() {
|
|
5980
6213
|
const npmName = this.config.platforms?.opencode?.npmPackage ?? `opencode-${this.config.name}`;
|
|
@@ -6286,7 +6519,10 @@ var OpenCodeGenerator = class extends Generator {
|
|
|
6286
6519
|
for (const command2 of commands) {
|
|
6287
6520
|
output[command2.commandId] = {
|
|
6288
6521
|
template: command2.body,
|
|
6289
|
-
...command2.description ? { description: command2.description } : {}
|
|
6522
|
+
...command2.description ? { description: command2.description } : {},
|
|
6523
|
+
...command2.agent ? { agent: command2.agent } : {},
|
|
6524
|
+
...typeof command2.subtask === "boolean" ? { subtask: command2.subtask } : {},
|
|
6525
|
+
...command2.model ? { model: command2.model } : {}
|
|
6290
6526
|
};
|
|
6291
6527
|
}
|
|
6292
6528
|
return output;
|
|
@@ -6312,14 +6548,36 @@ var OpenCodeGenerator = class extends Generator {
|
|
|
6312
6548
|
if (typeof agent.frontmatter.temperature === "number") {
|
|
6313
6549
|
definition.temperature = agent.frontmatter.temperature;
|
|
6314
6550
|
}
|
|
6551
|
+
if (typeof agent.frontmatter.steps === "number") {
|
|
6552
|
+
definition.steps = agent.frontmatter.steps;
|
|
6553
|
+
}
|
|
6554
|
+
if (typeof agent.frontmatter.maxSteps === "number" && definition.steps === void 0) {
|
|
6555
|
+
definition.steps = agent.frontmatter.maxSteps;
|
|
6556
|
+
}
|
|
6557
|
+
if (typeof agent.frontmatter.disable === "boolean") {
|
|
6558
|
+
definition.disable = agent.frontmatter.disable;
|
|
6559
|
+
}
|
|
6315
6560
|
if (typeof agent.frontmatter.hidden === "boolean") {
|
|
6316
6561
|
definition.hidden = agent.frontmatter.hidden;
|
|
6317
6562
|
}
|
|
6318
|
-
|
|
6563
|
+
if (typeof agent.frontmatter.color === "string" && agent.frontmatter.color) {
|
|
6564
|
+
definition.color = agent.frontmatter.color;
|
|
6565
|
+
}
|
|
6566
|
+
if (typeof agent.frontmatter.topP === "number") {
|
|
6567
|
+
definition.topP = agent.frontmatter.topP;
|
|
6568
|
+
}
|
|
6569
|
+
if (typeof agent.frontmatter.top_p === "number" && definition.topP === void 0) {
|
|
6570
|
+
definition.topP = agent.frontmatter.top_p;
|
|
6571
|
+
}
|
|
6572
|
+
const legacyToolTranslation = translateLegacyOpenCodeTools(agent.frontmatter.tools);
|
|
6573
|
+
const permission = mergeOpenCodeMaps(
|
|
6574
|
+
legacyToolTranslation.permission,
|
|
6575
|
+
asOpenCodeMap(agent.frontmatter.permission)
|
|
6576
|
+
);
|
|
6319
6577
|
if (permission) {
|
|
6320
6578
|
definition.permission = permission;
|
|
6321
6579
|
}
|
|
6322
|
-
const tools =
|
|
6580
|
+
const tools = legacyToolTranslation.untranslated;
|
|
6323
6581
|
if (tools) {
|
|
6324
6582
|
definition.tools = tools;
|
|
6325
6583
|
}
|
|
@@ -6401,11 +6659,99 @@ var OpenCodeGenerator = class extends Generator {
|
|
|
6401
6659
|
}
|
|
6402
6660
|
return files;
|
|
6403
6661
|
}
|
|
6662
|
+
rewriteOpenCodeSkillAgentMentions() {
|
|
6663
|
+
if (!this.config.agents || !this.config.skills) return;
|
|
6664
|
+
const skillsDir = resolve7(this.outDir, "skills");
|
|
6665
|
+
if (!existsSync11(skillsDir)) return;
|
|
6666
|
+
const agentsDir = this.resolveConfigPath(this.config.agents, "agents");
|
|
6667
|
+
const agentNames = readCanonicalAgentFiles(agentsDir).map((agent) => agent.name).filter(Boolean);
|
|
6668
|
+
if (agentNames.length === 0) return;
|
|
6669
|
+
for (const filePath of this.walkFiles(skillsDir)) {
|
|
6670
|
+
if (basename3(filePath) !== "SKILL.md") continue;
|
|
6671
|
+
const source = readFileSync5(filePath, "utf-8");
|
|
6672
|
+
let rewritten = source;
|
|
6673
|
+
for (const agentName of agentNames) {
|
|
6674
|
+
const escaped = escapeRegExp(agentName);
|
|
6675
|
+
rewritten = rewritten.replace(new RegExp(`\`(${escaped})\``, "g"), "`@$1`");
|
|
6676
|
+
}
|
|
6677
|
+
if (rewritten !== source) {
|
|
6678
|
+
writeFileSync2(filePath, rewritten);
|
|
6679
|
+
}
|
|
6680
|
+
}
|
|
6681
|
+
}
|
|
6404
6682
|
};
|
|
6405
6683
|
function asOpenCodeMap(value) {
|
|
6406
6684
|
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
6407
6685
|
return value;
|
|
6408
6686
|
}
|
|
6687
|
+
function mergeOpenCodeMaps(base, override) {
|
|
6688
|
+
if (!base) return override;
|
|
6689
|
+
if (!override) return base;
|
|
6690
|
+
const merged = { ...base };
|
|
6691
|
+
for (const [key, value] of Object.entries(override)) {
|
|
6692
|
+
if (isOpenCodeMap(merged[key]) && isOpenCodeMap(value)) {
|
|
6693
|
+
merged[key] = {
|
|
6694
|
+
...merged[key],
|
|
6695
|
+
...value
|
|
6696
|
+
};
|
|
6697
|
+
continue;
|
|
6698
|
+
}
|
|
6699
|
+
merged[key] = value;
|
|
6700
|
+
}
|
|
6701
|
+
return merged;
|
|
6702
|
+
}
|
|
6703
|
+
function translateLegacyOpenCodeTools(value) {
|
|
6704
|
+
const tools = asOpenCodeMap(value);
|
|
6705
|
+
if (!tools) return {};
|
|
6706
|
+
const permission = {};
|
|
6707
|
+
const untranslated = {};
|
|
6708
|
+
for (const [rawKey, rawValue] of Object.entries(tools)) {
|
|
6709
|
+
const key = normalizeLegacyOpenCodeToolKey(rawKey);
|
|
6710
|
+
const translated = translateLegacyOpenCodeToolValue(rawValue);
|
|
6711
|
+
if (translated === void 0) {
|
|
6712
|
+
untranslated[rawKey] = rawValue;
|
|
6713
|
+
continue;
|
|
6714
|
+
}
|
|
6715
|
+
permission[key] = translated;
|
|
6716
|
+
}
|
|
6717
|
+
return {
|
|
6718
|
+
...Object.keys(permission).length > 0 ? { permission } : {},
|
|
6719
|
+
...Object.keys(untranslated).length > 0 ? { untranslated } : {}
|
|
6720
|
+
};
|
|
6721
|
+
}
|
|
6722
|
+
function normalizeLegacyOpenCodeToolKey(key) {
|
|
6723
|
+
switch (key) {
|
|
6724
|
+
case "write":
|
|
6725
|
+
case "patch":
|
|
6726
|
+
case "multiedit":
|
|
6727
|
+
return "edit";
|
|
6728
|
+
case "shell":
|
|
6729
|
+
return "bash";
|
|
6730
|
+
default:
|
|
6731
|
+
return key;
|
|
6732
|
+
}
|
|
6733
|
+
}
|
|
6734
|
+
function translateLegacyOpenCodeToolValue(value) {
|
|
6735
|
+
if (typeof value === "boolean") {
|
|
6736
|
+
return value ? "allow" : "deny";
|
|
6737
|
+
}
|
|
6738
|
+
if (typeof value === "string" && ["allow", "ask", "deny"].includes(value)) {
|
|
6739
|
+
return value;
|
|
6740
|
+
}
|
|
6741
|
+
if (!isOpenCodeMap(value)) return void 0;
|
|
6742
|
+
const nested = {};
|
|
6743
|
+
for (const [key, rawNested] of Object.entries(value)) {
|
|
6744
|
+
const translated = translateLegacyOpenCodeToolValue(rawNested);
|
|
6745
|
+
if (translated === void 0 || typeof translated === "object") {
|
|
6746
|
+
return void 0;
|
|
6747
|
+
}
|
|
6748
|
+
nested[key] = translated;
|
|
6749
|
+
}
|
|
6750
|
+
return nested;
|
|
6751
|
+
}
|
|
6752
|
+
function isOpenCodeMap(value) {
|
|
6753
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
6754
|
+
}
|
|
6409
6755
|
function mapHookEventName(event) {
|
|
6410
6756
|
const map = {
|
|
6411
6757
|
sessionStart: "session.created",
|
|
@@ -6426,6 +6772,9 @@ function mapHookEventName(event) {
|
|
|
6426
6772
|
function toPascalCase(str) {
|
|
6427
6773
|
return str.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
6428
6774
|
}
|
|
6775
|
+
function escapeRegExp(value) {
|
|
6776
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6777
|
+
}
|
|
6429
6778
|
|
|
6430
6779
|
// src/generators/github-copilot/index.ts
|
|
6431
6780
|
var GitHubCopilotGenerator = class extends Generator {
|
|
@@ -6673,6 +7022,40 @@ var GENERATORS = {
|
|
|
6673
7022
|
cline: ClineGenerator,
|
|
6674
7023
|
amp: AmpGenerator
|
|
6675
7024
|
};
|
|
7025
|
+
function assertPathWithinRoot(rootDir, configPath, configKey) {
|
|
7026
|
+
const resolvedPath = resolve8(rootDir, configPath);
|
|
7027
|
+
const rel = relative5(rootDir, resolvedPath);
|
|
7028
|
+
if (rel.startsWith("..")) {
|
|
7029
|
+
throw new Error(`${configKey} path "${configPath}" resolves outside the project root.`);
|
|
7030
|
+
}
|
|
7031
|
+
}
|
|
7032
|
+
function validateConfiguredPaths(config, rootDir) {
|
|
7033
|
+
assertPathWithinRoot(rootDir, config.skills, "skills");
|
|
7034
|
+
if (config.commands) {
|
|
7035
|
+
assertPathWithinRoot(rootDir, config.commands, "commands");
|
|
7036
|
+
}
|
|
7037
|
+
if (config.agents) {
|
|
7038
|
+
assertPathWithinRoot(rootDir, config.agents, "agents");
|
|
7039
|
+
}
|
|
7040
|
+
if (config.scripts) {
|
|
7041
|
+
assertPathWithinRoot(rootDir, config.scripts, "scripts");
|
|
7042
|
+
}
|
|
7043
|
+
if (config.assets) {
|
|
7044
|
+
assertPathWithinRoot(rootDir, config.assets, "assets");
|
|
7045
|
+
}
|
|
7046
|
+
if (config.instructions) {
|
|
7047
|
+
assertPathWithinRoot(rootDir, config.instructions, "instructions");
|
|
7048
|
+
}
|
|
7049
|
+
for (const passthroughPath of config.passthrough ?? []) {
|
|
7050
|
+
assertPathWithinRoot(rootDir, passthroughPath, "passthrough");
|
|
7051
|
+
}
|
|
7052
|
+
if (config.brand?.icon) {
|
|
7053
|
+
assertPathWithinRoot(rootDir, config.brand.icon, "brand.icon");
|
|
7054
|
+
}
|
|
7055
|
+
for (const screenshot of config.brand?.screenshots ?? []) {
|
|
7056
|
+
assertPathWithinRoot(rootDir, screenshot, "brand.screenshots");
|
|
7057
|
+
}
|
|
7058
|
+
}
|
|
6676
7059
|
async function build(config, rootDir, options = {}) {
|
|
6677
7060
|
const targets = options.targets ?? config.targets;
|
|
6678
7061
|
const outDir = resolve8(rootDir, config.outDir);
|
|
@@ -6682,10 +7065,11 @@ async function build(config, rootDir, options = {}) {
|
|
|
6682
7065
|
`outDir "${config.outDir}" resolves outside the project root. Refusing to delete.`
|
|
6683
7066
|
);
|
|
6684
7067
|
}
|
|
7068
|
+
validateConfiguredPaths(config, rootDir);
|
|
6685
7069
|
if (options.clean !== false) {
|
|
6686
7070
|
rmSync(outDir, { recursive: true, force: true });
|
|
6687
7071
|
}
|
|
6688
|
-
|
|
7072
|
+
mkdirSync3(outDir, { recursive: true });
|
|
6689
7073
|
const generators = targets.map((target) => {
|
|
6690
7074
|
const GeneratorClass = GENERATORS[target];
|
|
6691
7075
|
if (!GeneratorClass) {
|
|
@@ -6705,7 +7089,7 @@ import { spawn as spawn2 } from "child_process";
|
|
|
6705
7089
|
|
|
6706
7090
|
// src/cli/lint.ts
|
|
6707
7091
|
import { existsSync as existsSync17, readdirSync as readdirSync5, readFileSync as readFileSync6 } from "fs";
|
|
6708
|
-
import { resolve as resolve9, relative as relative6, basename as
|
|
7092
|
+
import { resolve as resolve9, relative as relative6, basename as basename4, dirname as dirname3 } from "path";
|
|
6709
7093
|
|
|
6710
7094
|
// src/validation/platform-rules.ts
|
|
6711
7095
|
var STANDARD_SKILL_FRONTMATTER = [
|
|
@@ -6813,10 +7197,22 @@ var PLATFORM_LIMIT_POLICIES = {
|
|
|
6813
7197
|
},
|
|
6814
7198
|
"codex": {
|
|
6815
7199
|
...NULL_LIMIT_POLICIES,
|
|
6816
|
-
skillDescriptionMax: {
|
|
6817
|
-
|
|
6818
|
-
|
|
6819
|
-
|
|
7200
|
+
skillDescriptionMax: {
|
|
7201
|
+
kind: "advisory",
|
|
7202
|
+
notes: "Pluxx keeps Codex descriptions concise at 1,024 characters as a conservative compatibility heuristic; the current docs do not state this as an official hard cap."
|
|
7203
|
+
},
|
|
7204
|
+
skillNameMustMatchDir: {
|
|
7205
|
+
kind: "advisory",
|
|
7206
|
+
notes: "Pluxx keeps Codex skill directory names aligned with skill names for portability and predictability, but the current docs do not state this as a formal hard requirement."
|
|
7207
|
+
},
|
|
7208
|
+
manifestPromptMax: {
|
|
7209
|
+
kind: "advisory",
|
|
7210
|
+
notes: "Pluxx keeps Codex default prompts short at 128 characters as a conservative listing heuristic; the current docs do not publish this as a hard limit."
|
|
7211
|
+
},
|
|
7212
|
+
manifestPromptCountMax: {
|
|
7213
|
+
kind: "advisory",
|
|
7214
|
+
notes: "Pluxx keeps Codex default prompt count to three as a conservative listing heuristic; the current docs do not publish this as a hard limit."
|
|
7215
|
+
},
|
|
6820
7216
|
manifestPathPrefix: { kind: "hard" },
|
|
6821
7217
|
instructionsMaxBytes: {
|
|
6822
7218
|
kind: "hard",
|
|
@@ -6869,7 +7265,7 @@ var PLATFORM_LIMIT_POLICIES = {
|
|
|
6869
7265
|
var PLATFORM_VALIDATION_RULES = {
|
|
6870
7266
|
"claude-code": {
|
|
6871
7267
|
platform: "claude-code",
|
|
6872
|
-
summary: "Claude Code plugins use an optional manifest at .claude-plugin/plugin.json with auto-discovery for skills, commands, agents, hooks, MCP, and output styles.",
|
|
7268
|
+
summary: "Claude Code plugins use an optional manifest at .claude-plugin/plugin.json with auto-discovery for skills, commands, agents, hooks, MCP, marketplaces, and output styles.",
|
|
6873
7269
|
limits: PLATFORM_LIMITS["claude-code"],
|
|
6874
7270
|
limitPolicies: PLATFORM_LIMIT_POLICIES["claude-code"],
|
|
6875
7271
|
skillDiscoveryDirs: [
|
|
@@ -6877,7 +7273,21 @@ var PLATFORM_VALIDATION_RULES = {
|
|
|
6877
7273
|
],
|
|
6878
7274
|
frontmatter: {
|
|
6879
7275
|
standard: [...STANDARD_SKILL_FRONTMATTER],
|
|
6880
|
-
additional: [
|
|
7276
|
+
additional: [
|
|
7277
|
+
"when_to_use",
|
|
7278
|
+
"argument-hint",
|
|
7279
|
+
"arguments",
|
|
7280
|
+
"user-invocable",
|
|
7281
|
+
"allowed-tools",
|
|
7282
|
+
"model",
|
|
7283
|
+
"effort",
|
|
7284
|
+
"context",
|
|
7285
|
+
"agent",
|
|
7286
|
+
"hooks",
|
|
7287
|
+
"paths",
|
|
7288
|
+
"shell"
|
|
7289
|
+
],
|
|
7290
|
+
notes: "Claude exposes the richest documented skill frontmatter of the core four."
|
|
6881
7291
|
},
|
|
6882
7292
|
manifest: {
|
|
6883
7293
|
files: [".claude-plugin/plugin.json"],
|
|
@@ -6885,43 +7295,58 @@ var PLATFORM_VALIDATION_RULES = {
|
|
|
6885
7295
|
notes: "The manifest is optional; if present, name is the only required field."
|
|
6886
7296
|
},
|
|
6887
7297
|
mcp: {
|
|
6888
|
-
files: [".mcp.json"],
|
|
7298
|
+
files: [".mcp.json", ".claude-plugin/plugin.json"],
|
|
6889
7299
|
rootKey: "mcpServers",
|
|
6890
7300
|
transports: ["stdio", "http", "sse"],
|
|
6891
|
-
auth: ["headers", "env interpolation"],
|
|
6892
|
-
notes: "Claude Code supports either inline MCP config in plugin.json or a separate .mcp.json file."
|
|
7301
|
+
auth: ["headers", "env interpolation", "OAuth 2.0", "bearer tokens", "dynamic headers"],
|
|
7302
|
+
notes: "Claude Code supports either inline MCP config in plugin.json or a separate .mcp.json file, with marketplace and dependency-aware install flows."
|
|
6893
7303
|
},
|
|
6894
7304
|
hooks: {
|
|
6895
7305
|
supported: true,
|
|
6896
|
-
files: ["hooks/hooks.json"],
|
|
6897
|
-
eventNames: [],
|
|
6898
|
-
notes: "Hook configs can be stored in hooks/hooks.json
|
|
7306
|
+
files: ["hooks/hooks.json", ".claude-plugin/plugin.json", "~/.claude/settings.json", ".claude/settings.json", ".claude/settings.local.json"],
|
|
7307
|
+
eventNames: ["SessionStart", "PreToolUse", "PostToolUse", "PermissionRequest", "TaskCreated", "TaskCompleted", "Stop", "Notification", "ConfigChange"],
|
|
7308
|
+
notes: "Hook configs can be stored in hooks/hooks.json, inlined in plugin.json, added in settings files, or scoped through skill and agent frontmatter."
|
|
6899
7309
|
},
|
|
6900
7310
|
instructions: {
|
|
6901
7311
|
files: ["CLAUDE.md"],
|
|
6902
|
-
format: "markdown"
|
|
7312
|
+
format: "markdown",
|
|
7313
|
+
notes: "Claude keeps persistent instructions in CLAUDE.md and pushes longer procedures into skills."
|
|
6903
7314
|
},
|
|
6904
7315
|
sources: [
|
|
6905
|
-
{ label: "Claude Code
|
|
7316
|
+
{ label: "Claude Code MCP docs", url: "https://code.claude.com/docs/en/mcp" },
|
|
7317
|
+
{ label: "Claude Code plugin marketplaces docs", url: "https://code.claude.com/docs/en/plugin-marketplaces" },
|
|
7318
|
+
{ label: "Claude Code plugin dependencies docs", url: "https://code.claude.com/docs/en/plugin-dependencies" },
|
|
7319
|
+
{ label: "Claude Code features overview", url: "https://code.claude.com/docs/en/features-overview" },
|
|
7320
|
+
{ label: "Claude Code best practices", url: "https://code.claude.com/docs/en/best-practices" },
|
|
6906
7321
|
{ label: "Claude Code CLI reference", url: "https://code.claude.com/docs/en/cli-reference" },
|
|
6907
7322
|
{ label: "Claude Code discover plugins docs", url: "https://code.claude.com/docs/en/discover-plugins" },
|
|
7323
|
+
{ label: "Claude Code plugins docs", url: "https://code.claude.com/docs/en/plugins" },
|
|
6908
7324
|
{ label: "Claude Code plugins reference", url: "https://code.claude.com/docs/en/plugins-reference" },
|
|
7325
|
+
{ label: "Claude Code hooks guide", url: "https://code.claude.com/docs/en/hooks-guide" },
|
|
6909
7326
|
{ label: "Claude Code hooks docs", url: "https://code.claude.com/docs/en/hooks" },
|
|
6910
|
-
{ label: "Claude Code skills docs", url: "https://code.claude.com/docs/en/skills" }
|
|
7327
|
+
{ label: "Claude Code skills docs", url: "https://code.claude.com/docs/en/skills" },
|
|
7328
|
+
{ label: "Claude Code sub-agents docs", url: "https://code.claude.com/docs/en/sub-agents" },
|
|
7329
|
+
{ label: "Claude Code env vars docs", url: "https://code.claude.com/docs/en/env-vars" }
|
|
6911
7330
|
]
|
|
6912
7331
|
},
|
|
6913
7332
|
"cursor": {
|
|
6914
7333
|
platform: "cursor",
|
|
6915
|
-
summary: "Cursor plugins use .cursor-plugin/plugin.json plus
|
|
7334
|
+
summary: "Cursor plugins use .cursor-plugin/plugin.json plus native rules, skills, hooks, MCP, marketplace metadata, and subagent surfaces, with additional project and user config outside the plugin bundle.",
|
|
6916
7335
|
limits: PLATFORM_LIMITS["cursor"],
|
|
6917
7336
|
limitPolicies: PLATFORM_LIMIT_POLICIES["cursor"],
|
|
6918
7337
|
skillDiscoveryDirs: [
|
|
6919
7338
|
{ path: "skills/", level: "supported" },
|
|
6920
|
-
{ path: "
|
|
7339
|
+
{ path: ".cursor/skills/", level: "supported" },
|
|
7340
|
+
{ path: "~/.cursor/skills/", level: "supported" },
|
|
7341
|
+
{ path: ".agents/skills/", level: "supported" },
|
|
7342
|
+
{ path: "~/.agents/skills/", level: "supported" },
|
|
7343
|
+
{ path: ".claude/skills/", level: "supported", notes: "Compatibility directory" },
|
|
7344
|
+
{ path: ".codex/skills/", level: "supported", notes: "Compatibility directory" }
|
|
6921
7345
|
],
|
|
6922
7346
|
frontmatter: {
|
|
6923
7347
|
standard: [...STANDARD_SKILL_FRONTMATTER],
|
|
6924
|
-
additional: []
|
|
7348
|
+
additional: [],
|
|
7349
|
+
notes: "Cursor skills document the shared frontmatter set plus compatibility metadata and supporting-file patterns."
|
|
6925
7350
|
},
|
|
6926
7351
|
manifest: {
|
|
6927
7352
|
files: [".cursor-plugin/plugin.json"],
|
|
@@ -6929,16 +7354,16 @@ var PLATFORM_VALIDATION_RULES = {
|
|
|
6929
7354
|
notes: "Cursor documents plugin.json as the required plugin manifest."
|
|
6930
7355
|
},
|
|
6931
7356
|
mcp: {
|
|
6932
|
-
files: ["mcp.json"],
|
|
7357
|
+
files: ["mcp.json", ".cursor/mcp.json", "~/.cursor/mcp.json"],
|
|
6933
7358
|
rootKey: "mcpServers",
|
|
6934
|
-
transports: ["stdio", "
|
|
6935
|
-
auth: ["headers", "env interpolation"]
|
|
7359
|
+
transports: ["stdio", "sse", "streamable-http"],
|
|
7360
|
+
auth: ["headers", "env interpolation", "OAuth", "static OAuth credentials"]
|
|
6936
7361
|
},
|
|
6937
7362
|
hooks: {
|
|
6938
7363
|
supported: true,
|
|
6939
|
-
files: ["hooks/hooks.json"],
|
|
6940
|
-
eventNames: [],
|
|
6941
|
-
notes: "Cursor plugin hooks live under hooks/hooks.json; project hooks also exist separately
|
|
7364
|
+
files: ["hooks/hooks.json", ".cursor/hooks.json", "~/.cursor/hooks.json"],
|
|
7365
|
+
eventNames: ["sessionStart", "preToolUse", "postToolUse", "subagentStart", "subagentStop", "beforeShellExecution", "afterShellExecution"],
|
|
7366
|
+
notes: "Cursor plugin hooks live under hooks/hooks.json; project and user hooks also exist separately and reload on save."
|
|
6942
7367
|
},
|
|
6943
7368
|
instructions: {
|
|
6944
7369
|
files: ["rules/", "AGENTS.md"],
|
|
@@ -6946,25 +7371,32 @@ var PLATFORM_VALIDATION_RULES = {
|
|
|
6946
7371
|
notes: "rules/ is the plugin-native instruction surface. AGENTS.md remains useful as shared repo guidance. Cursor subagents use markdown files under .cursor/agents or ~/.cursor/agents (with .claude/.codex compatibility paths)."
|
|
6947
7372
|
},
|
|
6948
7373
|
sources: [
|
|
6949
|
-
{ label: "Cursor plugins reference", url: "https://cursor.com/docs/reference/plugins" },
|
|
6950
7374
|
{ label: "Cursor plugins overview", url: "https://cursor.com/docs/plugins" },
|
|
6951
7375
|
{ label: "Cursor hooks docs", url: "https://cursor.com/docs/hooks" },
|
|
6952
7376
|
{ label: "Cursor skills docs", url: "https://cursor.com/docs/skills" },
|
|
6953
7377
|
{ label: "Cursor rules docs", url: "https://cursor.com/docs/rules" },
|
|
6954
7378
|
{ label: "Cursor MCP docs", url: "https://cursor.com/docs/mcp" },
|
|
6955
7379
|
{ label: "Cursor CLI headless docs", url: "https://cursor.com/docs/cli/headless" },
|
|
7380
|
+
{ label: "Cursor CLI slash commands", url: "https://cursor.com/docs/cli/reference/slash-commands" },
|
|
6956
7381
|
{ label: "Cursor CLI parameters", url: "https://cursor.com/docs/cli/reference/parameters" },
|
|
6957
7382
|
{ label: "Cursor CLI authentication", url: "https://cursor.com/docs/cli/reference/authentication" },
|
|
7383
|
+
{ label: "Cursor CLI permissions", url: "https://cursor.com/docs/cli/reference/permissions" },
|
|
7384
|
+
{ label: "Cursor CLI configuration", url: "https://cursor.com/docs/cli/reference/configuration" },
|
|
7385
|
+
{ label: "Cursor ACP docs", url: "https://cursor.com/docs/cli/acp" },
|
|
6958
7386
|
{ label: "Cursor subagents docs", url: "https://cursor.com/docs/subagents" }
|
|
6959
7387
|
]
|
|
6960
7388
|
},
|
|
6961
7389
|
"codex": {
|
|
6962
7390
|
platform: "codex",
|
|
6963
|
-
summary: "Codex plugins use .codex-plugin/plugin.json with skills, .mcp.json
|
|
7391
|
+
summary: "Codex plugins use .codex-plugin/plugin.json with skills, optional .mcp.json and .app.json, marketplace catalogs, cache installs, AGENTS.md instructions, and separate hook configuration.",
|
|
6964
7392
|
limits: PLATFORM_LIMITS["codex"],
|
|
6965
7393
|
limitPolicies: PLATFORM_LIMIT_POLICIES["codex"],
|
|
6966
7394
|
skillDiscoveryDirs: [
|
|
6967
|
-
{ path: "skills/", level: "supported" }
|
|
7395
|
+
{ path: "skills/", level: "supported" },
|
|
7396
|
+
{ path: "$CWD/.agents/skills/", level: "supported" },
|
|
7397
|
+
{ path: "ancestor .agents/skills/", level: "supported", notes: "Walks upward until repo root" },
|
|
7398
|
+
{ path: "$HOME/.agents/skills/", level: "supported" },
|
|
7399
|
+
{ path: "/etc/codex/skills/", level: "supported" }
|
|
6968
7400
|
],
|
|
6969
7401
|
frontmatter: {
|
|
6970
7402
|
standard: [...STANDARD_SKILL_FRONTMATTER],
|
|
@@ -6976,68 +7408,95 @@ var PLATFORM_VALIDATION_RULES = {
|
|
|
6976
7408
|
notes: "The build plugins guide documents plugin.json, skills/, .mcp.json, .app.json, and assets/ as the standard plugin structure."
|
|
6977
7409
|
},
|
|
6978
7410
|
mcp: {
|
|
6979
|
-
files: [".mcp.json"],
|
|
7411
|
+
files: [".mcp.json", ".codex/config.toml"],
|
|
6980
7412
|
rootKey: "mcpServers",
|
|
6981
|
-
transports: ["stdio", "http"
|
|
6982
|
-
auth: ["
|
|
6983
|
-
notes: "The current build guide documents mcpServers as a path to .mcp.json in the plugin bundle."
|
|
7413
|
+
transports: ["stdio", "streamable-http"],
|
|
7414
|
+
auth: ["bearer token", "OAuth", "header env vars"],
|
|
7415
|
+
notes: "The current build guide documents mcpServers as a path to .mcp.json in the plugin bundle, while active MCP state also lives in config.toml."
|
|
6984
7416
|
},
|
|
6985
7417
|
hooks: {
|
|
6986
7418
|
supported: true,
|
|
6987
7419
|
files: [".codex/hooks.json", "~/.codex/hooks.json"],
|
|
6988
|
-
eventNames: [],
|
|
6989
|
-
notes: "Codex documents hooks in project/user config,
|
|
7420
|
+
eventNames: ["SessionStart", "PreToolUse", "PermissionRequest", "PostToolUse", "UserPromptSubmit", "Stop"],
|
|
7421
|
+
notes: "Codex documents hooks in project/user config, guarded by the codex_hooks feature flag; the current plugin build guide does not document plugin-packaged hooks."
|
|
6990
7422
|
},
|
|
6991
7423
|
instructions: {
|
|
6992
|
-
files: ["AGENTS.md"],
|
|
6993
|
-
format: "markdown"
|
|
7424
|
+
files: ["AGENTS.md", "AGENTS.override.md"],
|
|
7425
|
+
format: "markdown",
|
|
7426
|
+
notes: "Codex also supports model instruction overrides plus configurable fallback filenames for project docs."
|
|
6994
7427
|
},
|
|
6995
7428
|
sources: [
|
|
7429
|
+
{ label: "Codex plugins docs", url: "https://developers.openai.com/codex/plugins" },
|
|
6996
7430
|
{ label: "Codex build plugins docs", url: "https://developers.openai.com/codex/plugins/build" },
|
|
7431
|
+
{ label: "Codex CLI features docs", url: "https://developers.openai.com/codex/cli/features" },
|
|
7432
|
+
{ label: "Codex CLI reference docs", url: "https://developers.openai.com/codex/cli/reference" },
|
|
7433
|
+
{ label: "Codex slash commands docs", url: "https://developers.openai.com/codex/cli/slash-commands" },
|
|
7434
|
+
{ label: "Codex advanced config docs", url: "https://developers.openai.com/codex/config-advanced" },
|
|
7435
|
+
{ label: "Codex rules docs", url: "https://developers.openai.com/codex/rules" },
|
|
6997
7436
|
{ label: "Codex hooks docs", url: "https://developers.openai.com/codex/hooks" },
|
|
6998
7437
|
{ label: "Codex skills docs", url: "https://developers.openai.com/codex/skills" },
|
|
6999
7438
|
{ label: "Codex MCP docs", url: "https://developers.openai.com/codex/mcp" },
|
|
7000
|
-
{ label: "Codex AGENTS.md guide", url: "https://developers.openai.com/codex/guides/agents-md" }
|
|
7439
|
+
{ label: "Codex AGENTS.md guide", url: "https://developers.openai.com/codex/guides/agents-md" },
|
|
7440
|
+
{ label: "Codex subagents docs", url: "https://developers.openai.com/codex/subagents" },
|
|
7441
|
+
{ label: "Codex subagents concept docs", url: "https://developers.openai.com/codex/concepts/subagents" },
|
|
7442
|
+
{ label: "Codex noninteractive docs", url: "https://developers.openai.com/codex/noninteractive" },
|
|
7443
|
+
{ label: "Codex SDK docs", url: "https://developers.openai.com/codex/sdk" },
|
|
7444
|
+
{ label: "Codex agents SDK guide", url: "https://developers.openai.com/codex/guides/agents-sdk" }
|
|
7001
7445
|
]
|
|
7002
7446
|
},
|
|
7003
7447
|
"opencode": {
|
|
7004
7448
|
platform: "opencode",
|
|
7005
|
-
summary: "OpenCode plugins are code-first
|
|
7449
|
+
summary: "OpenCode plugins are code-first JS or TS modules loaded from local plugin dirs or npm references in config, with native skills, commands, agents, MCP, and permission surfaces.",
|
|
7006
7450
|
limits: PLATFORM_LIMITS["opencode"],
|
|
7007
7451
|
limitPolicies: PLATFORM_LIMIT_POLICIES["opencode"],
|
|
7008
7452
|
skillDiscoveryDirs: [
|
|
7009
|
-
{ path: "skills/", level: "supported" }
|
|
7453
|
+
{ path: "skills/", level: "supported" },
|
|
7454
|
+
{ path: ".opencode/skills/", level: "supported" },
|
|
7455
|
+
{ path: "~/.config/opencode/skills/", level: "supported" },
|
|
7456
|
+
{ path: ".claude/skills/", level: "supported", notes: "Compatibility directory" },
|
|
7457
|
+
{ path: ".agents/skills/", level: "supported", notes: "Compatibility directory" }
|
|
7010
7458
|
],
|
|
7011
7459
|
frontmatter: {
|
|
7012
7460
|
standard: [...STANDARD_SKILL_FRONTMATTER],
|
|
7013
|
-
additional: []
|
|
7461
|
+
additional: [],
|
|
7462
|
+
notes: "OpenCode supports Agent Skills semantics, but plugin runtime behavior is code-first rather than manifest-first."
|
|
7014
7463
|
},
|
|
7015
7464
|
manifest: {
|
|
7016
|
-
files: ["
|
|
7017
|
-
required:
|
|
7018
|
-
notes: "OpenCode plugins are loaded as local modules or npm packages rather than a
|
|
7465
|
+
files: ["opencode.json", ".opencode/plugins/", "~/.config/opencode/plugins/"],
|
|
7466
|
+
required: false,
|
|
7467
|
+
notes: "OpenCode plugins are loaded as local modules or npm packages through config rather than a dedicated manifest-only bundle."
|
|
7019
7468
|
},
|
|
7020
7469
|
mcp: {
|
|
7021
|
-
files: ["
|
|
7470
|
+
files: ["opencode.json"],
|
|
7471
|
+
rootKey: "mcp",
|
|
7022
7472
|
transports: ["local", "remote"],
|
|
7023
|
-
auth: ["headers", "
|
|
7024
|
-
notes:
|
|
7473
|
+
auth: ["headers", "env interpolation", "OAuth"],
|
|
7474
|
+
notes: "OpenCode config owns MCP; plugins can also extend runtime behavior programmatically."
|
|
7025
7475
|
},
|
|
7026
7476
|
hooks: {
|
|
7027
7477
|
supported: true,
|
|
7028
|
-
files: ["index.ts"],
|
|
7478
|
+
files: ["plugin module (index.ts/index.js)"],
|
|
7029
7479
|
eventNames: [],
|
|
7030
7480
|
notes: "OpenCode hooks are plugin event handlers implemented in code, not a separate hooks.json file."
|
|
7031
7481
|
},
|
|
7032
7482
|
instructions: {
|
|
7033
|
-
files: ["
|
|
7034
|
-
format: "
|
|
7035
|
-
notes: "
|
|
7483
|
+
files: ["AGENTS.md", "CLAUDE.md", "opencode.json"],
|
|
7484
|
+
format: "markdown + json + code",
|
|
7485
|
+
notes: "OpenCode supports AGENTS.md, CLAUDE.md fallback, config instructions, and plugin runtime instruction injection."
|
|
7036
7486
|
},
|
|
7037
7487
|
sources: [
|
|
7488
|
+
{ label: "OpenCode SDK docs", url: "https://opencode.ai/docs/sdk/" },
|
|
7489
|
+
{ label: "OpenCode server docs", url: "https://opencode.ai/docs/server/" },
|
|
7490
|
+
{ label: "OpenCode config docs", url: "https://opencode.ai/docs/config/" },
|
|
7038
7491
|
{ label: "OpenCode plugins docs", url: "https://opencode.ai/docs/plugins/" },
|
|
7039
7492
|
{ label: "OpenCode skills docs", url: "https://opencode.ai/docs/skills/" },
|
|
7040
|
-
{ label: "OpenCode
|
|
7493
|
+
{ label: "OpenCode commands docs", url: "https://opencode.ai/docs/commands/" },
|
|
7494
|
+
{ label: "OpenCode agents docs", url: "https://opencode.ai/docs/agents/" },
|
|
7495
|
+
{ label: "OpenCode MCP servers docs", url: "https://opencode.ai/docs/mcp-servers/" },
|
|
7496
|
+
{ label: "OpenCode custom tools docs", url: "https://opencode.ai/docs/custom-tools/" },
|
|
7497
|
+
{ label: "OpenCode permissions docs", url: "https://opencode.ai/docs/permissions/" },
|
|
7498
|
+
{ label: "OpenCode rules docs", url: "https://opencode.ai/docs/rules/" },
|
|
7499
|
+
{ label: "OpenCode ACP docs", url: "https://opencode.ai/docs/acp/" }
|
|
7041
7500
|
]
|
|
7042
7501
|
},
|
|
7043
7502
|
"openhands": {
|
|
@@ -7301,7 +7760,8 @@ var CORE_FOUR_PRIMITIVE_CAPABILITIES = {
|
|
|
7301
7760
|
},
|
|
7302
7761
|
commands: {
|
|
7303
7762
|
mode: "preserve",
|
|
7304
|
-
nativeSurfaces: ["commands/*.md"]
|
|
7763
|
+
nativeSurfaces: ["commands/*.md", "skills/<skill>/SKILL.md"],
|
|
7764
|
+
notes: "Claude still supports command files, but the product is increasingly converging command workflows into skills."
|
|
7305
7765
|
},
|
|
7306
7766
|
agents: {
|
|
7307
7767
|
mode: "preserve",
|
|
@@ -7310,7 +7770,7 @@ var CORE_FOUR_PRIMITIVE_CAPABILITIES = {
|
|
|
7310
7770
|
},
|
|
7311
7771
|
hooks: {
|
|
7312
7772
|
mode: "preserve",
|
|
7313
|
-
nativeSurfaces: ["hooks/hooks.json", ".claude-plugin/plugin.json"]
|
|
7773
|
+
nativeSurfaces: ["hooks/hooks.json", ".claude-plugin/plugin.json", "settings hooks", "skill/agent frontmatter hooks"]
|
|
7314
7774
|
},
|
|
7315
7775
|
permissions: {
|
|
7316
7776
|
mode: "translate",
|
|
@@ -7323,8 +7783,8 @@ var CORE_FOUR_PRIMITIVE_CAPABILITIES = {
|
|
|
7323
7783
|
},
|
|
7324
7784
|
distribution: {
|
|
7325
7785
|
mode: "translate",
|
|
7326
|
-
nativeSurfaces: [".claude-plugin/plugin.json", "install scopes", "user configuration"],
|
|
7327
|
-
notes: "Distribution surfaces are native,
|
|
7786
|
+
nativeSurfaces: [".claude-plugin/plugin.json", "marketplaces", "install scopes", "user configuration", "/reload-plugins"],
|
|
7787
|
+
notes: "Distribution surfaces are native, including plugin marketplaces and explicit reload behavior."
|
|
7328
7788
|
}
|
|
7329
7789
|
},
|
|
7330
7790
|
sources: PLATFORM_VALIDATION_RULES["claude-code"].sources
|
|
@@ -7343,7 +7803,7 @@ var CORE_FOUR_PRIMITIVE_CAPABILITIES = {
|
|
|
7343
7803
|
},
|
|
7344
7804
|
commands: {
|
|
7345
7805
|
mode: "preserve",
|
|
7346
|
-
nativeSurfaces: ["commands/*"]
|
|
7806
|
+
nativeSurfaces: ["commands/*", "slash commands"]
|
|
7347
7807
|
},
|
|
7348
7808
|
agents: {
|
|
7349
7809
|
mode: "translate",
|
|
@@ -7361,11 +7821,11 @@ var CORE_FOUR_PRIMITIVE_CAPABILITIES = {
|
|
|
7361
7821
|
},
|
|
7362
7822
|
runtime: {
|
|
7363
7823
|
mode: "preserve",
|
|
7364
|
-
nativeSurfaces: ["mcp.json", ".cursor-plugin/plugin.json", "scripts/", "assets/"]
|
|
7824
|
+
nativeSurfaces: ["mcp.json", ".cursor/mcp.json", "~/.cursor/mcp.json", ".cursor-plugin/plugin.json", "scripts/", "assets/"]
|
|
7365
7825
|
},
|
|
7366
7826
|
distribution: {
|
|
7367
7827
|
mode: "preserve",
|
|
7368
|
-
nativeSurfaces: [".cursor-plugin/plugin.json", ".cursor-plugin/marketplace.json", "local marketplace install path"]
|
|
7828
|
+
nativeSurfaces: [".cursor-plugin/plugin.json", ".cursor-plugin/marketplace.json", "local marketplace install path", "reload window / restart"]
|
|
7369
7829
|
}
|
|
7370
7830
|
},
|
|
7371
7831
|
sources: PLATFORM_VALIDATION_RULES["cursor"].sources
|
|
@@ -7408,7 +7868,7 @@ var CORE_FOUR_PRIMITIVE_CAPABILITIES = {
|
|
|
7408
7868
|
},
|
|
7409
7869
|
distribution: {
|
|
7410
7870
|
mode: "preserve",
|
|
7411
|
-
nativeSurfaces: [".codex-plugin/plugin.json", "~/.agents/plugins/marketplace.json", "$REPO_ROOT/.agents/plugins/marketplace.json"]
|
|
7871
|
+
nativeSurfaces: [".codex-plugin/plugin.json", "~/.agents/plugins/marketplace.json", "$REPO_ROOT/.agents/plugins/marketplace.json", "cache install path", "restart after update"]
|
|
7412
7872
|
}
|
|
7413
7873
|
},
|
|
7414
7874
|
sources: PLATFORM_VALIDATION_RULES["codex"].sources
|
|
@@ -7418,7 +7878,7 @@ var CORE_FOUR_PRIMITIVE_CAPABILITIES = {
|
|
|
7418
7878
|
buckets: {
|
|
7419
7879
|
instructions: {
|
|
7420
7880
|
mode: "translate",
|
|
7421
|
-
nativeSurfaces: ["config instructions", "plugin code"],
|
|
7881
|
+
nativeSurfaces: ["AGENTS.md", "CLAUDE.md", "config instructions", "plugin code"],
|
|
7422
7882
|
notes: "OpenCode instructions are native, but the surface is config- and code-driven rather than manifest markdown only."
|
|
7423
7883
|
},
|
|
7424
7884
|
skills: {
|
|
@@ -7431,7 +7891,8 @@ var CORE_FOUR_PRIMITIVE_CAPABILITIES = {
|
|
|
7431
7891
|
},
|
|
7432
7892
|
agents: {
|
|
7433
7893
|
mode: "preserve",
|
|
7434
|
-
nativeSurfaces: ["agents/*.md", "config agent definitions"]
|
|
7894
|
+
nativeSurfaces: ["agents/*.md", "config agent definitions"],
|
|
7895
|
+
notes: "OpenCode agents are first-class native surfaces. Prefer permission-first agent config for new builds; legacy tools remains compatibility input, not the preferred emitted shape."
|
|
7435
7896
|
},
|
|
7436
7897
|
hooks: {
|
|
7437
7898
|
mode: "translate",
|
|
@@ -7440,21 +7901,25 @@ var CORE_FOUR_PRIMITIVE_CAPABILITIES = {
|
|
|
7440
7901
|
},
|
|
7441
7902
|
permissions: {
|
|
7442
7903
|
mode: "preserve",
|
|
7443
|
-
nativeSurfaces: ["config permission", "per-agent overrides"]
|
|
7904
|
+
nativeSurfaces: ["config permission", "per-agent overrides"],
|
|
7905
|
+
notes: "OpenCode permission is keyed by tool name and patterns, including native skill and task controls. Legacy tools booleans are deprecated in favor of permission."
|
|
7444
7906
|
},
|
|
7445
7907
|
runtime: {
|
|
7446
7908
|
mode: "preserve",
|
|
7447
|
-
nativeSurfaces: ["config mcp", "plugin JS/TS runtime", "scripts/", "assets/"]
|
|
7909
|
+
nativeSurfaces: ["opencode.json", "config mcp", "plugin JS/TS runtime", "scripts/", "assets/"]
|
|
7448
7910
|
},
|
|
7449
7911
|
distribution: {
|
|
7450
7912
|
mode: "translate",
|
|
7451
|
-
nativeSurfaces: ["
|
|
7913
|
+
nativeSurfaces: [".opencode/plugins/", "~/.config/opencode/plugins/", "opencode.json", "npm package", "plugin JS/TS entrypoint"],
|
|
7452
7914
|
notes: "Distribution is native, but there is no single shared manifest analog to Claude, Cursor, or Codex."
|
|
7453
7915
|
}
|
|
7454
7916
|
},
|
|
7455
7917
|
sources: PLATFORM_VALIDATION_RULES["opencode"].sources
|
|
7456
7918
|
}
|
|
7457
7919
|
};
|
|
7920
|
+
function getPlatformRules(platform) {
|
|
7921
|
+
return PLATFORM_VALIDATION_RULES[platform];
|
|
7922
|
+
}
|
|
7458
7923
|
function getCoreFourPrimitiveCapabilities(platform) {
|
|
7459
7924
|
return CORE_FOUR_PRIMITIVE_CAPABILITIES[platform];
|
|
7460
7925
|
}
|
|
@@ -7520,6 +7985,21 @@ function renderPrimitiveTranslationSummary(summary) {
|
|
|
7520
7985
|
lines.push(` ${row.bucket.padEnd(bucketWidth, " ")} ${cells.join(" ")}`);
|
|
7521
7986
|
}
|
|
7522
7987
|
lines.push(" legend: keep=preserve xlat=translate weak=degrade drop=drop");
|
|
7988
|
+
const detailLines = [];
|
|
7989
|
+
for (const row of summary.rows) {
|
|
7990
|
+
for (const target of summary.targets) {
|
|
7991
|
+
const mode = row.modes[target];
|
|
7992
|
+
if (!mode || mode === "preserve") continue;
|
|
7993
|
+
const capability = getCoreFourPrimitiveCapabilities(target).buckets[row.bucket];
|
|
7994
|
+
const verb = mode === "translate" ? "re-expressed via" : mode === "degrade" ? "weakened to" : "omitted; nearest surface would be";
|
|
7995
|
+
const suffix = capability.notes ? ` ${capability.notes}` : "";
|
|
7996
|
+
detailLines.push(` - ${row.bucket} on ${TARGET_LABELS[target]}: ${verb} ${capability.nativeSurfaces.join(", ")}.${suffix}`);
|
|
7997
|
+
}
|
|
7998
|
+
}
|
|
7999
|
+
if (detailLines.length > 0) {
|
|
8000
|
+
lines.push(" details:");
|
|
8001
|
+
lines.push(...detailLines);
|
|
8002
|
+
}
|
|
7523
8003
|
return lines;
|
|
7524
8004
|
}
|
|
7525
8005
|
|
|
@@ -7710,16 +8190,27 @@ function lintSkillFile(skillFile, targets, issues, frontmatterCache) {
|
|
|
7710
8190
|
platform: "Agent Skills"
|
|
7711
8191
|
});
|
|
7712
8192
|
}
|
|
7713
|
-
const expectedDirName =
|
|
8193
|
+
const expectedDirName = basename4(dirname3(skillFile));
|
|
7714
8194
|
const platformsRequiringDirMatch = targets.filter((t2) => PLATFORM_LIMITS[t2].skillNameMustMatchDir);
|
|
7715
|
-
|
|
7716
|
-
|
|
8195
|
+
const hardDirMatchPlatforms = platformsRequiringDirMatch.filter((target) => PLATFORM_LIMIT_POLICIES[target].skillNameMustMatchDir.kind === "hard");
|
|
8196
|
+
const advisoryDirMatchPlatforms = platformsRequiringDirMatch.filter((target) => PLATFORM_LIMIT_POLICIES[target].skillNameMustMatchDir.kind !== "hard");
|
|
8197
|
+
if (hardDirMatchPlatforms.length > 0 && nameField.value !== expectedDirName) {
|
|
8198
|
+
const platformNames = hardDirMatchPlatforms.join(", ");
|
|
7717
8199
|
pushIssue(issues, {
|
|
7718
8200
|
level: "error",
|
|
7719
8201
|
code: "skill-name-dir-mismatch",
|
|
7720
8202
|
message: `Skill name "${nameField.value}" must match directory name "${expectedDirName}" (required by ${platformNames}).`,
|
|
7721
8203
|
file: skillFile,
|
|
7722
|
-
platform:
|
|
8204
|
+
platform: hardDirMatchPlatforms[0]
|
|
8205
|
+
});
|
|
8206
|
+
} else if (advisoryDirMatchPlatforms.length > 0 && nameField.value !== expectedDirName) {
|
|
8207
|
+
const platformNames = advisoryDirMatchPlatforms.join(", ");
|
|
8208
|
+
pushIssue(issues, {
|
|
8209
|
+
level: "warning",
|
|
8210
|
+
code: "skill-name-dir-guideline",
|
|
8211
|
+
message: `Skill name "${nameField.value}" should match directory name "${expectedDirName}" for ${platformNames} compatibility.`,
|
|
8212
|
+
file: skillFile,
|
|
8213
|
+
platform: advisoryDirMatchPlatforms[0]
|
|
7723
8214
|
});
|
|
7724
8215
|
}
|
|
7725
8216
|
if (!nameField.quoted && needsQuotes(nameField.rawValue)) {
|
|
@@ -7744,10 +8235,11 @@ function lintSkillFile(skillFile, targets, issues, frontmatterCache) {
|
|
|
7744
8235
|
for (const target of targets) {
|
|
7745
8236
|
const limits = PLATFORM_LIMITS[target];
|
|
7746
8237
|
if (limits.skillDescriptionMax !== null && descriptionField.value.length > limits.skillDescriptionMax) {
|
|
8238
|
+
const policy = PLATFORM_LIMIT_POLICIES[target].skillDescriptionMax;
|
|
7747
8239
|
pushIssue(issues, {
|
|
7748
|
-
level: "error",
|
|
7749
|
-
code: "skill-description-length",
|
|
7750
|
-
message: `Description exceeds ${target} max of ${limits.skillDescriptionMax} characters.`,
|
|
8240
|
+
level: policy?.kind === "hard" ? "error" : "warning",
|
|
8241
|
+
code: policy?.kind === "hard" ? "skill-description-length" : "skill-description-guideline",
|
|
8242
|
+
message: policy?.kind === "hard" ? `Description exceeds ${target} max of ${limits.skillDescriptionMax} characters.` : `Description exceeds the Pluxx ${target} compatibility guideline of ${limits.skillDescriptionMax} characters.`,
|
|
7751
8243
|
file: skillFile,
|
|
7752
8244
|
platform: target
|
|
7753
8245
|
});
|
|
@@ -7946,11 +8438,47 @@ function lintMcpUrls(config, issues) {
|
|
|
7946
8438
|
platform: "MCP"
|
|
7947
8439
|
});
|
|
7948
8440
|
}
|
|
7949
|
-
} catch {
|
|
8441
|
+
} catch {
|
|
8442
|
+
pushIssue(issues, {
|
|
8443
|
+
level: "error",
|
|
8444
|
+
code: "mcp-url-invalid",
|
|
8445
|
+
message: `MCP server "${serverName}" has an invalid URL.`,
|
|
8446
|
+
file: "pluxx.config.ts",
|
|
8447
|
+
platform: "MCP"
|
|
8448
|
+
});
|
|
8449
|
+
}
|
|
8450
|
+
}
|
|
8451
|
+
}
|
|
8452
|
+
function lintMcpRuntimeState(config, issues) {
|
|
8453
|
+
if (!config.mcp) return;
|
|
8454
|
+
const claudeUsesPlatformAuth = config.targets.includes("claude-code") && config.platforms?.["claude-code"]?.mcpAuth === "platform";
|
|
8455
|
+
const cursorUsesPlatformAuth = config.targets.includes("cursor") && config.platforms?.cursor?.mcpAuth === "platform";
|
|
8456
|
+
for (const [serverName, server] of Object.entries(config.mcp)) {
|
|
8457
|
+
if (server.transport === "stdio") {
|
|
8458
|
+
pushIssue(issues, {
|
|
8459
|
+
level: "warning",
|
|
8460
|
+
code: "mcp-stdio-runtime-dependency",
|
|
8461
|
+
message: `MCP server "${serverName}" runs through a local stdio command. End users still need that command and its runtime dependencies available after install.`,
|
|
8462
|
+
file: "pluxx.config.ts",
|
|
8463
|
+
platform: "MCP"
|
|
8464
|
+
});
|
|
8465
|
+
}
|
|
8466
|
+
const runtimeAuthTargets = [];
|
|
8467
|
+
if (server.auth?.type === "platform") {
|
|
8468
|
+
for (const target of config.targets) {
|
|
8469
|
+
if (target === "claude-code" || target === "cursor" || target === "codex" || target === "opencode") {
|
|
8470
|
+
runtimeAuthTargets.push(target);
|
|
8471
|
+
}
|
|
8472
|
+
}
|
|
8473
|
+
} else {
|
|
8474
|
+
if (claudeUsesPlatformAuth) runtimeAuthTargets.push("claude-code");
|
|
8475
|
+
if (cursorUsesPlatformAuth) runtimeAuthTargets.push("cursor");
|
|
8476
|
+
}
|
|
8477
|
+
if (runtimeAuthTargets.length > 0) {
|
|
7950
8478
|
pushIssue(issues, {
|
|
7951
|
-
level: "
|
|
7952
|
-
code: "mcp-
|
|
7953
|
-
message: `MCP server "${serverName}"
|
|
8479
|
+
level: "warning",
|
|
8480
|
+
code: "mcp-runtime-auth-external",
|
|
8481
|
+
message: `MCP server "${serverName}" depends on host-managed auth or runtime config on ${runtimeAuthTargets.join(", ")}. Verify the installed bundle in the real host instead of assuming the bundle alone materializes auth.`,
|
|
7954
8482
|
file: "pluxx.config.ts",
|
|
7955
8483
|
platform: "MCP"
|
|
7956
8484
|
});
|
|
@@ -7979,10 +8507,11 @@ function lintManifestPromptLimits(config, issues) {
|
|
|
7979
8507
|
const prompts = config.brand?.defaultPrompts;
|
|
7980
8508
|
if (!prompts) continue;
|
|
7981
8509
|
if (limits.manifestPromptCountMax !== null && prompts.length > limits.manifestPromptCountMax) {
|
|
8510
|
+
const policy = PLATFORM_LIMIT_POLICIES[target].manifestPromptCountMax;
|
|
7982
8511
|
pushIssue(issues, {
|
|
7983
|
-
level: "error",
|
|
7984
|
-
code: "platform-prompt-count",
|
|
7985
|
-
message: `${target} supports at most ${limits.manifestPromptCountMax} default prompts (found ${prompts.length}).`,
|
|
8512
|
+
level: policy?.kind === "hard" ? "error" : "warning",
|
|
8513
|
+
code: policy?.kind === "hard" ? "platform-prompt-count" : "platform-prompt-count-guideline",
|
|
8514
|
+
message: policy?.kind === "hard" ? `${target} supports at most ${limits.manifestPromptCountMax} default prompts (found ${prompts.length}).` : `Pluxx recommends keeping ${target} default prompts to ${limits.manifestPromptCountMax} or fewer (found ${prompts.length}).`,
|
|
7986
8515
|
file: "pluxx.config.ts",
|
|
7987
8516
|
platform: target
|
|
7988
8517
|
});
|
|
@@ -7990,10 +8519,11 @@ function lintManifestPromptLimits(config, issues) {
|
|
|
7990
8519
|
if (limits.manifestPromptMax !== null) {
|
|
7991
8520
|
for (const prompt of prompts) {
|
|
7992
8521
|
if (prompt.length > limits.manifestPromptMax) {
|
|
8522
|
+
const policy = PLATFORM_LIMIT_POLICIES[target].manifestPromptMax;
|
|
7993
8523
|
pushIssue(issues, {
|
|
7994
|
-
level: "error",
|
|
7995
|
-
code: "platform-prompt-length",
|
|
7996
|
-
message: `A default prompt exceeds ${target} max of ${limits.manifestPromptMax} characters.`,
|
|
8524
|
+
level: policy?.kind === "hard" ? "error" : "warning",
|
|
8525
|
+
code: policy?.kind === "hard" ? "platform-prompt-length" : "platform-prompt-length-guideline",
|
|
8526
|
+
message: policy?.kind === "hard" ? `A default prompt exceeds ${target} max of ${limits.manifestPromptMax} characters.` : `A default prompt exceeds the Pluxx ${target} compatibility guideline of ${limits.manifestPromptMax} characters.`,
|
|
7997
8527
|
file: "pluxx.config.ts",
|
|
7998
8528
|
platform: target
|
|
7999
8529
|
});
|
|
@@ -8183,6 +8713,20 @@ function lintAgentIsolation(agentFiles, issues, frontmatterCache) {
|
|
|
8183
8713
|
}
|
|
8184
8714
|
}
|
|
8185
8715
|
}
|
|
8716
|
+
function lintOpenCodeAgentFrontmatter(dir, config, issues) {
|
|
8717
|
+
if (!config.targets.includes("opencode") || !config.agents) return;
|
|
8718
|
+
const agents = readCanonicalAgentFiles(resolve9(dir, config.agents));
|
|
8719
|
+
for (const agent of agents) {
|
|
8720
|
+
if (!("tools" in agent.frontmatter)) continue;
|
|
8721
|
+
pushIssue(issues, {
|
|
8722
|
+
level: "warning",
|
|
8723
|
+
code: "opencode-agent-tools-deprecated",
|
|
8724
|
+
message: "OpenCode agent `tools` is deprecated. Pluxx will translate legacy agent tools into permission-first OpenCode output where possible, but canonical agents should prefer `permission`.",
|
|
8725
|
+
file: relative6(dir, agent.filePath).replace(/\\/g, "/"),
|
|
8726
|
+
platform: "OpenCode"
|
|
8727
|
+
});
|
|
8728
|
+
}
|
|
8729
|
+
}
|
|
8186
8730
|
function lintAbsolutePaths(config, issues) {
|
|
8187
8731
|
const absolutePathPattern = /^\/[a-zA-Z]|^[A-Z]:\\/;
|
|
8188
8732
|
if (config.hooks) {
|
|
@@ -8324,24 +8868,130 @@ function lintCursorHooks(config, issues) {
|
|
|
8324
8868
|
}
|
|
8325
8869
|
}
|
|
8326
8870
|
function lintCursorSkillFrontmatter(config, skillFiles, issues, frontmatterCache) {
|
|
8327
|
-
|
|
8328
|
-
|
|
8871
|
+
const supportedByTarget = new Map(
|
|
8872
|
+
["cursor", "codex", "opencode"].filter((target) => config.targets.includes(target)).map((target) => {
|
|
8873
|
+
const rules = getPlatformRules(target);
|
|
8874
|
+
return [target, /* @__PURE__ */ new Set([...rules.frontmatter.standard, ...rules.frontmatter.additional])];
|
|
8875
|
+
})
|
|
8876
|
+
);
|
|
8877
|
+
if (supportedByTarget.size === 0) return;
|
|
8329
8878
|
for (const skillFile of skillFiles) {
|
|
8330
8879
|
const { parsed } = getParsedFrontmatterFile(skillFile, frontmatterCache);
|
|
8331
8880
|
if (!parsed.valid) continue;
|
|
8332
8881
|
for (const [key] of parsed.fields) {
|
|
8333
|
-
|
|
8882
|
+
for (const [target, supported] of supportedByTarget.entries()) {
|
|
8883
|
+
if (supported.has(key)) continue;
|
|
8884
|
+
const issue = target === "cursor" ? {
|
|
8885
|
+
code: "cursor-skill-frontmatter-unsupported",
|
|
8886
|
+
message: `Skill frontmatter field "${key}" is not supported by Cursor. Supported: ${[...supported].join(", ")}`,
|
|
8887
|
+
platform: "Cursor"
|
|
8888
|
+
} : target === "codex" ? {
|
|
8889
|
+
code: "codex-skill-frontmatter-translation",
|
|
8890
|
+
message: `Skill frontmatter field "${key}" is not part of documented Codex skill frontmatter. Pluxx may need to translate that intent through AGENTS.md, .codex/agents/*.toml, permissions companions, or runtime config instead of preserving it on SKILL.md.`,
|
|
8891
|
+
platform: "Codex"
|
|
8892
|
+
} : {
|
|
8893
|
+
code: "opencode-skill-frontmatter-translation",
|
|
8894
|
+
message: `Skill frontmatter field "${key}" is not part of documented OpenCode skill frontmatter. Pluxx may need to translate that intent through commands, agents, opencode.json, or plugin runtime code instead of preserving it on SKILL.md.`,
|
|
8895
|
+
platform: "OpenCode"
|
|
8896
|
+
};
|
|
8334
8897
|
pushIssue(issues, {
|
|
8335
8898
|
level: "warning",
|
|
8336
|
-
code: "cursor-skill-frontmatter-unsupported",
|
|
8337
|
-
message: `Skill frontmatter field "${key}" is not supported by Cursor. Supported: ${cursorSupportedFrontmatter.join(", ")}`,
|
|
8338
8899
|
file: skillFile,
|
|
8339
|
-
|
|
8900
|
+
...issue
|
|
8340
8901
|
});
|
|
8341
8902
|
}
|
|
8342
8903
|
}
|
|
8343
8904
|
}
|
|
8344
8905
|
}
|
|
8906
|
+
function lintHookFieldTranslations(config, issues) {
|
|
8907
|
+
if (!config.hooks) return;
|
|
8908
|
+
const hasPromptHooks = Object.values(config.hooks).some(
|
|
8909
|
+
(entries) => (entries ?? []).some((entry) => entry.type === "prompt")
|
|
8910
|
+
);
|
|
8911
|
+
const hasFailClosed = Object.values(config.hooks).some(
|
|
8912
|
+
(entries) => (entries ?? []).some((entry) => entry.failClosed !== void 0)
|
|
8913
|
+
);
|
|
8914
|
+
const hasLoopLimit = Object.values(config.hooks).some(
|
|
8915
|
+
(entries) => (entries ?? []).some((entry) => entry.loop_limit !== void 0)
|
|
8916
|
+
);
|
|
8917
|
+
if (hasPromptHooks) {
|
|
8918
|
+
if (config.targets.includes("claude-code")) {
|
|
8919
|
+
pushIssue(issues, {
|
|
8920
|
+
level: "warning",
|
|
8921
|
+
code: "claude-prompt-hook-degrade",
|
|
8922
|
+
message: "Prompt hooks are documented in Claude-native hook surfaces, but the current Claude-family generator still drops prompt hooks. Expect a degraded result unless you remodel them as command hooks or host-specific manual work.",
|
|
8923
|
+
file: "pluxx.config.ts",
|
|
8924
|
+
platform: "claude-code"
|
|
8925
|
+
});
|
|
8926
|
+
}
|
|
8927
|
+
if (config.targets.includes("codex")) {
|
|
8928
|
+
pushIssue(issues, {
|
|
8929
|
+
level: "warning",
|
|
8930
|
+
code: "codex-prompt-hook-drop",
|
|
8931
|
+
message: "Codex currently receives only command-hook companions from Pluxx. Prompt hooks will be dropped from the generated Codex bundle.",
|
|
8932
|
+
file: "pluxx.config.ts",
|
|
8933
|
+
platform: "codex"
|
|
8934
|
+
});
|
|
8935
|
+
}
|
|
8936
|
+
if (config.targets.includes("opencode")) {
|
|
8937
|
+
pushIssue(issues, {
|
|
8938
|
+
level: "warning",
|
|
8939
|
+
code: "opencode-prompt-hook-drop",
|
|
8940
|
+
message: "The current OpenCode runtime wrapper only emits command hooks. Prompt hooks will be dropped from the generated OpenCode plugin.",
|
|
8941
|
+
file: "pluxx.config.ts",
|
|
8942
|
+
platform: "opencode"
|
|
8943
|
+
});
|
|
8944
|
+
}
|
|
8945
|
+
}
|
|
8946
|
+
if (hasFailClosed && config.targets.includes("claude-code")) {
|
|
8947
|
+
pushIssue(issues, {
|
|
8948
|
+
level: "warning",
|
|
8949
|
+
code: "claude-hook-failclosed-degrade",
|
|
8950
|
+
message: "Claude hook entries currently drop `failClosed` in generated output. Keep this behavior host-specific or verify the generated hook bundle carefully.",
|
|
8951
|
+
file: "pluxx.config.ts",
|
|
8952
|
+
platform: "claude-code"
|
|
8953
|
+
});
|
|
8954
|
+
}
|
|
8955
|
+
if (hasLoopLimit) {
|
|
8956
|
+
if (config.targets.includes("claude-code")) {
|
|
8957
|
+
pushIssue(issues, {
|
|
8958
|
+
level: "warning",
|
|
8959
|
+
code: "claude-hook-loop-limit-degrade",
|
|
8960
|
+
message: "Claude outputs currently drop `loop_limit`. Recursive hook protection is not preserved there today.",
|
|
8961
|
+
file: "pluxx.config.ts",
|
|
8962
|
+
platform: "claude-code"
|
|
8963
|
+
});
|
|
8964
|
+
}
|
|
8965
|
+
if (config.targets.includes("codex")) {
|
|
8966
|
+
pushIssue(issues, {
|
|
8967
|
+
level: "warning",
|
|
8968
|
+
code: "codex-hook-loop-limit-drop",
|
|
8969
|
+
message: "Codex hook companions currently drop `loop_limit`. Only command, matcher, timeout, and failClosed survive there today.",
|
|
8970
|
+
file: "pluxx.config.ts",
|
|
8971
|
+
platform: "codex"
|
|
8972
|
+
});
|
|
8973
|
+
}
|
|
8974
|
+
if (config.targets.includes("opencode")) {
|
|
8975
|
+
pushIssue(issues, {
|
|
8976
|
+
level: "warning",
|
|
8977
|
+
code: "opencode-hook-loop-limit-drop",
|
|
8978
|
+
message: "OpenCode runtime hooks currently drop `loop_limit`. Recursive hook protection is still Cursor-first in Pluxx.",
|
|
8979
|
+
file: "pluxx.config.ts",
|
|
8980
|
+
platform: "opencode"
|
|
8981
|
+
});
|
|
8982
|
+
}
|
|
8983
|
+
}
|
|
8984
|
+
}
|
|
8985
|
+
function lintCodexCommandGuidance(config, issues) {
|
|
8986
|
+
if (!config.targets.includes("codex") || !config.commands) return;
|
|
8987
|
+
pushIssue(issues, {
|
|
8988
|
+
level: "warning",
|
|
8989
|
+
code: "codex-commands-routing-guidance",
|
|
8990
|
+
message: "Codex does not currently document plugin-packaged slash-command parity. Pluxx will degrade commands into skills plus AGENTS.md and `.codex/commands.generated.json` routing guidance.",
|
|
8991
|
+
file: "pluxx.config.ts",
|
|
8992
|
+
platform: "codex"
|
|
8993
|
+
});
|
|
8994
|
+
}
|
|
8345
8995
|
function lintSkillListingBudgets(skillFiles, targets, issues, frontmatterCache) {
|
|
8346
8996
|
for (const target of targets) {
|
|
8347
8997
|
const budget = PLATFORM_LIMITS[target].skillListingBudgetMax;
|
|
@@ -8428,12 +9078,12 @@ function lintPermissions(config, issues) {
|
|
|
8428
9078
|
});
|
|
8429
9079
|
}
|
|
8430
9080
|
if (rules.some((rule) => rule.kind === "Skill")) {
|
|
8431
|
-
const
|
|
8432
|
-
if (
|
|
9081
|
+
const limitedTargets = config.targets.filter((target) => !["claude-code", "codex", "opencode"].includes(target));
|
|
9082
|
+
if (limitedTargets.length > 0) {
|
|
8433
9083
|
pushIssue(issues, {
|
|
8434
9084
|
level: "warning",
|
|
8435
9085
|
code: "permissions-skill-selector-limited",
|
|
8436
|
-
message: `Skill(...) permission rules
|
|
9086
|
+
message: `Skill(...) permission rules do not have the same native support on ${limitedTargets.join(", ")} and will require downgrade or translation there.`,
|
|
8437
9087
|
file: "pluxx.config.ts",
|
|
8438
9088
|
platform: "Permissions"
|
|
8439
9089
|
});
|
|
@@ -8443,7 +9093,7 @@ function lintPermissions(config, issues) {
|
|
|
8443
9093
|
pushIssue(issues, {
|
|
8444
9094
|
level: "warning",
|
|
8445
9095
|
code: "permissions-opencode-downgrade",
|
|
8446
|
-
message: "OpenCode
|
|
9096
|
+
message: "OpenCode now preserves most canonical permission selectors natively, but MCP(...) rules still translate through OpenCode tool-name patterns and should be verified against the configured MCP server names.",
|
|
8447
9097
|
file: "pluxx.config.ts",
|
|
8448
9098
|
platform: "OpenCode"
|
|
8449
9099
|
});
|
|
@@ -8513,11 +9163,13 @@ async function lintProject(dir = process.cwd(), options = {}) {
|
|
|
8513
9163
|
lintSettingsJson(dir, issues);
|
|
8514
9164
|
lintLegacyCommandsDir(dir, lintConfig, issues);
|
|
8515
9165
|
lintHookEvents(lintConfig, issues);
|
|
8516
|
-
const agentsDir = resolve9(dir, "agents");
|
|
9166
|
+
const agentsDir = resolve9(dir, lintConfig.agents ?? "agents");
|
|
8517
9167
|
const agentFiles = existsSync17(agentsDir) ? collectMarkdownFiles(agentsDir) : [];
|
|
8518
9168
|
lintAgentFrontmatter(agentFiles, issues, frontmatterCache);
|
|
8519
9169
|
lintAgentIsolation(agentFiles, issues, frontmatterCache);
|
|
9170
|
+
lintOpenCodeAgentFrontmatter(dir, { ...lintConfig, agents: lintConfig.agents ?? "./agents/" }, issues);
|
|
8520
9171
|
lintMcpUrls(lintConfig, issues);
|
|
9172
|
+
lintMcpRuntimeState(lintConfig, issues);
|
|
8521
9173
|
lintBrandMetadata(lintConfig, issues);
|
|
8522
9174
|
lintCodexOverrides(lintConfig, issues);
|
|
8523
9175
|
lintCodexHookCompatibility(lintConfig, issues);
|
|
@@ -8525,6 +9177,8 @@ async function lintProject(dir = process.cwd(), options = {}) {
|
|
|
8525
9177
|
lintCodexHooksExternalConfig(lintConfig, issues);
|
|
8526
9178
|
lintPermissions(lintConfig, issues);
|
|
8527
9179
|
lintPrimitiveTranslations(lintConfig, issues);
|
|
9180
|
+
lintHookFieldTranslations(lintConfig, issues);
|
|
9181
|
+
lintCodexCommandGuidance(lintConfig, issues);
|
|
8528
9182
|
lintCursorHooks(lintConfig, issues);
|
|
8529
9183
|
lintCursorRuleContentLimits(lintConfig, issues);
|
|
8530
9184
|
const skillsDir = resolve9(dir, lintConfig.skills);
|
|
@@ -8608,7 +9262,7 @@ import { resolve as resolve11 } from "path";
|
|
|
8608
9262
|
|
|
8609
9263
|
// src/cli/init-from-mcp.ts
|
|
8610
9264
|
import { mkdir as mkdir2 } from "fs/promises";
|
|
8611
|
-
import { basename as
|
|
9265
|
+
import { basename as basename5, resolve as resolve10 } from "path";
|
|
8612
9266
|
|
|
8613
9267
|
// src/user-config.ts
|
|
8614
9268
|
var ENV_VAR_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
@@ -9188,7 +9842,7 @@ function buildCommandContent(skill, existingContent) {
|
|
|
9188
9842
|
const generatedContent = [
|
|
9189
9843
|
"---",
|
|
9190
9844
|
`description: ${JSON.stringify(description)}`,
|
|
9191
|
-
`argument-hint: ${
|
|
9845
|
+
`argument-hint: ${formatArgumentHintFrontmatter(argumentHint)}`,
|
|
9192
9846
|
"---",
|
|
9193
9847
|
"",
|
|
9194
9848
|
entryBlurb,
|
|
@@ -9225,6 +9879,14 @@ function buildCommandContent(skill, existingContent) {
|
|
|
9225
9879
|
}
|
|
9226
9880
|
);
|
|
9227
9881
|
}
|
|
9882
|
+
function formatArgumentHintFrontmatter(value) {
|
|
9883
|
+
const trimmed = value.trim();
|
|
9884
|
+
if (!trimmed) return '""';
|
|
9885
|
+
if (trimmed.includes("\n") || /(^#)|(\s#)/.test(trimmed)) {
|
|
9886
|
+
return JSON.stringify(trimmed);
|
|
9887
|
+
}
|
|
9888
|
+
return trimmed;
|
|
9889
|
+
}
|
|
9228
9890
|
function buildInstructionsContent(input) {
|
|
9229
9891
|
const accessLine = describePluginAccess(input.displayName, input.source, input.runtimeAuthMode ?? "inline");
|
|
9230
9892
|
const lines = [
|
|
@@ -10334,7 +10996,7 @@ function derivePluginName(introspection, source) {
|
|
|
10334
10996
|
const candidates = [
|
|
10335
10997
|
introspection.serverInfo.name,
|
|
10336
10998
|
introspection.serverInfo.title,
|
|
10337
|
-
source.transport === "stdio" ?
|
|
10999
|
+
source.transport === "stdio" ? basename5(source.command) : new URL(source.url).hostname.split(".")[0]
|
|
10338
11000
|
].filter((value) => Boolean(value));
|
|
10339
11001
|
for (const candidate of candidates) {
|
|
10340
11002
|
const normalized = toKebabCase(candidate);
|
|
@@ -10855,7 +11517,7 @@ function printTestResult(result) {
|
|
|
10855
11517
|
}
|
|
10856
11518
|
|
|
10857
11519
|
// src/cli/sync-from-mcp.ts
|
|
10858
|
-
import { cpSync as cpSync2, existsSync as existsSync20, mkdtempSync, readFileSync as readFileSync8, rmSync as rmSync2, readdirSync as readdirSync6, rmdirSync, writeFileSync as
|
|
11520
|
+
import { cpSync as cpSync2, existsSync as existsSync20, mkdtempSync, readFileSync as readFileSync8, rmSync as rmSync2, readdirSync as readdirSync6, rmdirSync, writeFileSync as writeFileSync3 } from "fs";
|
|
10859
11521
|
import { dirname as dirname4, isAbsolute, relative as relative7, resolve as resolve13 } from "path";
|
|
10860
11522
|
import { tmpdir } from "os";
|
|
10861
11523
|
|
|
@@ -11138,10 +11800,10 @@ async function createSseClient(server) {
|
|
|
11138
11800
|
let resolveEndpoint;
|
|
11139
11801
|
let rejectEndpoint;
|
|
11140
11802
|
let endpointSettled = false;
|
|
11141
|
-
const endpointReady = new Promise((
|
|
11803
|
+
const endpointReady = new Promise((resolve24, reject) => {
|
|
11142
11804
|
resolveEndpoint = (value) => {
|
|
11143
11805
|
endpointSettled = true;
|
|
11144
|
-
|
|
11806
|
+
resolve24(value);
|
|
11145
11807
|
};
|
|
11146
11808
|
rejectEndpoint = (error) => {
|
|
11147
11809
|
endpointSettled = true;
|
|
@@ -11278,7 +11940,7 @@ async function createSseClient(server) {
|
|
|
11278
11940
|
async request(method, params) {
|
|
11279
11941
|
const requestId = nextRequestId();
|
|
11280
11942
|
const endpoint = endpointUrl ?? await endpointReady;
|
|
11281
|
-
const resultPromise = new Promise((
|
|
11943
|
+
const resultPromise = new Promise((resolve24, reject) => {
|
|
11282
11944
|
const timeout = setTimeout(() => {
|
|
11283
11945
|
pending.delete(requestId);
|
|
11284
11946
|
reject(new McpIntrospectionError(`Timed out waiting for MCP SSE response to ${method}.`));
|
|
@@ -11286,7 +11948,7 @@ async function createSseClient(server) {
|
|
|
11286
11948
|
pending.set(requestId, {
|
|
11287
11949
|
resolve: (value) => {
|
|
11288
11950
|
clearTimeout(timeout);
|
|
11289
|
-
|
|
11951
|
+
resolve24(value);
|
|
11290
11952
|
},
|
|
11291
11953
|
reject: (error) => {
|
|
11292
11954
|
clearTimeout(timeout);
|
|
@@ -11439,7 +12101,7 @@ async function createStdioClient(server) {
|
|
|
11439
12101
|
method,
|
|
11440
12102
|
...params ? { params } : {}
|
|
11441
12103
|
});
|
|
11442
|
-
return new Promise((
|
|
12104
|
+
return new Promise((resolve24, reject) => {
|
|
11443
12105
|
const timeout = setTimeout(() => {
|
|
11444
12106
|
pending.delete(id);
|
|
11445
12107
|
reject(new McpIntrospectionError(`Timed out waiting for MCP stdio response to ${method}.`));
|
|
@@ -11447,7 +12109,7 @@ async function createStdioClient(server) {
|
|
|
11447
12109
|
pending.set(id, {
|
|
11448
12110
|
resolve: (value) => {
|
|
11449
12111
|
clearTimeout(timeout);
|
|
11450
|
-
|
|
12112
|
+
resolve24(value);
|
|
11451
12113
|
},
|
|
11452
12114
|
reject: (error) => {
|
|
11453
12115
|
clearTimeout(timeout);
|
|
@@ -11670,7 +12332,7 @@ async function syncFromMcp(options) {
|
|
|
11670
12332
|
if (!existsSync20(newSkillPath)) continue;
|
|
11671
12333
|
const currentContent = readFileSync8(newSkillPath, "utf-8");
|
|
11672
12334
|
const updatedContent = injectCustomContent(currentContent, extracted.customContent);
|
|
11673
|
-
|
|
12335
|
+
writeFileSync3(newSkillPath, updatedContent, "utf-8");
|
|
11674
12336
|
}
|
|
11675
12337
|
const renamedFiles = [];
|
|
11676
12338
|
const renamedOldDirs = /* @__PURE__ */ new Set();
|
|
@@ -11754,7 +12416,7 @@ async function applyPersistedTaxonomy(rootDir) {
|
|
|
11754
12416
|
const instructionsPath = "./INSTRUCTIONS.md";
|
|
11755
12417
|
const previousInstructions = beforeContents.get(instructionsPath);
|
|
11756
12418
|
if (previousInstructions !== void 0) {
|
|
11757
|
-
|
|
12419
|
+
writeFileSync3(resolveWithinRoot(rootDir, instructionsPath), previousInstructions, "utf-8");
|
|
11758
12420
|
}
|
|
11759
12421
|
const newMetadataPath = resolveWithinRoot(rootDir, MCP_SCAFFOLD_METADATA_PATH);
|
|
11760
12422
|
const newMetadata = JSON.parse(readFileSync8(newMetadataPath, "utf-8"));
|
|
@@ -11779,7 +12441,7 @@ async function applyPersistedTaxonomy(rootDir) {
|
|
|
11779
12441
|
}
|
|
11780
12442
|
for (const file of afterManaged) {
|
|
11781
12443
|
if (file === instructionsPath && previousInstructions !== void 0) {
|
|
11782
|
-
|
|
12444
|
+
writeFileSync3(resolveWithinRoot(rootDir, file), previousInstructions, "utf-8");
|
|
11783
12445
|
}
|
|
11784
12446
|
}
|
|
11785
12447
|
invalidateSavedAgentPack(rootDir);
|
|
@@ -11820,7 +12482,7 @@ function preserveCustomContentForRenames(rootDir, renames, pathForName) {
|
|
|
11820
12482
|
if (!existsSync20(newPath)) continue;
|
|
11821
12483
|
const currentContent = readFileSync8(newPath, "utf-8");
|
|
11822
12484
|
const updatedContent = injectCustomContent(currentContent, extracted.customContent);
|
|
11823
|
-
|
|
12485
|
+
writeFileSync3(newPath, updatedContent, "utf-8");
|
|
11824
12486
|
}
|
|
11825
12487
|
}
|
|
11826
12488
|
function snapshotManagedFiles(rootDir, files) {
|
|
@@ -13073,6 +13735,9 @@ function scoreFirecrawlMappedLink(link, kind) {
|
|
|
13073
13735
|
const url = link.url.toLowerCase();
|
|
13074
13736
|
const text = [link.title, link.description, link.url].filter(Boolean).join(" ").toLowerCase();
|
|
13075
13737
|
let score = 0;
|
|
13738
|
+
if (url.endsWith(".xml") || text.includes("sitemap")) {
|
|
13739
|
+
return -100;
|
|
13740
|
+
}
|
|
13076
13741
|
if (kind === "docs") {
|
|
13077
13742
|
if (url.includes("/mcp")) score += 50;
|
|
13078
13743
|
if (url.includes("quickstart") || url.includes("get-started")) score += 35;
|
|
@@ -13221,9 +13886,9 @@ function buildDocsContextArtifact(sources) {
|
|
|
13221
13886
|
const remoteSources = sources.filter((source) => source.status === "ok" && (source.kind === "website" || source.kind === "docs"));
|
|
13222
13887
|
if (remoteSources.length === 0) return void 0;
|
|
13223
13888
|
const productName = inferProductName(remoteSources);
|
|
13224
|
-
const shortDescription = remoteSources.map((source) => source.description ?? source.paragraphs?.[0]).find((value) => Boolean(value && value.trim()));
|
|
13225
|
-
const setupHints = collectHintSentences(remoteSources, ["setup", "install", "get started", "quickstart", "configuration", "configuring", "running", "restart", "onlymaincontent", "main content"]);
|
|
13226
|
-
const authHints = collectHintSentences(remoteSources, ["auth", "authentication", "api key", "bearer", "header", "token", "credential"]);
|
|
13889
|
+
const shortDescription = dedupeRepeatedSentences(remoteSources.map((source) => source.description ?? source.paragraphs?.[0]).find((value) => Boolean(value && value.trim())));
|
|
13890
|
+
const setupHints = collectHintSentences(remoteSources, ["setup", "install", "get started", "quickstart", "configuration", "configuring", "running", "restart", "onlymaincontent", "main content", "npx", "npm", "curl", "remote hosted url"]);
|
|
13891
|
+
const authHints = collectHintSentences(remoteSources, ["auth", "authentication", "api key", "api_key", "firecrawl_api_key", "bearer", "header", "token", "credential"]);
|
|
13227
13892
|
const warnings = collectHintSentences(remoteSources, ["warning", "note", "requires", "must", "if you", "unavailable", "couldn"]);
|
|
13228
13893
|
const workflowHints = collectWorkflowHints(remoteSources);
|
|
13229
13894
|
const importantTerms = collectImportantTerms(remoteSources);
|
|
@@ -13297,8 +13962,19 @@ function truncateForNote(value, maxLength) {
|
|
|
13297
13962
|
}
|
|
13298
13963
|
function summarizeMarkdownArtifact(content, metadata = {}) {
|
|
13299
13964
|
const cleaned = content.replace(/\r\n/g, "\n");
|
|
13300
|
-
const headings = cleaned.split("\n").map((line) => line.match(/^#{1,
|
|
13301
|
-
const
|
|
13965
|
+
const headings = cleaned.split("\n").map((line) => line.match(/^#{1,4}\s+(.+)$/)?.[1]?.trim()).filter((value) => Boolean(value)).map(stripMarkdownFormatting).filter((value) => Boolean(value) && !isLikelyChromeHeading(value)).slice(0, 12);
|
|
13966
|
+
const textChunks = cleaned.split(/\n{2,}/).map((chunk) => stripMarkdownFormatting(chunk)).map((chunk) => chunk.replace(/\s+/g, " ").trim()).filter((chunk) => Boolean(chunk) && !chunk.startsWith("#"));
|
|
13967
|
+
const filteredTextChunks = filterLikelyContentText(textChunks);
|
|
13968
|
+
const codeHints = extractMarkdownCodeHints(cleaned);
|
|
13969
|
+
const paragraphCandidates = uniqueStrings([
|
|
13970
|
+
...filteredTextChunks,
|
|
13971
|
+
...codeHints
|
|
13972
|
+
]);
|
|
13973
|
+
const paragraphs = paragraphCandidates.map((chunk, index) => ({
|
|
13974
|
+
chunk,
|
|
13975
|
+
score: scoreMarkdownTextChunk(chunk),
|
|
13976
|
+
index
|
|
13977
|
+
})).sort((left, right) => right.score - left.score || left.index - right.index).map(({ chunk }) => chunk).slice(0, 5);
|
|
13302
13978
|
const title = metadata.title?.trim() || headings[0] || paragraphs[0];
|
|
13303
13979
|
const description = metadata.description?.trim() || paragraphs[0];
|
|
13304
13980
|
const lines = [];
|
|
@@ -13325,6 +14001,42 @@ function summarizeMarkdownArtifact(content, metadata = {}) {
|
|
|
13325
14001
|
function stripMarkdownFormatting(value) {
|
|
13326
14002
|
return value.replace(/```[\s\S]*?```/g, " ").replace(/`([^`]+)`/g, "$1").replace(/!\[([^\]]*)\]\([^)]+\)/g, "$1").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/^>\s*/gm, "").replace(/^\s*[-*+]\s+/gm, "").replace(/^\s*\d+\.\s+/gm, "").replace(/[_*~#]+/g, " ").replace(/\|/g, " ").replace(/\s+/g, " ").trim();
|
|
13327
14003
|
}
|
|
14004
|
+
function extractMarkdownCodeHints(content) {
|
|
14005
|
+
const hints = [];
|
|
14006
|
+
for (const match of content.matchAll(/```[^\n]*\n([\s\S]*?)```/g)) {
|
|
14007
|
+
const block = match[1] ?? "";
|
|
14008
|
+
for (const rawLine of block.split("\n")) {
|
|
14009
|
+
const line = rawLine.replace(/\r/g, "").trim();
|
|
14010
|
+
if (!line) continue;
|
|
14011
|
+
if (!/(api[_-]?key|token|header|auth|install|npx|npm|curl|onlymaincontent|main content|map|scrape|crawl|extract|search|agent|command|url|mcp)/i.test(line)) {
|
|
14012
|
+
continue;
|
|
14013
|
+
}
|
|
14014
|
+
const normalized = line.replace(/^[`"'[{(]+|[`"'[\]}):,;]+$/g, "").replace(/\s+/g, " ").trim();
|
|
14015
|
+
if (normalized) {
|
|
14016
|
+
hints.push(normalized);
|
|
14017
|
+
}
|
|
14018
|
+
}
|
|
14019
|
+
}
|
|
14020
|
+
return uniqueStrings(hints).slice(0, 10);
|
|
14021
|
+
}
|
|
14022
|
+
function scoreMarkdownTextChunk(value) {
|
|
14023
|
+
const normalized = value.replace(/\s+/g, " ").trim();
|
|
14024
|
+
if (!normalized) return Number.NEGATIVE_INFINITY;
|
|
14025
|
+
if (isLikelyChromeText(normalized)) return -200;
|
|
14026
|
+
const lower = normalized.toLowerCase();
|
|
14027
|
+
let score = 0;
|
|
14028
|
+
if (/[.!?:]/.test(normalized)) score += 20;
|
|
14029
|
+
if (/`|https?:\/\/|(^| )npx( |$)|(^| )npm( |$)|(^| )curl( |$)|=/.test(normalized)) score += 20;
|
|
14030
|
+
if (lower.includes("search") || lower.includes("scrape") || lower.includes("map") || lower.includes("crawl") || lower.includes("extract") || lower.includes("agent") || lower.includes("browser") || lower.includes("workflow") || lower.includes("knowledge")) {
|
|
14031
|
+
score += 25;
|
|
14032
|
+
}
|
|
14033
|
+
if (lower.includes("api key") || lower.includes("firecrawl_api_key") || lower.includes("token") || lower.includes("auth") || lower.includes("header") || lower.includes("install") || lower.includes("quickstart") || lower.includes("configuration") || lower.includes("onlymaincontent") || lower.includes("main content") || lower.includes("remote hosted url")) {
|
|
14034
|
+
score += 25;
|
|
14035
|
+
}
|
|
14036
|
+
if (normalized.length >= 24 && normalized.length <= 240) score += 10;
|
|
14037
|
+
if (normalized.split(/\s+/).length > 40) score -= 10;
|
|
14038
|
+
return score;
|
|
14039
|
+
}
|
|
13328
14040
|
function summarizeHtml(html) {
|
|
13329
14041
|
const cleanedHtml = stripNonContentHtml(html);
|
|
13330
14042
|
const primaryHtml = selectPrimaryHtmlFragment(cleanedHtml);
|
|
@@ -13394,14 +14106,38 @@ function filterLikelyContentText(values) {
|
|
|
13394
14106
|
}
|
|
13395
14107
|
return uniqueStrings(values).map((value) => value.replace(/\s+/g, " ").trim()).filter((value) => value.length > 0);
|
|
13396
14108
|
}
|
|
14109
|
+
function dedupeRepeatedSentences(value) {
|
|
14110
|
+
if (!value) return void 0;
|
|
14111
|
+
const normalized = value.replace(/\s+/g, " ").replace(/([.!?])\s*,\s*/g, "$1 ").trim();
|
|
14112
|
+
if (!normalized) return void 0;
|
|
14113
|
+
const sentenceMatches = normalized.match(/[^.!?]+[.!?]?/g);
|
|
14114
|
+
if (!sentenceMatches) return normalized;
|
|
14115
|
+
const seen = /* @__PURE__ */ new Set();
|
|
14116
|
+
const uniqueSentences = [];
|
|
14117
|
+
for (const rawSentence of sentenceMatches) {
|
|
14118
|
+
const sentence = rawSentence.trim().replace(/^,+\s*/, "");
|
|
14119
|
+
if (!sentence) continue;
|
|
14120
|
+
const key = sentence.replace(/[.!?]+$/, "").replace(/\s+/g, " ").trim().toLowerCase();
|
|
14121
|
+
if (!key || seen.has(key)) continue;
|
|
14122
|
+
seen.add(key);
|
|
14123
|
+
uniqueSentences.push(sentence);
|
|
14124
|
+
}
|
|
14125
|
+
if (uniqueSentences.length === 0) {
|
|
14126
|
+
return normalized;
|
|
14127
|
+
}
|
|
14128
|
+
return uniqueSentences.join(" ");
|
|
14129
|
+
}
|
|
13397
14130
|
function isLikelyChromeHeading(value) {
|
|
13398
14131
|
const normalized = value.replace(/\s+/g, " ").trim();
|
|
13399
14132
|
if (!normalized) return true;
|
|
13400
14133
|
const lower = normalized.toLowerCase();
|
|
13401
14134
|
const chromePatterns = [
|
|
13402
14135
|
"search docs",
|
|
14136
|
+
"skip to main content",
|
|
13403
14137
|
"table of contents",
|
|
13404
14138
|
"on this page",
|
|
14139
|
+
"navigation",
|
|
14140
|
+
"ctrl k",
|
|
13405
14141
|
"previous",
|
|
13406
14142
|
"next",
|
|
13407
14143
|
"privacy",
|
|
@@ -13411,6 +14147,8 @@ function isLikelyChromeHeading(value) {
|
|
|
13411
14147
|
"blog",
|
|
13412
14148
|
"pricing",
|
|
13413
14149
|
"careers",
|
|
14150
|
+
"playground",
|
|
14151
|
+
"community",
|
|
13414
14152
|
"discord",
|
|
13415
14153
|
"github",
|
|
13416
14154
|
"twitter",
|
|
@@ -13426,8 +14164,11 @@ function isLikelyChromeText(value) {
|
|
|
13426
14164
|
const wordCount = normalized.split(/\s+/).length;
|
|
13427
14165
|
const chromePatterns = [
|
|
13428
14166
|
"search docs",
|
|
14167
|
+
"skip to main content",
|
|
13429
14168
|
"table of contents",
|
|
13430
14169
|
"on this page",
|
|
14170
|
+
"navigation",
|
|
14171
|
+
"ctrl k",
|
|
13431
14172
|
"previous",
|
|
13432
14173
|
"next",
|
|
13433
14174
|
"privacy",
|
|
@@ -13437,6 +14178,8 @@ function isLikelyChromeText(value) {
|
|
|
13437
14178
|
"blog",
|
|
13438
14179
|
"pricing",
|
|
13439
14180
|
"careers",
|
|
14181
|
+
"playground",
|
|
14182
|
+
"community",
|
|
13440
14183
|
"discord",
|
|
13441
14184
|
"github",
|
|
13442
14185
|
"twitter",
|
|
@@ -13563,11 +14306,11 @@ function normalizeProductNameCandidate(value) {
|
|
|
13563
14306
|
function collectHintSentences(sources, keywords) {
|
|
13564
14307
|
const sentences = uniqueStrings(
|
|
13565
14308
|
sources.flatMap((source) => {
|
|
13566
|
-
const
|
|
13567
|
-
source.description,
|
|
13568
|
-
...source.paragraphs ?? []
|
|
13569
|
-
]
|
|
13570
|
-
return
|
|
14309
|
+
const segments = [
|
|
14310
|
+
...source.description ? splitIntoHintSegments(source.description) : [],
|
|
14311
|
+
...(source.paragraphs ?? []).flatMap(splitIntoHintSegments)
|
|
14312
|
+
];
|
|
14313
|
+
return segments.filter((sentence) => keywords.some((keyword) => sentence.toLowerCase().includes(keyword))).slice(0, 6);
|
|
13571
14314
|
})
|
|
13572
14315
|
);
|
|
13573
14316
|
return sentences.slice(0, 6);
|
|
@@ -13579,16 +14322,50 @@ function collectWorkflowHints(sources) {
|
|
|
13579
14322
|
"manual installation",
|
|
13580
14323
|
"configuration",
|
|
13581
14324
|
"environment variables",
|
|
14325
|
+
"remote hosted url",
|
|
14326
|
+
"available tools",
|
|
13582
14327
|
"features",
|
|
13583
14328
|
"get started",
|
|
13584
14329
|
"overview",
|
|
13585
14330
|
"developer guides",
|
|
13586
14331
|
"quickstarts",
|
|
13587
|
-
"mcp server"
|
|
14332
|
+
"mcp server",
|
|
14333
|
+
"ready to build?",
|
|
14334
|
+
"ready to build",
|
|
14335
|
+
"use well-known tools",
|
|
14336
|
+
"code you can trust",
|
|
14337
|
+
"frequently asked questions"
|
|
13588
14338
|
]);
|
|
13589
|
-
|
|
14339
|
+
const workflowKeywords = ["search", "scrape", "map", "crawl", "extract", "agent", "browser", "workflow", "knowledge"];
|
|
14340
|
+
const workflowStopKeywords = ["install", "auth", "token", "header", "configuration", "quickstart"];
|
|
14341
|
+
const sourceTitles = new Set(
|
|
14342
|
+
sources.flatMap((source) => source.title ? [source.title.toLowerCase()] : []).filter(Boolean)
|
|
14343
|
+
);
|
|
14344
|
+
const headingHints = uniqueStrings(
|
|
13590
14345
|
sources.flatMap((source) => source.headings ?? [])
|
|
13591
|
-
).map(
|
|
14346
|
+
).map(normalizeHintText).filter(
|
|
14347
|
+
(heading) => heading.length > 0 && heading.split(/\s+/).length <= 4 && !genericHeadings.has(heading.toLowerCase()) && (!sourceTitles.has(heading.toLowerCase()) || workflowKeywords.some((keyword) => containsWholeWordKeyword(heading, keyword))) && !heading.includes("://")
|
|
14348
|
+
).map((heading, index) => ({
|
|
14349
|
+
value: heading,
|
|
14350
|
+
index,
|
|
14351
|
+
score: scoreWorkflowHint(heading, workflowKeywords)
|
|
14352
|
+
})).filter((entry) => entry.score > 0).sort((left, right) => right.score - left.score || left.index - right.index).map((entry) => entry.value);
|
|
14353
|
+
const paragraphHints = uniqueStrings(
|
|
14354
|
+
sources.flatMap(
|
|
14355
|
+
(source) => (source.paragraphs ?? []).flatMap(splitIntoHintSegments).filter((segment) => {
|
|
14356
|
+
const lower = segment.toLowerCase();
|
|
14357
|
+
return workflowKeywords.some((keyword) => containsWholeWordKeyword(lower, keyword)) && !workflowStopKeywords.some((keyword) => lower.includes(keyword)) && !segment.includes("://") && !segment.includes("=");
|
|
14358
|
+
})
|
|
14359
|
+
)
|
|
14360
|
+
).map(normalizeHintText).map((segment, index) => ({
|
|
14361
|
+
value: segment,
|
|
14362
|
+
index,
|
|
14363
|
+
score: scoreWorkflowHint(segment, workflowKeywords)
|
|
14364
|
+
})).filter((entry) => entry.score > 0).sort((left, right) => right.score - left.score || left.index - right.index).map((entry) => entry.value);
|
|
14365
|
+
return uniqueStrings([
|
|
14366
|
+
...headingHints,
|
|
14367
|
+
...paragraphHints
|
|
14368
|
+
]).slice(0, 8);
|
|
13592
14369
|
}
|
|
13593
14370
|
function collectImportantTerms(sources) {
|
|
13594
14371
|
return uniqueStrings(
|
|
@@ -13598,8 +14375,34 @@ function collectImportantTerms(sources) {
|
|
|
13598
14375
|
])
|
|
13599
14376
|
).map((term) => term.replace(/\s+/g, " ").trim()).filter((term) => term.length > 0 && term.split(/\s+/).length <= 4).slice(0, 8);
|
|
13600
14377
|
}
|
|
13601
|
-
function
|
|
13602
|
-
return value.split(/(?<=[.!?])\s+/).map(
|
|
14378
|
+
function splitIntoHintSegments(value) {
|
|
14379
|
+
return value.split(/(?<=[.!?])\s+|\n+/).map(normalizeHintText).filter(Boolean);
|
|
14380
|
+
}
|
|
14381
|
+
function normalizeHintText(value) {
|
|
14382
|
+
return value.replace(/[\u200B-\u200D\uFEFF]/g, " ").replace(/^(?:hashtag\s+)+/gi, "").replace(/^(?:[#>*-]+\s*)+/, "").replace(/[\\]+/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[`*_]+/g, " ").replace(/\s+/g, " ").trim();
|
|
14383
|
+
}
|
|
14384
|
+
function scoreWorkflowHint(value, workflowKeywords) {
|
|
14385
|
+
const normalized = normalizeHintText(value);
|
|
14386
|
+
const lower = normalized.toLowerCase();
|
|
14387
|
+
let score = 0;
|
|
14388
|
+
for (const keyword of workflowKeywords) {
|
|
14389
|
+
if (containsWholeWordKeyword(lower, keyword)) {
|
|
14390
|
+
score += 30;
|
|
14391
|
+
} else if (lower.includes(keyword)) {
|
|
14392
|
+
score += 10;
|
|
14393
|
+
}
|
|
14394
|
+
}
|
|
14395
|
+
if (lower.includes("feature") || lower.includes("ready to build") || lower.includes("faq") || lower.includes("question")) {
|
|
14396
|
+
score -= 20;
|
|
14397
|
+
}
|
|
14398
|
+
if (normalized.split(/\s+/).length <= 3) {
|
|
14399
|
+
score += 5;
|
|
14400
|
+
}
|
|
14401
|
+
return score;
|
|
14402
|
+
}
|
|
14403
|
+
function containsWholeWordKeyword(value, keyword) {
|
|
14404
|
+
const pattern = new RegExp(`\\b${keyword.replace(/\s+/g, "\\s+")}\\b`, "i");
|
|
14405
|
+
return pattern.test(value);
|
|
13603
14406
|
}
|
|
13604
14407
|
function uniqueStrings(values) {
|
|
13605
14408
|
return [...new Set(values.map((value) => value.trim()).filter(Boolean))];
|
|
@@ -13656,17 +14459,22 @@ function buildAgentPrompt(kind, input) {
|
|
|
13656
14459
|
2. Infer the MCP's real product surfaces and workflows from tools, resources, resource templates, and prompt templates.
|
|
13657
14460
|
3. Merge, split, or rename generated skills so labels are product-facing, not lexical buckets.
|
|
13658
14461
|
4. Update the taxonomy file first; Pluxx will re-render generated skills and commands from that taxonomy after the pass.
|
|
13659
|
-
5.
|
|
13660
|
-
6.
|
|
13661
|
-
7.
|
|
13662
|
-
8.
|
|
14462
|
+
5. Promote clear, repeated user entrypoints into explicit commands when host-native command UX would be clearer than asking users to infer the right skill.
|
|
14463
|
+
6. Promote specialist, delegated, reviewer, or bounded-execution workflows into agents/subagents when isolation is a better native fit than another inline skill.
|
|
14464
|
+
7. Keep setup/onboarding, account-admin, and runtime workflows intentionally separated when appropriate.
|
|
14465
|
+
8. Eliminate misleading labels such as contact or people discovery when the tools do not actually perform direct lookup.
|
|
14466
|
+
9. Use per-skill related resources and prompt templates as strong evidence for workflow shape, but correct them when broader discovery evidence shows a mismatch.
|
|
14467
|
+
10. When prompt templates or tool schemas imply a parameterized workflow, preserve that intent through realistic arguments, argument-bearing commands, and runnable entrypoints instead of flattening everything into generic skills.
|
|
14468
|
+
11. Reject stale scaffold assumptions; if current files conflict with discovery context, prefer the discovery evidence and flag the mismatch.
|
|
14469
|
+
12. Do not settle for a least-common-denominator skill-only scaffold if the product would be more native with commands, agents, or richer argument shapes.
|
|
13663
14470
|
${buildPromptOverrideBlock(kind, input.overrides)}
|
|
13664
14471
|
Success criteria:
|
|
13665
14472
|
- each skill represents a real user workflow or product surface
|
|
13666
14473
|
- skill names are product-shaped and avoid raw MCP tool/server identifiers when possible
|
|
13667
14474
|
- setup/onboarding, account-admin, and runtime workflows are grouped intentionally
|
|
13668
14475
|
- singleton skills are avoided unless they represent a real standalone user workflow
|
|
13669
|
-
- commands stay aligned with the chosen taxonomy
|
|
14476
|
+
- commands stay aligned with the chosen taxonomy, avoid weak command UX, and use realistic arguments when workflows are parameterized
|
|
14477
|
+
- specialist or delegated workflows are promoted into agents/subagents when that native shape is stronger than another flat skill
|
|
13670
14478
|
- per-skill resource and prompt-template associations remain coherent with the chosen taxonomy
|
|
13671
14479
|
- taxonomy decisions are grounded in current discovery context, not stale scaffold assumptions
|
|
13672
14480
|
`;
|
|
@@ -13679,7 +14487,9 @@ Success criteria:
|
|
|
13679
14487
|
4. Keep wording aligned to the MCP's product narrative and branded language; avoid raw MCP server/tool identifiers except when technically required.
|
|
13680
14488
|
5. Prefer the branded product name in user-facing copy; do not lead with internal MCP server identifiers.
|
|
13681
14489
|
6. Replace stale scaffold claims with current discovery-backed language and keep command examples operational, concrete, and copy-paste runnable.
|
|
13682
|
-
7. When
|
|
14490
|
+
7. When discovery implies a parameterized workflow, make the examples show realistic arguments instead of bare placeholder commands.
|
|
14491
|
+
8. Call out when a request should route to a specialist agent/subagent instead of the generic skill path.
|
|
14492
|
+
9. When a workflow already has related resources or prompt templates in the context, keep the wording and examples aligned to that surfaced workflow evidence.
|
|
13683
14493
|
${buildPromptOverrideBlock(kind, input.overrides)}
|
|
13684
14494
|
Success criteria:
|
|
13685
14495
|
- instructions are concise, actionable, and product-shaped
|
|
@@ -13688,20 +14498,21 @@ Success criteria:
|
|
|
13688
14498
|
- raw MCP server identifiers are omitted unless operationally necessary
|
|
13689
14499
|
- the generated section reads like routing guidance, not pasted vendor docs
|
|
13690
14500
|
- command examples use strong command UX (clear intent, realistic args, and runnable shapes)
|
|
14501
|
+
- specialist routing is explicit when certain work should go to an agent/subagent instead of a generic skill
|
|
13691
14502
|
- workflow guidance stays coherent with related resource and prompt-template evidence in the context
|
|
13692
14503
|
- the file remains safe for future \`pluxx sync --from-mcp\`
|
|
13693
14504
|
`;
|
|
13694
14505
|
}
|
|
13695
14506
|
return `${sharedIntro.join("\n")}Your job:
|
|
13696
14507
|
1. Review the current scaffold critically.
|
|
13697
|
-
2. Call out weak skill groupings, missing setup guidance, vague examples, product/category mismatches, raw documentation dumps, lexical skill names, stale scaffold assumptions, weak command UX${input.sourceKind === "mcp-derived" ? ", incoherent per-skill resource/prompt associations, or weak MCP metadata signals" : ", weak marketplace/listing copy, awkward installation guidance, or unclear operator boundaries"}.
|
|
14508
|
+
2. Call out weak skill groupings, missing setup guidance, vague examples, product/category mismatches, raw documentation dumps, lexical skill names, stale scaffold assumptions, weak command UX, missing argument-bearing command entrypoints, missing specialist agent/subagent boundaries, lowest-common-denominator skill-only scaffolds, and misplaced Claude-only skill-frontmatter intent${input.sourceKind === "mcp-derived" ? ", incoherent per-skill resource/prompt associations, or weak MCP metadata signals" : ", weak marketplace/listing copy, awkward installation guidance, or unclear operator boundaries"}.
|
|
13698
14509
|
3. Separate scaffold quality findings from runtime-correctness findings.
|
|
13699
14510
|
4. Propose only the highest-value changes needed to make the scaffold useful.
|
|
13700
14511
|
${buildPromptOverrideBlock(kind, input.overrides)}
|
|
13701
14512
|
Success criteria:
|
|
13702
14513
|
- findings are concrete and tied to files
|
|
13703
14514
|
- scaffold quality gaps are distinguished from runtime correctness
|
|
13704
|
-
- stale assumptions${input.sourceKind === "mcp-derived" ? ", incoherent per-skill discovery associations," : ","}
|
|
14515
|
+
- stale assumptions${input.sourceKind === "mcp-derived" ? ", incoherent per-skill discovery associations," : ","} command-UX weaknesses, and missing agent/subagent shaping are identified explicitly when present
|
|
13705
14516
|
- suggested changes improve user-facing plugin quality
|
|
13706
14517
|
- recommendations stay inside Pluxx-managed boundaries
|
|
13707
14518
|
`;
|
|
@@ -14306,11 +15117,11 @@ ${additions.map((block) => `- ${block.replace(/\n/g, "\n ")}`).join("\n")}
|
|
|
14306
15117
|
|
|
14307
15118
|
// src/cli/doctor.ts
|
|
14308
15119
|
import { accessSync, constants, existsSync as existsSync23, lstatSync, readFileSync as readFileSync11, readdirSync as readdirSync9 } from "fs";
|
|
14309
|
-
import { basename as
|
|
15120
|
+
import { basename as basename6, dirname as dirname6, resolve as resolve16 } from "path";
|
|
14310
15121
|
|
|
14311
15122
|
// src/cli/install.ts
|
|
14312
15123
|
import { resolve as resolve15, dirname as dirname5 } from "path";
|
|
14313
|
-
import { existsSync as existsSync22, symlinkSync, mkdirSync as
|
|
15124
|
+
import { existsSync as existsSync22, symlinkSync, mkdirSync as mkdirSync4, rmSync as rmSync3, readFileSync as readFileSync10, writeFileSync as writeFileSync4, cpSync as cpSync3, readdirSync as readdirSync8 } from "fs";
|
|
14314
15125
|
import { spawnSync } from "child_process";
|
|
14315
15126
|
import * as readline2 from "readline";
|
|
14316
15127
|
function listHookCommands(hooks) {
|
|
@@ -14476,7 +15287,7 @@ function getInstallTargets(pluginName) {
|
|
|
14476
15287
|
{
|
|
14477
15288
|
platform: "opencode",
|
|
14478
15289
|
pluginDir: resolve15(home, ".config/opencode/plugins", pluginName),
|
|
14479
|
-
description: `~/.config/opencode/plugins/${pluginName}.ts
|
|
15290
|
+
description: `~/.config/opencode/plugins/${pluginName}.ts + ~/.config/opencode/plugins/${pluginName}/`
|
|
14480
15291
|
},
|
|
14481
15292
|
{
|
|
14482
15293
|
platform: "github-copilot",
|
|
@@ -14524,7 +15335,7 @@ function toPascalCase2(value) {
|
|
|
14524
15335
|
function writeOpenCodeEntryFile(pluginDir, pluginName) {
|
|
14525
15336
|
const entryPath = getOpenCodeEntryPath(pluginDir);
|
|
14526
15337
|
const exportName = toPascalCase2(pluginName);
|
|
14527
|
-
|
|
15338
|
+
writeFileSync4(
|
|
14528
15339
|
entryPath,
|
|
14529
15340
|
[
|
|
14530
15341
|
'import type { Plugin } from "@opencode-ai/plugin"',
|
|
@@ -14580,7 +15391,7 @@ ${content.slice(frontmatterMatch[0].length)}`;
|
|
|
14580
15391
|
function syncOpenCodeSkills(pluginDir, pluginName) {
|
|
14581
15392
|
const sourceSkillsDir = resolve15(pluginDir, "skills");
|
|
14582
15393
|
if (!existsSync22(sourceSkillsDir)) return;
|
|
14583
|
-
|
|
15394
|
+
mkdirSync4(getOpenCodeSkillRoot(), { recursive: true });
|
|
14584
15395
|
for (const entry of readdirSync8(sourceSkillsDir, { withFileTypes: true })) {
|
|
14585
15396
|
if (!entry.isDirectory()) continue;
|
|
14586
15397
|
const skillSourceDir = resolve15(sourceSkillsDir, entry.name);
|
|
@@ -14589,7 +15400,7 @@ function syncOpenCodeSkills(pluginDir, pluginName) {
|
|
|
14589
15400
|
rmSync3(installedSkillDir, { recursive: true, force: true });
|
|
14590
15401
|
cpSync3(skillSourceDir, installedSkillDir, { recursive: true });
|
|
14591
15402
|
const skillPath = resolve15(installedSkillDir, "SKILL.md");
|
|
14592
|
-
|
|
15403
|
+
writeFileSync4(
|
|
14593
15404
|
skillPath,
|
|
14594
15405
|
namespaceOpenCodeSkill(readFileSync10(skillPath, "utf-8"), pluginName, entry.name)
|
|
14595
15406
|
);
|
|
@@ -14641,6 +15452,15 @@ function getInstallFollowupNotes(platforms) {
|
|
|
14641
15452
|
if (platforms.includes("claude-code")) {
|
|
14642
15453
|
notes.push("Claude Code note: if Claude is already open, run /reload-plugins in the session to pick up the new install.");
|
|
14643
15454
|
}
|
|
15455
|
+
if (platforms.includes("cursor")) {
|
|
15456
|
+
notes.push("Cursor note: if Cursor is already open, use Developer: Reload Window or restart Cursor to pick up the new install.");
|
|
15457
|
+
}
|
|
15458
|
+
if (platforms.includes("codex")) {
|
|
15459
|
+
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.");
|
|
15460
|
+
}
|
|
15461
|
+
if (platforms.includes("opencode")) {
|
|
15462
|
+
notes.push("OpenCode note: if OpenCode is already open, restart or reload it so the plugin is picked up.");
|
|
15463
|
+
}
|
|
14644
15464
|
return notes;
|
|
14645
15465
|
}
|
|
14646
15466
|
function runCommandDefault(command2, args2) {
|
|
@@ -14653,7 +15473,7 @@ function runCommandDefault(command2, args2) {
|
|
|
14653
15473
|
}
|
|
14654
15474
|
function createSymlinkInstall(target) {
|
|
14655
15475
|
const parentDir = resolve15(target.pluginDir, "..");
|
|
14656
|
-
|
|
15476
|
+
mkdirSync4(parentDir, { recursive: true });
|
|
14657
15477
|
if (existsSync22(target.pluginDir)) {
|
|
14658
15478
|
rmSync3(target.pluginDir, { recursive: true, force: true });
|
|
14659
15479
|
}
|
|
@@ -14691,7 +15511,7 @@ function readCodexMarketplace(filepath) {
|
|
|
14691
15511
|
}
|
|
14692
15512
|
function ensureCodexMarketplace(pluginName) {
|
|
14693
15513
|
const filepath = getCodexMarketplacePath();
|
|
14694
|
-
|
|
15514
|
+
mkdirSync4(dirname5(filepath), { recursive: true });
|
|
14695
15515
|
const marketplace = readCodexMarketplace(filepath);
|
|
14696
15516
|
const nextPlugins = (marketplace.plugins ?? []).filter((plugin) => plugin.name !== pluginName);
|
|
14697
15517
|
nextPlugins.push({
|
|
@@ -14706,7 +15526,7 @@ function ensureCodexMarketplace(pluginName) {
|
|
|
14706
15526
|
},
|
|
14707
15527
|
category: "Productivity"
|
|
14708
15528
|
});
|
|
14709
|
-
|
|
15529
|
+
writeFileSync4(
|
|
14710
15530
|
filepath,
|
|
14711
15531
|
JSON.stringify({
|
|
14712
15532
|
name: marketplace.name ?? "pluxx-local",
|
|
@@ -14727,7 +15547,7 @@ function removeCodexMarketplacePlugin(pluginName) {
|
|
|
14727
15547
|
rmSync3(filepath, { force: true });
|
|
14728
15548
|
return;
|
|
14729
15549
|
}
|
|
14730
|
-
|
|
15550
|
+
writeFileSync4(
|
|
14731
15551
|
filepath,
|
|
14732
15552
|
JSON.stringify({
|
|
14733
15553
|
name: marketplace.name ?? "pluxx-local",
|
|
@@ -14738,7 +15558,7 @@ function removeCodexMarketplacePlugin(pluginName) {
|
|
|
14738
15558
|
}
|
|
14739
15559
|
function createCopiedInstall(target) {
|
|
14740
15560
|
const parentDir = resolve15(target.pluginDir, "..");
|
|
14741
|
-
|
|
15561
|
+
mkdirSync4(parentDir, { recursive: true });
|
|
14742
15562
|
if (existsSync22(target.pluginDir)) {
|
|
14743
15563
|
rmSync3(target.pluginDir, { recursive: true, force: true });
|
|
14744
15564
|
}
|
|
@@ -14791,7 +15611,7 @@ function patchInstalledMcpConfig(pluginDir, platform, config, entries) {
|
|
|
14791
15611
|
}
|
|
14792
15612
|
mcpServers[name] = entry;
|
|
14793
15613
|
}
|
|
14794
|
-
|
|
15614
|
+
writeFileSync4(filepath, JSON.stringify({ mcpServers }, null, 2) + "\n");
|
|
14795
15615
|
return;
|
|
14796
15616
|
}
|
|
14797
15617
|
if (platform === "codex") {
|
|
@@ -14821,7 +15641,7 @@ function patchInstalledMcpConfig(pluginDir, platform, config, entries) {
|
|
|
14821
15641
|
}
|
|
14822
15642
|
mcpServers[name] = entry;
|
|
14823
15643
|
}
|
|
14824
|
-
|
|
15644
|
+
writeFileSync4(filepath, JSON.stringify({ mcpServers }, null, 2) + "\n");
|
|
14825
15645
|
}
|
|
14826
15646
|
}
|
|
14827
15647
|
function writeInstalledUserConfig(pluginDir, entries) {
|
|
@@ -14831,13 +15651,13 @@ function writeInstalledUserConfig(pluginDir, entries) {
|
|
|
14831
15651
|
values: buildUserConfigValueMap(entries),
|
|
14832
15652
|
env: buildUserConfigEnvMap(entries)
|
|
14833
15653
|
};
|
|
14834
|
-
|
|
15654
|
+
writeFileSync4(filepath, JSON.stringify(payload, null, 2) + "\n");
|
|
14835
15655
|
}
|
|
14836
15656
|
function disableInstalledEnvValidation(pluginDir, entries) {
|
|
14837
15657
|
if (entries.length === 0) return;
|
|
14838
15658
|
const filepath = resolve15(pluginDir, "scripts/check-env.sh");
|
|
14839
15659
|
if (!existsSync22(filepath)) return;
|
|
14840
|
-
|
|
15660
|
+
writeFileSync4(
|
|
14841
15661
|
filepath,
|
|
14842
15662
|
"#!/usr/bin/env bash\nset -euo pipefail\n# pluxx install materialized required config for this local plugin install.\nexit 0\n"
|
|
14843
15663
|
);
|
|
@@ -14869,15 +15689,15 @@ function ensureClaudeMarketplace(pluginName, sourceDir, materialized) {
|
|
|
14869
15689
|
const pluginManifestPath = resolve15(sourceDir, ".claude-plugin/plugin.json");
|
|
14870
15690
|
const pluginManifest = JSON.parse(readFileSync10(pluginManifestPath, "utf-8"));
|
|
14871
15691
|
rmSync3(marketplaceRoot, { recursive: true, force: true });
|
|
14872
|
-
|
|
14873
|
-
|
|
15692
|
+
mkdirSync4(marketplaceManifestDir, { recursive: true });
|
|
15693
|
+
mkdirSync4(resolve15(marketplaceRoot, "plugins"), { recursive: true });
|
|
14874
15694
|
if (materialized && materialized.entries.length > 0) {
|
|
14875
15695
|
cpSync3(sourceDir, marketplacePluginDir, { recursive: true });
|
|
14876
15696
|
materializeInstalledPlugin(marketplacePluginDir, "claude-code", materialized.config, materialized.entries);
|
|
14877
15697
|
} else {
|
|
14878
15698
|
symlinkSync(sourceDir, marketplacePluginDir);
|
|
14879
15699
|
}
|
|
14880
|
-
|
|
15700
|
+
writeFileSync4(
|
|
14881
15701
|
resolve15(marketplaceManifestDir, "marketplace.json"),
|
|
14882
15702
|
JSON.stringify({
|
|
14883
15703
|
name: marketplaceName,
|
|
@@ -15473,6 +16293,43 @@ function checkMcpMetadataQuality(checks, metadata) {
|
|
|
15473
16293
|
path: MCP_SCAFFOLD_METADATA_PATH
|
|
15474
16294
|
});
|
|
15475
16295
|
}
|
|
16296
|
+
function checkCompilerIntent(checks, rootDir) {
|
|
16297
|
+
try {
|
|
16298
|
+
const compilerIntent = readCompilerIntent(rootDir);
|
|
16299
|
+
if (!compilerIntent) return;
|
|
16300
|
+
if ((compilerIntent.skillPolicies?.length ?? 0) === 0) {
|
|
16301
|
+
addCheck2(checks, {
|
|
16302
|
+
level: "info",
|
|
16303
|
+
code: "compiler-intent-empty",
|
|
16304
|
+
title: "Compiler intent file present",
|
|
16305
|
+
detail: `${PLUXX_COMPILER_INTENT_PATH} exists but does not currently carry migrated source-host policy rows.`,
|
|
16306
|
+
fix: "No action needed unless you expected migrated source-host policy to survive into generated outputs.",
|
|
16307
|
+
path: PLUXX_COMPILER_INTENT_PATH
|
|
16308
|
+
});
|
|
16309
|
+
return;
|
|
16310
|
+
}
|
|
16311
|
+
const sourceLabels = [...new Set(
|
|
16312
|
+
compilerIntent.skillPolicies.map((policy) => `${policy.source.platform}:${policy.source.kind}`)
|
|
16313
|
+
)];
|
|
16314
|
+
addCheck2(checks, {
|
|
16315
|
+
level: "info",
|
|
16316
|
+
code: "compiler-intent-source-host",
|
|
16317
|
+
title: "Imported source-host intent still influences compilation",
|
|
16318
|
+
detail: `${PLUXX_COMPILER_INTENT_PATH} preserves ${compilerIntent.skillPolicies.length} migrated skill polic${compilerIntent.skillPolicies.length === 1 ? "y" : "ies"} from ${sourceLabels.join(", ")} so Pluxx can translate that source-host intent into native target surfaces.`,
|
|
16319
|
+
fix: "Review the generated permissions, agents, and host companions to confirm the migrated source-host assumptions still match the plugin you want to ship.",
|
|
16320
|
+
path: PLUXX_COMPILER_INTENT_PATH
|
|
16321
|
+
});
|
|
16322
|
+
} catch (error) {
|
|
16323
|
+
addCheck2(checks, {
|
|
16324
|
+
level: "warning",
|
|
16325
|
+
code: "compiler-intent-invalid",
|
|
16326
|
+
title: "Compiler intent file could not be parsed",
|
|
16327
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
16328
|
+
fix: `Repair or remove ${PLUXX_COMPILER_INTENT_PATH} and rerun pluxx doctor.`,
|
|
16329
|
+
path: PLUXX_COMPILER_INTENT_PATH
|
|
16330
|
+
});
|
|
16331
|
+
}
|
|
16332
|
+
}
|
|
15476
16333
|
function checkScaffoldMetadata(checks, rootDir, config) {
|
|
15477
16334
|
const metadataPath = resolve16(rootDir, MCP_SCAFFOLD_METADATA_PATH);
|
|
15478
16335
|
if (!existsSync23(metadataPath)) {
|
|
@@ -15805,7 +16662,7 @@ function checkInstalledMcpConfig(checks, rootDir, layout) {
|
|
|
15805
16662
|
function isLikelyOpenCodeInstallPath(rootDir) {
|
|
15806
16663
|
const parent = dirname6(rootDir);
|
|
15807
16664
|
const grandparent = dirname6(parent);
|
|
15808
|
-
return
|
|
16665
|
+
return basename6(parent) === "plugins" && basename6(grandparent) === "opencode";
|
|
15809
16666
|
}
|
|
15810
16667
|
function checkInstalledOpenCodeHostBridge(checks, rootDir) {
|
|
15811
16668
|
if (!isLikelyOpenCodeInstallPath(rootDir)) {
|
|
@@ -15819,7 +16676,7 @@ function checkInstalledOpenCodeHostBridge(checks, rootDir) {
|
|
|
15819
16676
|
});
|
|
15820
16677
|
return;
|
|
15821
16678
|
}
|
|
15822
|
-
const pluginName =
|
|
16679
|
+
const pluginName = basename6(rootDir);
|
|
15823
16680
|
const entryPath = `${rootDir}.ts`;
|
|
15824
16681
|
const entryRelativePath = `${pluginName}.ts`;
|
|
15825
16682
|
if (!existsSync23(entryPath)) {
|
|
@@ -15860,7 +16717,7 @@ function checkInstalledOpenCodeSkills(checks, rootDir) {
|
|
|
15860
16717
|
if (!isLikelyOpenCodeInstallPath(rootDir)) {
|
|
15861
16718
|
return;
|
|
15862
16719
|
}
|
|
15863
|
-
const pluginName =
|
|
16720
|
+
const pluginName = basename6(rootDir);
|
|
15864
16721
|
const sourceSkillsDir = resolve16(rootDir, "skills");
|
|
15865
16722
|
if (!existsSync23(sourceSkillsDir)) {
|
|
15866
16723
|
addCheck2(checks, {
|
|
@@ -16033,6 +16890,7 @@ async function doctorProject(rootDir = process.cwd()) {
|
|
|
16033
16890
|
checkMcpConfig(checks, config);
|
|
16034
16891
|
checkUserConfig(checks, config);
|
|
16035
16892
|
checkScaffoldMetadata(checks, rootDir, config);
|
|
16893
|
+
checkCompilerIntent(checks, rootDir);
|
|
16036
16894
|
checkHookTrust(checks, config);
|
|
16037
16895
|
for (const target of config.targets) {
|
|
16038
16896
|
const limits = PLATFORM_LIMITS[target];
|
|
@@ -16159,8 +17017,8 @@ async function runBuild(rootDir, targets) {
|
|
|
16159
17017
|
}
|
|
16160
17018
|
|
|
16161
17019
|
// src/cli/migrate.ts
|
|
16162
|
-
import { basename as
|
|
16163
|
-
import { existsSync as existsSync24, readdirSync as readdirSync10, mkdirSync as
|
|
17020
|
+
import { basename as basename7, relative as relative10, resolve as resolve18 } from "path";
|
|
17021
|
+
import { existsSync as existsSync24, readdirSync as readdirSync10, mkdirSync as mkdirSync5, cpSync as cpSync4, readFileSync as readFileSync12, writeFileSync as writeFileSync5 } from "fs";
|
|
16164
17022
|
function detectPlatform(pluginDir) {
|
|
16165
17023
|
const checks = [
|
|
16166
17024
|
{ dir: ".claude-plugin", platform: "claude-code" },
|
|
@@ -16622,7 +17480,7 @@ function sanitizeMigratedSkillFrontmatter(outputDir) {
|
|
|
16622
17480
|
"---",
|
|
16623
17481
|
...lines.slice(endIndex + 1)
|
|
16624
17482
|
].join("\n");
|
|
16625
|
-
|
|
17483
|
+
writeFileSync5(skillPath, rewritten, "utf-8");
|
|
16626
17484
|
}
|
|
16627
17485
|
}
|
|
16628
17486
|
function readTomlScalarValue(content, key) {
|
|
@@ -16646,16 +17504,6 @@ function renderMigratedAgentMarkdown(fileStem, parsed) {
|
|
|
16646
17504
|
const agentName = toKebabCase2(parsed.name ?? fileStem) || "agent";
|
|
16647
17505
|
const title = parsed.name ?? titleCaseFromDirName(agentName);
|
|
16648
17506
|
const bodyLines = [];
|
|
16649
|
-
if (parsed.model || parsed.effort) {
|
|
16650
|
-
bodyLines.push("Source metadata:");
|
|
16651
|
-
if (parsed.model) {
|
|
16652
|
-
bodyLines.push(`- Preferred model: \`${parsed.model}\``);
|
|
16653
|
-
}
|
|
16654
|
-
if (parsed.effort) {
|
|
16655
|
-
bodyLines.push(`- Preferred reasoning effort: \`${parsed.effort}\``);
|
|
16656
|
-
}
|
|
16657
|
-
bodyLines.push("");
|
|
16658
|
-
}
|
|
16659
17507
|
if (parsed.developerInstructions) {
|
|
16660
17508
|
bodyLines.push(parsed.developerInstructions.trim());
|
|
16661
17509
|
} else {
|
|
@@ -16665,6 +17513,8 @@ function renderMigratedAgentMarkdown(fileStem, parsed) {
|
|
|
16665
17513
|
"---",
|
|
16666
17514
|
`name: ${JSON.stringify(agentName)}`,
|
|
16667
17515
|
...parsed.description ? [`description: ${JSON.stringify(parsed.description)}`] : [],
|
|
17516
|
+
...parsed.model ? [`model: ${JSON.stringify(parsed.model)}`] : [],
|
|
17517
|
+
...parsed.effort ? [`model_reasoning_effort: ${JSON.stringify(parsed.effort)}`] : [],
|
|
16668
17518
|
"---",
|
|
16669
17519
|
"",
|
|
16670
17520
|
`# ${title}`,
|
|
@@ -16716,7 +17566,7 @@ function hasTopLevelFrontmatterKey(frontmatterLines, key) {
|
|
|
16716
17566
|
function normalizeMigratedOpenCodeAgentFile(agentPath) {
|
|
16717
17567
|
const original = readFileSync12(agentPath, "utf-8");
|
|
16718
17568
|
const parsed = splitMarkdownFrontmatter4(original);
|
|
16719
|
-
const fileStem = toKebabCase2(
|
|
17569
|
+
const fileStem = toKebabCase2(basename7(agentPath, ".md")) || "agent";
|
|
16720
17570
|
const fallbackDescription = buildFallbackAgentDescription(fileStem);
|
|
16721
17571
|
if (!parsed.hasFrontmatter) {
|
|
16722
17572
|
const rewritten2 = [
|
|
@@ -16728,7 +17578,7 @@ function normalizeMigratedOpenCodeAgentFile(agentPath) {
|
|
|
16728
17578
|
original.trimEnd(),
|
|
16729
17579
|
""
|
|
16730
17580
|
].join("\n");
|
|
16731
|
-
|
|
17581
|
+
writeFileSync5(agentPath, rewritten2, "utf-8");
|
|
16732
17582
|
return true;
|
|
16733
17583
|
}
|
|
16734
17584
|
const additions = [];
|
|
@@ -16749,7 +17599,7 @@ function normalizeMigratedOpenCodeAgentFile(agentPath) {
|
|
|
16749
17599
|
"---",
|
|
16750
17600
|
parsed.body
|
|
16751
17601
|
].join("\n");
|
|
16752
|
-
|
|
17602
|
+
writeFileSync5(agentPath, rewritten, "utf-8");
|
|
16753
17603
|
return true;
|
|
16754
17604
|
}
|
|
16755
17605
|
function walkMarkdownFiles3(dir) {
|
|
@@ -16781,13 +17631,13 @@ function copyCodexAgents(sourceDir, destDir) {
|
|
|
16781
17631
|
const entries = readdirSync10(sourceDir, { withFileTypes: true });
|
|
16782
17632
|
const tomlEntries = entries.filter((entry) => entry.isFile() && entry.name.endsWith(".toml"));
|
|
16783
17633
|
if (tomlEntries.length === 0) return false;
|
|
16784
|
-
|
|
17634
|
+
mkdirSync5(destDir, { recursive: true });
|
|
16785
17635
|
for (const entry of tomlEntries) {
|
|
16786
17636
|
const sourcePath = resolve18(sourceDir, entry.name);
|
|
16787
17637
|
const parsed = parseCodexAgentToml(readFileSync12(sourcePath, "utf-8"));
|
|
16788
17638
|
const fallbackName = entry.name.replace(/\.toml$/, "");
|
|
16789
17639
|
const fileName = `${toKebabCase2(parsed.name ?? fallbackName) || "agent"}.md`;
|
|
16790
|
-
|
|
17640
|
+
writeFileSync5(resolve18(destDir, fileName), renderMigratedAgentMarkdown(fallbackName, parsed), "utf-8");
|
|
16791
17641
|
}
|
|
16792
17642
|
return true;
|
|
16793
17643
|
}
|
|
@@ -17179,7 +18029,7 @@ Generated pluxx.config.ts`);
|
|
|
17179
18029
|
}
|
|
17180
18030
|
const taxonomyPath = resolve18(outputDir, MCP_TAXONOMY_PATH);
|
|
17181
18031
|
const metadataPath = resolve18(outputDir, MCP_SCAFFOLD_METADATA_PATH);
|
|
17182
|
-
|
|
18032
|
+
mkdirSync5(resolve18(outputDir, ".pluxx"), { recursive: true });
|
|
17183
18033
|
await writeTextFile(taxonomyPath, `${JSON.stringify(result.persistedSkills, null, 2)}
|
|
17184
18034
|
`);
|
|
17185
18035
|
if (result.compilerIntent) {
|
|
@@ -17206,7 +18056,7 @@ Generated pluxx.config.ts`);
|
|
|
17206
18056
|
}
|
|
17207
18057
|
|
|
17208
18058
|
// src/cli/mcp-proxy.ts
|
|
17209
|
-
import { mkdirSync as
|
|
18059
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync13 } from "fs";
|
|
17210
18060
|
import { dirname as dirname7, resolve as resolve19 } from "path";
|
|
17211
18061
|
import * as readline3 from "readline";
|
|
17212
18062
|
function usage() {
|
|
@@ -17284,7 +18134,7 @@ async function loadReplayTape(filepath) {
|
|
|
17284
18134
|
}
|
|
17285
18135
|
async function writeTape(filepath, tape) {
|
|
17286
18136
|
const absolutePath = resolve19(process.cwd(), filepath);
|
|
17287
|
-
|
|
18137
|
+
mkdirSync6(dirname7(absolutePath), { recursive: true });
|
|
17288
18138
|
await writeTextFile(absolutePath, `${JSON.stringify(tape, null, 2)}
|
|
17289
18139
|
`);
|
|
17290
18140
|
}
|
|
@@ -17455,7 +18305,7 @@ var PromptCancelledError = class extends Error {
|
|
|
17455
18305
|
}
|
|
17456
18306
|
};
|
|
17457
18307
|
function ask(question) {
|
|
17458
|
-
return new Promise((
|
|
18308
|
+
return new Promise((resolve24, reject) => {
|
|
17459
18309
|
const rl = readline4.createInterface({
|
|
17460
18310
|
input: process.stdin,
|
|
17461
18311
|
output: process.stdout
|
|
@@ -17483,7 +18333,7 @@ function ask(question) {
|
|
|
17483
18333
|
rl.once("close", onClose);
|
|
17484
18334
|
rl.question(question, (answer) => {
|
|
17485
18335
|
settle(() => {
|
|
17486
|
-
|
|
18336
|
+
resolve24(answer);
|
|
17487
18337
|
rl.close();
|
|
17488
18338
|
});
|
|
17489
18339
|
});
|
|
@@ -18458,13 +19308,13 @@ ${c2}
|
|
|
18458
19308
|
} }).prompt();
|
|
18459
19309
|
|
|
18460
19310
|
// src/cli/index.ts
|
|
18461
|
-
import { basename as
|
|
18462
|
-
import { mkdir as mkdir4, mkdtemp as
|
|
18463
|
-
import { tmpdir as
|
|
18464
|
-
import { spawn as
|
|
19311
|
+
import { basename as basename8, resolve as resolve23 } from "path";
|
|
19312
|
+
import { mkdir as mkdir4, mkdtemp as mkdtemp3, rm as rm4 } from "fs/promises";
|
|
19313
|
+
import { tmpdir as tmpdir5 } from "os";
|
|
19314
|
+
import { spawn as spawn4 } from "child_process";
|
|
18465
19315
|
|
|
18466
19316
|
// src/cli/publish.ts
|
|
18467
|
-
import { chmodSync, existsSync as existsSync25, mkdtempSync as mkdtempSync2, readFileSync as readFileSync14, rmSync as rmSync4, writeFileSync as
|
|
19317
|
+
import { chmodSync, existsSync as existsSync25, mkdtempSync as mkdtempSync2, readFileSync as readFileSync14, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "fs";
|
|
18468
19318
|
import { createHash } from "crypto";
|
|
18469
19319
|
import { resolve as resolve20 } from "path";
|
|
18470
19320
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
@@ -18604,9 +19454,9 @@ function collectChecks(args2) {
|
|
|
18604
19454
|
const gitStatus = args2.runCommand("git", ["status", "--porcelain"], { cwd: args2.rootDir });
|
|
18605
19455
|
checks.push({
|
|
18606
19456
|
name: "git-clean",
|
|
18607
|
-
ok: gitStatus.status === 0 && gitStatus.stdout.trim() === "",
|
|
19457
|
+
ok: args2.allowDirty || gitStatus.status === 0 && gitStatus.stdout.trim() === "",
|
|
18608
19458
|
code: "git-clean",
|
|
18609
|
-
detail: gitStatus.status !== 0 ? gitStatus.stderr || gitStatus.stdout || "Unable to read git status" : gitStatus.stdout.trim() === "" ? "Working tree is clean." : "Working tree has uncommitted changes."
|
|
19459
|
+
detail: args2.allowDirty ? "Working tree cleanliness check skipped via --allow-dirty." : gitStatus.status !== 0 ? gitStatus.stderr || gitStatus.stdout || "Unable to read git status" : gitStatus.stdout.trim() === "" ? "Working tree is clean." : "Working tree has uncommitted changes."
|
|
18610
19460
|
});
|
|
18611
19461
|
if (args2.npmEnabled) {
|
|
18612
19462
|
checks.push({
|
|
@@ -18658,6 +19508,7 @@ function planPublish(config, options = {}) {
|
|
|
18658
19508
|
config,
|
|
18659
19509
|
npmEnabled,
|
|
18660
19510
|
githubReleaseEnabled,
|
|
19511
|
+
allowDirty: options.allowDirty ?? false,
|
|
18661
19512
|
packageDir,
|
|
18662
19513
|
packageName,
|
|
18663
19514
|
githubRepo,
|
|
@@ -18921,7 +19772,7 @@ rm -rf "$INSTALL_DIR"
|
|
|
18921
19772
|
cp -R "$BUNDLE_DIR" "$INSTALL_DIR"
|
|
18922
19773
|
|
|
18923
19774
|
echo "Installed $PLUGIN_NAME to $INSTALL_DIR"
|
|
18924
|
-
echo "If Cursor is already open,
|
|
19775
|
+
echo "If Cursor is already open, use Developer: Reload Window or restart Cursor so the plugin is picked up."
|
|
18925
19776
|
`;
|
|
18926
19777
|
}
|
|
18927
19778
|
function renderInstallCodexScript(config) {
|
|
@@ -19035,7 +19886,7 @@ NODE
|
|
|
19035
19886
|
|
|
19036
19887
|
echo "Installed $PLUGIN_NAME to $INSTALL_DIR"
|
|
19037
19888
|
echo "Updated Codex marketplace catalog at $MARKETPLACE_PATH"
|
|
19038
|
-
echo "If Codex is already open, restart
|
|
19889
|
+
echo "If Codex is already open, use Plugins > Refresh if that action is available in your current UI, or restart Codex so the plugin is picked up."
|
|
19039
19890
|
`;
|
|
19040
19891
|
}
|
|
19041
19892
|
function renderInstallOpenCodeScript(config) {
|
|
@@ -19210,7 +20061,7 @@ function writeChecksumFile(tempRoot, files) {
|
|
|
19210
20061
|
const name = filePath.split("/").pop();
|
|
19211
20062
|
return `${digest} ${name}`;
|
|
19212
20063
|
}).join("\n");
|
|
19213
|
-
|
|
20064
|
+
writeFileSync6(checksumPath, `${lines}
|
|
19214
20065
|
`);
|
|
19215
20066
|
return checksumPath;
|
|
19216
20067
|
}
|
|
@@ -19249,14 +20100,14 @@ function createReleaseArtifacts(rootDir, config, plan, runCommand) {
|
|
|
19249
20100
|
for (const asset of githubRelease.assets) {
|
|
19250
20101
|
if (asset.kind !== "installer") continue;
|
|
19251
20102
|
const installerPath = resolve20(tempRoot, asset.name);
|
|
19252
|
-
|
|
20103
|
+
writeFileSync6(installerPath, renderInstallerScript(asset, config, context));
|
|
19253
20104
|
chmodSync(installerPath, 493);
|
|
19254
20105
|
created.push(installerPath);
|
|
19255
20106
|
}
|
|
19256
20107
|
const manifestAsset = githubRelease.assets.find((asset) => asset.kind === "manifest");
|
|
19257
20108
|
if (manifestAsset) {
|
|
19258
20109
|
const manifestPath = resolve20(tempRoot, manifestAsset.name);
|
|
19259
|
-
|
|
20110
|
+
writeFileSync6(manifestPath, buildReleaseManifest(config, context));
|
|
19260
20111
|
created.push(manifestPath);
|
|
19261
20112
|
}
|
|
19262
20113
|
const checksumAsset = githubRelease.assets.find((asset) => asset.kind === "checksum");
|
|
@@ -19464,6 +20315,221 @@ function printVerifyInstallResult(result) {
|
|
|
19464
20315
|
console.log(result.ok ? "pluxx verify-install passed." : "pluxx verify-install failed.");
|
|
19465
20316
|
}
|
|
19466
20317
|
|
|
20318
|
+
// src/cli/behavioral.ts
|
|
20319
|
+
import { existsSync as existsSync27, readFileSync as readFileSync15 } from "fs";
|
|
20320
|
+
import { mkdtemp as mkdtemp2, rm as rm3 } from "fs/promises";
|
|
20321
|
+
import { tmpdir as tmpdir4 } from "os";
|
|
20322
|
+
import { resolve as resolve22 } from "path";
|
|
20323
|
+
import { spawn as spawn3 } from "child_process";
|
|
20324
|
+
var BEHAVIORAL_CONFIG_PATH = ".pluxx/behavioral-smoke.json";
|
|
20325
|
+
var CURSOR_RUNNER_BINARIES2 = ["agent", "cursor-agent"];
|
|
20326
|
+
var SUPPORTED_PLATFORMS = ["claude-code", "cursor", "codex", "opencode"];
|
|
20327
|
+
async function runBehavioralSuite(rootDir, config, targets, options = {}) {
|
|
20328
|
+
const selectedPlatforms = targets.filter(
|
|
20329
|
+
(target) => SUPPORTED_PLATFORMS.includes(target)
|
|
20330
|
+
);
|
|
20331
|
+
const cases = loadBehavioralCases(rootDir, selectedPlatforms, options.promptOverride);
|
|
20332
|
+
const checks = [];
|
|
20333
|
+
for (const behavioralCase of cases) {
|
|
20334
|
+
for (const platform of selectedPlatforms) {
|
|
20335
|
+
const targetConfig = behavioralCase.targets[platform];
|
|
20336
|
+
if (!targetConfig) continue;
|
|
20337
|
+
checks.push(await runBehavioralCheck(rootDir, config, behavioralCase.name, platform, targetConfig));
|
|
20338
|
+
}
|
|
20339
|
+
}
|
|
20340
|
+
return {
|
|
20341
|
+
ok: checks.every((check) => check.ok),
|
|
20342
|
+
source: options.promptOverride ? "--behavioral-prompt" : BEHAVIORAL_CONFIG_PATH,
|
|
20343
|
+
checks
|
|
20344
|
+
};
|
|
20345
|
+
}
|
|
20346
|
+
function loadBehavioralCases(rootDir, targets, promptOverride) {
|
|
20347
|
+
if (promptOverride) {
|
|
20348
|
+
return [{
|
|
20349
|
+
name: "inline-prompt",
|
|
20350
|
+
targets: Object.fromEntries(
|
|
20351
|
+
targets.map((target) => [target, { prompt: promptOverride }])
|
|
20352
|
+
)
|
|
20353
|
+
}];
|
|
20354
|
+
}
|
|
20355
|
+
const filePath = resolve22(rootDir, BEHAVIORAL_CONFIG_PATH);
|
|
20356
|
+
if (!existsSync27(filePath)) {
|
|
20357
|
+
throw new Error(
|
|
20358
|
+
`No behavioral smoke config found at ${BEHAVIORAL_CONFIG_PATH}. Add that file or pass --behavioral-prompt to define a real example query.`
|
|
20359
|
+
);
|
|
20360
|
+
}
|
|
20361
|
+
const parsed = JSON.parse(readFileSync15(filePath, "utf-8"));
|
|
20362
|
+
if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.cases) || parsed.cases.length === 0) {
|
|
20363
|
+
throw new Error(`${BEHAVIORAL_CONFIG_PATH} must contain a non-empty "cases" array.`);
|
|
20364
|
+
}
|
|
20365
|
+
return parsed.cases;
|
|
20366
|
+
}
|
|
20367
|
+
async function runBehavioralCheck(rootDir, config, caseName, platform, targetConfig) {
|
|
20368
|
+
const prompt = targetConfig.prompt.trim();
|
|
20369
|
+
if (!prompt) {
|
|
20370
|
+
throw new Error(`Behavioral smoke case "${caseName}" for ${platform} is missing a prompt.`);
|
|
20371
|
+
}
|
|
20372
|
+
const command2 = await buildBehavioralCommand(platform, prompt, rootDir);
|
|
20373
|
+
const execution = await executeBehavioralCommand(platform, command2, rootDir);
|
|
20374
|
+
const responseText = execution.response.trim();
|
|
20375
|
+
const failures = [];
|
|
20376
|
+
if (execution.exitCode !== 0) {
|
|
20377
|
+
failures.push(`runner exited with code ${execution.exitCode}`);
|
|
20378
|
+
}
|
|
20379
|
+
if (!responseText) {
|
|
20380
|
+
failures.push("runner returned no response text");
|
|
20381
|
+
}
|
|
20382
|
+
for (const required of targetConfig.require ?? []) {
|
|
20383
|
+
if (!includesNeedle(responseText, required)) {
|
|
20384
|
+
failures.push(`missing required text: ${required}`);
|
|
20385
|
+
}
|
|
20386
|
+
}
|
|
20387
|
+
for (const forbidden of targetConfig.forbid ?? []) {
|
|
20388
|
+
if (includesNeedle(responseText, forbidden)) {
|
|
20389
|
+
failures.push(`matched forbidden text: ${forbidden}`);
|
|
20390
|
+
}
|
|
20391
|
+
}
|
|
20392
|
+
return {
|
|
20393
|
+
caseName,
|
|
20394
|
+
platform,
|
|
20395
|
+
prompt,
|
|
20396
|
+
command: command2,
|
|
20397
|
+
ok: failures.length === 0,
|
|
20398
|
+
exitCode: execution.exitCode,
|
|
20399
|
+
responseBytes: responseText.length,
|
|
20400
|
+
responsePreview: truncate2(responseText, 220),
|
|
20401
|
+
require: targetConfig.require,
|
|
20402
|
+
forbid: targetConfig.forbid,
|
|
20403
|
+
failures
|
|
20404
|
+
};
|
|
20405
|
+
}
|
|
20406
|
+
async function buildBehavioralCommand(platform, prompt, workspace) {
|
|
20407
|
+
if (platform === "claude-code") {
|
|
20408
|
+
return [
|
|
20409
|
+
"claude",
|
|
20410
|
+
"--no-session-persistence",
|
|
20411
|
+
"--output-format",
|
|
20412
|
+
"text",
|
|
20413
|
+
"--permission-mode",
|
|
20414
|
+
"acceptEdits",
|
|
20415
|
+
"-p",
|
|
20416
|
+
prompt
|
|
20417
|
+
];
|
|
20418
|
+
}
|
|
20419
|
+
if (platform === "cursor") {
|
|
20420
|
+
const binary = await resolveCursorBinary2();
|
|
20421
|
+
if (!binary) {
|
|
20422
|
+
throw new Error("Cursor CLI `agent` or `cursor-agent` is not available on PATH.");
|
|
20423
|
+
}
|
|
20424
|
+
await ensureCursorAuthenticated(binary);
|
|
20425
|
+
return [
|
|
20426
|
+
binary,
|
|
20427
|
+
"-p",
|
|
20428
|
+
"--trust",
|
|
20429
|
+
"--workspace",
|
|
20430
|
+
workspace,
|
|
20431
|
+
"--force",
|
|
20432
|
+
prompt
|
|
20433
|
+
];
|
|
20434
|
+
}
|
|
20435
|
+
if (platform === "codex") {
|
|
20436
|
+
return [
|
|
20437
|
+
"codex",
|
|
20438
|
+
"exec",
|
|
20439
|
+
"--ephemeral",
|
|
20440
|
+
"--skip-git-repo-check",
|
|
20441
|
+
"--full-auto",
|
|
20442
|
+
prompt
|
|
20443
|
+
];
|
|
20444
|
+
}
|
|
20445
|
+
return ["opencode", "run", prompt];
|
|
20446
|
+
}
|
|
20447
|
+
async function executeBehavioralCommand(platform, command2, cwd) {
|
|
20448
|
+
let codexOutputDir = null;
|
|
20449
|
+
let codexLastMessagePath = null;
|
|
20450
|
+
const runtimeCommand = [...command2];
|
|
20451
|
+
if (platform === "codex") {
|
|
20452
|
+
codexOutputDir = await mkdtemp2(resolve22(tmpdir4(), "pluxx-codex-behavioral-"));
|
|
20453
|
+
codexLastMessagePath = resolve22(codexOutputDir, "last-message.txt");
|
|
20454
|
+
runtimeCommand.splice(2, 0, "--output-last-message", codexLastMessagePath);
|
|
20455
|
+
}
|
|
20456
|
+
try {
|
|
20457
|
+
return await new Promise((resolvePromise, reject) => {
|
|
20458
|
+
const child = spawn3(runtimeCommand[0], runtimeCommand.slice(1), {
|
|
20459
|
+
cwd,
|
|
20460
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
20461
|
+
env: process.env
|
|
20462
|
+
});
|
|
20463
|
+
const stdoutChunks = [];
|
|
20464
|
+
const stderrChunks = [];
|
|
20465
|
+
child.stdout?.on("data", (chunk) => stdoutChunks.push(Buffer.from(chunk)));
|
|
20466
|
+
child.stderr?.on("data", (chunk) => stderrChunks.push(Buffer.from(chunk)));
|
|
20467
|
+
child.on("error", reject);
|
|
20468
|
+
child.on("close", (code) => {
|
|
20469
|
+
const stdout = Buffer.concat(stdoutChunks).toString("utf-8");
|
|
20470
|
+
const stderr = Buffer.concat(stderrChunks).toString("utf-8");
|
|
20471
|
+
const codexMessage = codexLastMessagePath && existsSync27(codexLastMessagePath) ? readFileSync15(codexLastMessagePath, "utf-8") : "";
|
|
20472
|
+
resolvePromise({
|
|
20473
|
+
exitCode: code ?? 1,
|
|
20474
|
+
response: codexMessage.trim() || stdout.trim() || stderr.trim()
|
|
20475
|
+
});
|
|
20476
|
+
});
|
|
20477
|
+
});
|
|
20478
|
+
} finally {
|
|
20479
|
+
if (codexOutputDir) {
|
|
20480
|
+
await rm3(codexOutputDir, { recursive: true, force: true });
|
|
20481
|
+
}
|
|
20482
|
+
}
|
|
20483
|
+
}
|
|
20484
|
+
async function resolveCursorBinary2() {
|
|
20485
|
+
for (const candidate of CURSOR_RUNNER_BINARIES2) {
|
|
20486
|
+
if (await commandExists2(candidate)) {
|
|
20487
|
+
return candidate;
|
|
20488
|
+
}
|
|
20489
|
+
}
|
|
20490
|
+
return void 0;
|
|
20491
|
+
}
|
|
20492
|
+
async function ensureCursorAuthenticated(binary) {
|
|
20493
|
+
if (process.env.CURSOR_API_KEY && process.env.CURSOR_API_KEY.trim()) {
|
|
20494
|
+
return;
|
|
20495
|
+
}
|
|
20496
|
+
const ok = await commandSucceeds2([binary, "status"]);
|
|
20497
|
+
if (!ok) {
|
|
20498
|
+
throw new Error("Cursor CLI authentication is required. Run `agent login` (or `cursor-agent login`) or export `CURSOR_API_KEY` before behavioral smoke runs.");
|
|
20499
|
+
}
|
|
20500
|
+
}
|
|
20501
|
+
async function commandExists2(binary) {
|
|
20502
|
+
return await new Promise((resolvePromise) => {
|
|
20503
|
+
const child = spawn3("sh", ["-c", `command -v ${shellQuote2(binary)} >/dev/null 2>&1`], {
|
|
20504
|
+
stdio: "ignore",
|
|
20505
|
+
env: process.env
|
|
20506
|
+
});
|
|
20507
|
+
child.on("close", (code) => resolvePromise(code === 0));
|
|
20508
|
+
child.on("error", () => resolvePromise(false));
|
|
20509
|
+
});
|
|
20510
|
+
}
|
|
20511
|
+
async function commandSucceeds2(command2) {
|
|
20512
|
+
return await new Promise((resolvePromise) => {
|
|
20513
|
+
const child = spawn3(command2[0], command2.slice(1), {
|
|
20514
|
+
stdio: "ignore",
|
|
20515
|
+
env: process.env
|
|
20516
|
+
});
|
|
20517
|
+
child.on("close", (code) => resolvePromise(code === 0));
|
|
20518
|
+
child.on("error", () => resolvePromise(false));
|
|
20519
|
+
});
|
|
20520
|
+
}
|
|
20521
|
+
function truncate2(value, length) {
|
|
20522
|
+
if (value.length <= length) return value;
|
|
20523
|
+
return `${value.slice(0, Math.max(0, length - 3))}...`;
|
|
20524
|
+
}
|
|
20525
|
+
function includesNeedle(haystack, needle) {
|
|
20526
|
+
return haystack.toLowerCase().includes(needle.trim().toLowerCase());
|
|
20527
|
+
}
|
|
20528
|
+
function shellQuote2(value) {
|
|
20529
|
+
if (/^[A-Za-z0-9_./:-]+$/.test(value)) return value;
|
|
20530
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
20531
|
+
}
|
|
20532
|
+
|
|
19467
20533
|
// src/cli/index.ts
|
|
19468
20534
|
var args = process.argv.slice(2);
|
|
19469
20535
|
var command = args[0];
|
|
@@ -19912,7 +20978,7 @@ function defaultAuthEnvVar(provider, discoveredAuth) {
|
|
|
19912
20978
|
function tryOpenBrowser(url) {
|
|
19913
20979
|
const launcher = process.platform === "darwin" ? { command: "open", args: [url] } : process.platform === "win32" ? { command: "cmd", args: ["/c", "start", "", url] } : { command: "xdg-open", args: [url] };
|
|
19914
20980
|
try {
|
|
19915
|
-
const child =
|
|
20981
|
+
const child = spawn4(launcher.command, launcher.args, {
|
|
19916
20982
|
detached: true,
|
|
19917
20983
|
stdio: "ignore"
|
|
19918
20984
|
});
|
|
@@ -20130,7 +21196,7 @@ async function planInitContextArtifactFiles(rootDir, contextPack) {
|
|
|
20130
21196
|
return plannedFiles;
|
|
20131
21197
|
}
|
|
20132
21198
|
async function planAuxiliaryFile(rootDir, relativePath, content) {
|
|
20133
|
-
const filePath =
|
|
21199
|
+
const filePath = resolve23(rootDir, relativePath);
|
|
20134
21200
|
const action = await planTextFileAction(filePath, content);
|
|
20135
21201
|
return {
|
|
20136
21202
|
relativePath,
|
|
@@ -20199,7 +21265,7 @@ async function runInit() {
|
|
|
20199
21265
|
if (!runtime.isInteractive) {
|
|
20200
21266
|
throw new Error("pluxx init requires an interactive terminal unless you use `pluxx init --from-mcp ... --yes`.");
|
|
20201
21267
|
}
|
|
20202
|
-
const dirName = positionalName ? toKebabCase3(positionalName) :
|
|
21268
|
+
const dirName = positionalName ? toKebabCase3(positionalName) : basename8(process.cwd()).toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
20203
21269
|
console.log("");
|
|
20204
21270
|
console.log(" pluxx init \u2014 Create a new plugin");
|
|
20205
21271
|
console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
@@ -20276,7 +21342,7 @@ ${mcpBlock}${brandBlock}
|
|
|
20276
21342
|
targets: [${targetsList}],
|
|
20277
21343
|
})
|
|
20278
21344
|
`;
|
|
20279
|
-
await writeTextFile(
|
|
21345
|
+
await writeTextFile(resolve23(process.cwd(), "pluxx.config.ts"), template);
|
|
20280
21346
|
const skillDir = `skills/${skillName}`;
|
|
20281
21347
|
await mkdir4(skillDir, { recursive: true });
|
|
20282
21348
|
const skillContent = `---
|
|
@@ -20298,7 +21364,7 @@ Describe how agents should use this skill.
|
|
|
20298
21364
|
Example prompt or command here
|
|
20299
21365
|
\`\`\`
|
|
20300
21366
|
`;
|
|
20301
|
-
await writeTextFile(
|
|
21367
|
+
await writeTextFile(resolve23(process.cwd(), `${skillDir}/SKILL.md`), skillContent);
|
|
20302
21368
|
console.log("");
|
|
20303
21369
|
console.log(" Created:");
|
|
20304
21370
|
console.log(" pluxx.config.ts");
|
|
@@ -20542,7 +21608,7 @@ ${formatAuthRequiredMessage("init", retryError, source)}`);
|
|
|
20542
21608
|
if (!runtime.dryRun) {
|
|
20543
21609
|
await applyMcpScaffoldPlan(process.cwd(), plan);
|
|
20544
21610
|
for (const file of contextArtifactFiles) {
|
|
20545
|
-
await writeTextFile(
|
|
21611
|
+
await writeTextFile(resolve23(process.cwd(), file.relativePath), file.content);
|
|
20546
21612
|
}
|
|
20547
21613
|
}
|
|
20548
21614
|
const lintResult = runtime.dryRun ? { errors: 0, warnings: 0, issues: [] } : await lintProject(process.cwd());
|
|
@@ -20705,7 +21771,7 @@ async function runSync() {
|
|
|
20705
21771
|
async function runDoctor() {
|
|
20706
21772
|
const consumerMode = readFlag(args, "--consumer");
|
|
20707
21773
|
const doctorPath = args.slice(1).find((value) => !value.startsWith("-"));
|
|
20708
|
-
const rootDir = doctorPath ?
|
|
21774
|
+
const rootDir = doctorPath ? resolve23(process.cwd(), doctorPath) : process.cwd();
|
|
20709
21775
|
const report = consumerMode ? await doctorConsumer(rootDir) : await doctorProject(rootDir);
|
|
20710
21776
|
if (runtime.jsonOutput) {
|
|
20711
21777
|
printJson(report);
|
|
@@ -21107,7 +22173,7 @@ ${formatAuthRequiredMessage("autopilot", retryError, source)}`);
|
|
|
21107
22173
|
review: passDecisions.review,
|
|
21108
22174
|
verify
|
|
21109
22175
|
});
|
|
21110
|
-
const workspaceRoot = runtime.dryRun ? await
|
|
22176
|
+
const workspaceRoot = runtime.dryRun ? await mkdtemp3(`${tmpdir5()}/pluxx-autopilot-`) : process.cwd();
|
|
21111
22177
|
tempDir = runtime.dryRun ? workspaceRoot : void 0;
|
|
21112
22178
|
const scaffoldSpinner = createSpinner(runtime);
|
|
21113
22179
|
scaffoldSpinner?.start(`Autopilot 2/${totalSteps} \xB7 Planning scaffold...`);
|
|
@@ -21454,11 +22520,13 @@ ${formatAuthRequiredMessage("autopilot", retryError, source)}`);
|
|
|
21454
22520
|
}
|
|
21455
22521
|
} finally {
|
|
21456
22522
|
if (tempDir) {
|
|
21457
|
-
await
|
|
22523
|
+
await rm4(tempDir, { recursive: true, force: true });
|
|
21458
22524
|
}
|
|
21459
22525
|
}
|
|
21460
22526
|
}
|
|
21461
22527
|
async function runTestCommand() {
|
|
22528
|
+
const behavioral = readFlag(args, "--behavioral");
|
|
22529
|
+
const behavioralPrompt = readOption2(args, "--behavioral-prompt");
|
|
21462
22530
|
const targets = parseTargetFlagValues(args);
|
|
21463
22531
|
const result = await runTestSuite({
|
|
21464
22532
|
rootDir: process.cwd(),
|
|
@@ -21466,15 +22534,20 @@ async function runTestCommand() {
|
|
|
21466
22534
|
});
|
|
21467
22535
|
const config = result.config.ok ? await loadConfig() : null;
|
|
21468
22536
|
const platforms = targets ?? config?.targets ?? [];
|
|
22537
|
+
if (behavioral && !args.includes("--install")) {
|
|
22538
|
+
throw new Error("--behavioral requires --install so the selected host CLIs can see the installed plugin bundle.");
|
|
22539
|
+
}
|
|
21469
22540
|
const install = result.ok && config ? await maybeInstallBuiltOutputs(config, platforms, { verifyConsumers: true }) : void 0;
|
|
21470
|
-
const
|
|
22541
|
+
const behavioralResult = result.ok && config && install && behavioral ? await runBehavioralSuite(process.cwd(), config, platforms, { promptOverride: behavioralPrompt }) : void 0;
|
|
22542
|
+
const finalResult = install?.verification || behavioralResult ? {
|
|
21471
22543
|
...result,
|
|
21472
|
-
ok: result.ok && install
|
|
22544
|
+
ok: result.ok && (install?.verification?.ok ?? true) && (behavioralResult?.ok ?? true)
|
|
21473
22545
|
} : result;
|
|
21474
22546
|
if (runtime.jsonOutput) {
|
|
21475
22547
|
printJson({
|
|
21476
22548
|
...finalResult,
|
|
21477
|
-
install
|
|
22549
|
+
install,
|
|
22550
|
+
behavioral: behavioralResult
|
|
21478
22551
|
});
|
|
21479
22552
|
return;
|
|
21480
22553
|
}
|
|
@@ -21492,6 +22565,18 @@ async function runTestCommand() {
|
|
|
21492
22565
|
console.log(`${prefix} ${check.platform}: ${check.consumerPath}`);
|
|
21493
22566
|
}
|
|
21494
22567
|
}
|
|
22568
|
+
if (behavioralResult) {
|
|
22569
|
+
console.log("Behavioral headless smoke:");
|
|
22570
|
+
for (const check of behavioralResult.checks) {
|
|
22571
|
+
const prefix = check.ok ? " PASS" : " FAIL";
|
|
22572
|
+
console.log(`${prefix} ${check.platform}/${check.caseName}: ${check.responsePreview || "(no response preview)"}`);
|
|
22573
|
+
if (!check.ok) {
|
|
22574
|
+
for (const failure of check.failures) {
|
|
22575
|
+
console.log(` - ${failure}`);
|
|
22576
|
+
}
|
|
22577
|
+
}
|
|
22578
|
+
}
|
|
22579
|
+
}
|
|
21495
22580
|
for (const note of install.notes) {
|
|
21496
22581
|
console.log(note);
|
|
21497
22582
|
}
|
|
@@ -21598,7 +22683,8 @@ async function runPublishCommand() {
|
|
|
21598
22683
|
requestedChannels,
|
|
21599
22684
|
version: readOption2(args, "--version"),
|
|
21600
22685
|
tag: readOption2(args, "--tag"),
|
|
21601
|
-
dryRun: runtime.dryRun
|
|
22686
|
+
dryRun: runtime.dryRun,
|
|
22687
|
+
allowDirty: args.includes("--allow-dirty")
|
|
21602
22688
|
});
|
|
21603
22689
|
if (runtime.dryRun) {
|
|
21604
22690
|
if (runtime.jsonOutput) {
|
|
@@ -21618,7 +22704,8 @@ async function runPublishCommand() {
|
|
|
21618
22704
|
requestedChannels,
|
|
21619
22705
|
version: readOption2(args, "--version"),
|
|
21620
22706
|
tag: readOption2(args, "--tag"),
|
|
21621
|
-
dryRun: false
|
|
22707
|
+
dryRun: false,
|
|
22708
|
+
allowDirty: args.includes("--allow-dirty")
|
|
21622
22709
|
});
|
|
21623
22710
|
if (runtime.jsonOutput) {
|
|
21624
22711
|
printJson(result);
|
|
@@ -21641,7 +22728,7 @@ async function runVerifyInstall() {
|
|
|
21641
22728
|
const targets = parseTargetFlagValues(args);
|
|
21642
22729
|
const config = await loadConfig();
|
|
21643
22730
|
if (runtime.dryRun) {
|
|
21644
|
-
const distDir =
|
|
22731
|
+
const distDir = resolve23(process.cwd(), config.outDir);
|
|
21645
22732
|
const plan = planInstallPlugin(distDir, config.name, targets ?? config.targets);
|
|
21646
22733
|
const summary = {
|
|
21647
22734
|
dryRun: true,
|
|
@@ -21731,11 +22818,11 @@ Usage:
|
|
|
21731
22818
|
pluxx init [name] [--from-mcp <source>] Create a new pluxx.config.ts
|
|
21732
22819
|
pluxx sync [--from-mcp <source>] Refresh MCP-derived scaffold files
|
|
21733
22820
|
pluxx migrate <path> Import an existing plugin into pluxx
|
|
21734
|
-
pluxx test [--target <platforms...>] [--install] Run config, lint, eval, build, and smoke checks
|
|
22821
|
+
pluxx test [--target <platforms...>] [--install] [--behavioral] Run config, lint, eval, build, and smoke checks
|
|
21735
22822
|
pluxx eval Evaluate scaffold and prompt-pack quality
|
|
21736
22823
|
pluxx install [--target <platforms>] [--trust] Install built plugins for local testing
|
|
21737
22824
|
pluxx verify-install [--target <platforms>] Inspect installed host-visible plugin state
|
|
21738
|
-
pluxx publish [--npm] [--github-release] [--dry-run] [--json] [--tag latest] [--version x.y.z]
|
|
22825
|
+
pluxx publish [--npm] [--github-release] [--allow-dirty] [--dry-run] [--json] [--tag latest] [--version x.y.z]
|
|
21739
22826
|
pluxx uninstall [--target <platforms>] Remove symlinked plugins
|
|
21740
22827
|
pluxx help Show this help
|
|
21741
22828
|
|
|
@@ -21744,6 +22831,7 @@ Common flags:
|
|
|
21744
22831
|
--quiet Suppress non-error chatter
|
|
21745
22832
|
--verbose-runner Stream runner stdout/stderr for agent run/autopilot
|
|
21746
22833
|
--dry-run Show planned work without writing files or installing anything
|
|
22834
|
+
--allow-dirty Skip the clean-working-tree check for publish planning or CI release flows
|
|
21747
22835
|
--mode quick|standard|thorough Control how much agent refinement autopilot performs
|
|
21748
22836
|
--approve-mcp-tools Preapprove all tools from the imported MCP in canonical permissions
|
|
21749
22837
|
--ingest-provider auto|local|firecrawl Choose the docs/website ingestion backend for agent prepare/autopilot
|
|
@@ -21792,6 +22880,8 @@ Examples:
|
|
|
21792
22880
|
pluxx eval --json Inspect scaffold/prompt-pack quality as JSON
|
|
21793
22881
|
pluxx test --target claude-code codex Verify selected target outputs
|
|
21794
22882
|
pluxx test --install Verify and install all configured targets locally
|
|
22883
|
+
pluxx test --install --trust --behavioral Run installed headless example-query smoke checks
|
|
22884
|
+
pluxx test --install --trust --behavioral --behavioral-prompt "Use My Plugin to summarize this repo"
|
|
21795
22885
|
pluxx install Install to all configured targets
|
|
21796
22886
|
pluxx install --target claude-code Install to Claude Code only
|
|
21797
22887
|
pluxx verify-install --target codex Verify the installed Codex bundle in its native local path
|
|
@@ -21801,7 +22891,7 @@ Examples:
|
|
|
21801
22891
|
}
|
|
21802
22892
|
if (import.meta.main) {
|
|
21803
22893
|
main().catch((err) => {
|
|
21804
|
-
console.error(err);
|
|
22894
|
+
console.error(err instanceof Error ? err.message : err);
|
|
21805
22895
|
process.exit(1);
|
|
21806
22896
|
});
|
|
21807
22897
|
}
|