@orchid-labs/pluxx 0.1.14 → 0.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-translation-registry.d.ts +6 -0
- package/dist/agent-translation-registry.d.ts.map +1 -0
- package/dist/cli/agent.d.ts.map +1 -1
- package/dist/cli/behavioral.d.ts +2 -0
- package/dist/cli/behavioral.d.ts.map +1 -1
- package/dist/cli/discover-installed-mcp.d.ts +2 -1
- package/dist/cli/discover-installed-mcp.d.ts.map +1 -1
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/index.js +2433 -673
- package/dist/cli/init-from-mcp.d.ts +2 -0
- 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/command-translation-registry.d.ts +9 -0
- package/dist/command-translation-registry.d.ts.map +1 -0
- package/dist/commands.d.ts +20 -0
- package/dist/commands.d.ts.map +1 -1
- package/dist/generators/base.d.ts.map +1 -1
- 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/opencode/index.d.ts +1 -0
- package/dist/generators/opencode/index.d.ts.map +1 -1
- package/dist/generators/shared/claude-family.d.ts.map +1 -1
- package/dist/hook-translation-registry.d.ts +10 -0
- package/dist/hook-translation-registry.d.ts.map +1 -1
- package/dist/index.js +164 -3
- package/dist/mcp-native-overrides.d.ts +14 -0
- package/dist/mcp-native-overrides.d.ts.map +1 -0
- package/dist/schema.d.ts +36 -36
- package/dist/skill-translation-registry.d.ts +8 -0
- package/dist/skill-translation-registry.d.ts.map +1 -0
- package/dist/skills.d.ts +24 -0
- package/dist/skills.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -5055,6 +5055,262 @@ function parsePluginRootReference(value) {
|
|
|
5055
5055
|
};
|
|
5056
5056
|
}
|
|
5057
5057
|
|
|
5058
|
+
// src/user-config.ts
|
|
5059
|
+
var ENV_VAR_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
5060
|
+
var PLACEHOLDER_SECRET_PATTERNS = [
|
|
5061
|
+
/\bdummy\b/i,
|
|
5062
|
+
/\bplaceholder\b/i,
|
|
5063
|
+
/\bexample\b/i,
|
|
5064
|
+
/\bchangeme\b/i,
|
|
5065
|
+
/\breplace[_ -]?me\b/i,
|
|
5066
|
+
/\byour[_ -]?(api[_ -]?)?key\b/i,
|
|
5067
|
+
/\bapi[_ -]?key[_ -]?here\b/i,
|
|
5068
|
+
/\btoken[_ -]?here\b/i
|
|
5069
|
+
];
|
|
5070
|
+
function normalizeUserConfigKey(value) {
|
|
5071
|
+
return value.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2").replace(/[._/\s]+/g, "-").toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
|
|
5072
|
+
}
|
|
5073
|
+
function humanizeUserConfigLabel(value) {
|
|
5074
|
+
return value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/[._/]+/g, " ").replace(/-/g, " ").split(/\s+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()).join(" ");
|
|
5075
|
+
}
|
|
5076
|
+
function defaultUserConfigEnvVar(key) {
|
|
5077
|
+
return key.replace(/[^A-Za-z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
|
|
5078
|
+
}
|
|
5079
|
+
function extractEnvReference(value) {
|
|
5080
|
+
if (!value) return void 0;
|
|
5081
|
+
const match = value.match(/^\$\{([A-Za-z_][A-Za-z0-9_]*)\}$/);
|
|
5082
|
+
return match?.[1];
|
|
5083
|
+
}
|
|
5084
|
+
function isPlaceholderSecretValue(value) {
|
|
5085
|
+
if (typeof value !== "string") return false;
|
|
5086
|
+
const normalized = value.trim();
|
|
5087
|
+
if (normalized === "") return false;
|
|
5088
|
+
return PLACEHOLDER_SECRET_PATTERNS.some((pattern) => pattern.test(normalized));
|
|
5089
|
+
}
|
|
5090
|
+
function isRuntimePlatformManaged(config, target, server) {
|
|
5091
|
+
if (server.transport === "stdio") return false;
|
|
5092
|
+
if (server.auth?.type === "platform") {
|
|
5093
|
+
return true;
|
|
5094
|
+
}
|
|
5095
|
+
if (target === "claude-code") {
|
|
5096
|
+
return config.platforms?.["claude-code"]?.mcpAuth === "platform";
|
|
5097
|
+
}
|
|
5098
|
+
if (target === "cursor") {
|
|
5099
|
+
return config.platforms?.cursor?.mcpAuth === "platform";
|
|
5100
|
+
}
|
|
5101
|
+
return false;
|
|
5102
|
+
}
|
|
5103
|
+
function targetApplies(entry, target) {
|
|
5104
|
+
return !entry.targets || entry.targets.includes(target);
|
|
5105
|
+
}
|
|
5106
|
+
function dedupeUserConfigEntries(entries) {
|
|
5107
|
+
const deduped = [];
|
|
5108
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
5109
|
+
const seenEnvVars = /* @__PURE__ */ new Set();
|
|
5110
|
+
for (const entry of entries) {
|
|
5111
|
+
const envVar = entry.envVar?.trim();
|
|
5112
|
+
if (seenKeys.has(entry.key)) continue;
|
|
5113
|
+
if (envVar && seenEnvVars.has(envVar)) continue;
|
|
5114
|
+
deduped.push(entry);
|
|
5115
|
+
seenKeys.add(entry.key);
|
|
5116
|
+
if (envVar) seenEnvVars.add(envVar);
|
|
5117
|
+
}
|
|
5118
|
+
return deduped;
|
|
5119
|
+
}
|
|
5120
|
+
function collectUserConfigEntries(config, platforms = config.targets) {
|
|
5121
|
+
const explicitEntries = (config.userConfig ?? []).filter((entry) => !entry.targets || entry.targets.some((target) => platforms.includes(target))).map((entry) => ({ ...entry, source: "explicit" }));
|
|
5122
|
+
const derivedEntries = [];
|
|
5123
|
+
for (const [serverName, server] of Object.entries(config.mcp ?? {})) {
|
|
5124
|
+
const applicableTargets = platforms.filter((target) => !isRuntimePlatformManaged(config, target, server));
|
|
5125
|
+
if (server.auth?.type && server.auth.type !== "none" && server.auth.type !== "platform" && ENV_VAR_NAME.test(server.auth.envVar) && applicableTargets.length > 0) {
|
|
5126
|
+
derivedEntries.push({
|
|
5127
|
+
key: normalizeUserConfigKey(server.auth.envVar),
|
|
5128
|
+
title: humanizeUserConfigLabel(server.auth.envVar),
|
|
5129
|
+
description: `Authentication credential for the ${serverName} MCP server.`,
|
|
5130
|
+
type: "secret",
|
|
5131
|
+
required: true,
|
|
5132
|
+
envVar: server.auth.envVar,
|
|
5133
|
+
targets: applicableTargets,
|
|
5134
|
+
source: "mcp-auth"
|
|
5135
|
+
});
|
|
5136
|
+
}
|
|
5137
|
+
if (server.transport === "stdio") {
|
|
5138
|
+
for (const [key, rawValue] of Object.entries(server.env ?? {})) {
|
|
5139
|
+
const envVar = extractEnvReference(rawValue) ?? (ENV_VAR_NAME.test(key) ? key : void 0);
|
|
5140
|
+
if (!envVar || !ENV_VAR_NAME.test(envVar)) continue;
|
|
5141
|
+
if (extractEnvReference(rawValue) === void 0 && rawValue !== "") {
|
|
5142
|
+
continue;
|
|
5143
|
+
}
|
|
5144
|
+
derivedEntries.push({
|
|
5145
|
+
key: normalizeUserConfigKey(envVar),
|
|
5146
|
+
title: humanizeUserConfigLabel(envVar),
|
|
5147
|
+
description: `Environment value required to launch the ${serverName} stdio MCP server.`,
|
|
5148
|
+
type: "secret",
|
|
5149
|
+
required: true,
|
|
5150
|
+
envVar,
|
|
5151
|
+
targets: applicableTargets.length > 0 ? applicableTargets : platforms,
|
|
5152
|
+
source: "mcp-env"
|
|
5153
|
+
});
|
|
5154
|
+
}
|
|
5155
|
+
}
|
|
5156
|
+
}
|
|
5157
|
+
return dedupeUserConfigEntries([
|
|
5158
|
+
...explicitEntries,
|
|
5159
|
+
...derivedEntries
|
|
5160
|
+
]);
|
|
5161
|
+
}
|
|
5162
|
+
function resolveUserConfigEntriesForTarget(entries, target) {
|
|
5163
|
+
return entries.filter(({ field }) => targetApplies(field, target));
|
|
5164
|
+
}
|
|
5165
|
+
function buildUserConfigEnvMap(entries) {
|
|
5166
|
+
const env = {};
|
|
5167
|
+
for (const entry of entries) {
|
|
5168
|
+
if (!entry.envVar) continue;
|
|
5169
|
+
env[entry.envVar] = String(entry.value);
|
|
5170
|
+
}
|
|
5171
|
+
return env;
|
|
5172
|
+
}
|
|
5173
|
+
function buildUserConfigValueMap(entries) {
|
|
5174
|
+
const values = {};
|
|
5175
|
+
for (const entry of entries) {
|
|
5176
|
+
values[entry.field.key] = entry.value;
|
|
5177
|
+
}
|
|
5178
|
+
return values;
|
|
5179
|
+
}
|
|
5180
|
+
|
|
5181
|
+
// src/mcp-native-overrides.ts
|
|
5182
|
+
function asRecord(value) {
|
|
5183
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
5184
|
+
return value;
|
|
5185
|
+
}
|
|
5186
|
+
function cloneRecord(value) {
|
|
5187
|
+
return JSON.parse(JSON.stringify(value));
|
|
5188
|
+
}
|
|
5189
|
+
function firstString(...values) {
|
|
5190
|
+
for (const value of values) {
|
|
5191
|
+
if (typeof value === "string" && value.trim()) return value.trim();
|
|
5192
|
+
}
|
|
5193
|
+
return void 0;
|
|
5194
|
+
}
|
|
5195
|
+
function readStringRecord(value) {
|
|
5196
|
+
const record = asRecord(value);
|
|
5197
|
+
if (!record) return void 0;
|
|
5198
|
+
const stringRecord = Object.fromEntries(
|
|
5199
|
+
Object.entries(record).filter(([, entryValue]) => typeof entryValue === "string")
|
|
5200
|
+
);
|
|
5201
|
+
return Object.keys(stringRecord).length > 0 ? stringRecord : void 0;
|
|
5202
|
+
}
|
|
5203
|
+
function extractNativeMcpAuthConfig(cfg) {
|
|
5204
|
+
const preserved = {};
|
|
5205
|
+
const auth = asRecord(cfg.auth);
|
|
5206
|
+
if (auth) preserved.auth = cloneRecord(auth);
|
|
5207
|
+
const bearerTokenEnv = firstString(cfg.bearer_token_env_var, cfg.bearerTokenEnvVar);
|
|
5208
|
+
if (typeof cfg.bearer_token_env_var === "string") {
|
|
5209
|
+
preserved.bearer_token_env_var = cfg.bearer_token_env_var;
|
|
5210
|
+
} else if (typeof cfg.bearerTokenEnvVar === "string" && bearerTokenEnv) {
|
|
5211
|
+
preserved.bearerTokenEnvVar = bearerTokenEnv;
|
|
5212
|
+
}
|
|
5213
|
+
for (const key of ["env_http_headers", "envHttpHeaders", "headers", "http_headers", "httpHeaders"]) {
|
|
5214
|
+
const value = asRecord(cfg[key]);
|
|
5215
|
+
if (value) {
|
|
5216
|
+
preserved[key] = cloneRecord(value);
|
|
5217
|
+
}
|
|
5218
|
+
}
|
|
5219
|
+
return Object.keys(preserved).length > 0 ? preserved : void 0;
|
|
5220
|
+
}
|
|
5221
|
+
function buildNativeMcpPlatformOverrides(platform, servers) {
|
|
5222
|
+
const preservedServers = {};
|
|
5223
|
+
for (const [serverName, rawConfig] of Object.entries(servers)) {
|
|
5224
|
+
const cfg = asRecord(rawConfig);
|
|
5225
|
+
if (!cfg) continue;
|
|
5226
|
+
const nativeAuth = extractNativeMcpAuthConfig(cfg);
|
|
5227
|
+
if (!nativeAuth) continue;
|
|
5228
|
+
preservedServers[serverName] = nativeAuth;
|
|
5229
|
+
}
|
|
5230
|
+
if (Object.keys(preservedServers).length === 0) return void 0;
|
|
5231
|
+
return {
|
|
5232
|
+
[platform]: {
|
|
5233
|
+
mcpServers: preservedServers
|
|
5234
|
+
}
|
|
5235
|
+
};
|
|
5236
|
+
}
|
|
5237
|
+
function extractEnvVar(value) {
|
|
5238
|
+
const match = value.match(/\$\{(?:env:)?([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*)/);
|
|
5239
|
+
return match?.[1] ?? match?.[2];
|
|
5240
|
+
}
|
|
5241
|
+
function envVarToKey(envVar) {
|
|
5242
|
+
return envVar.toLowerCase().replace(/_/g, "-");
|
|
5243
|
+
}
|
|
5244
|
+
function getNativeMcpServerOverride(config, platform, serverName) {
|
|
5245
|
+
const platformConfig = asRecord(config.platforms?.[platform]);
|
|
5246
|
+
const mcpServers = asRecord(platformConfig?.mcpServers);
|
|
5247
|
+
const serverOverride = asRecord(mcpServers?.[serverName]);
|
|
5248
|
+
return serverOverride ? cloneRecord(serverOverride) : void 0;
|
|
5249
|
+
}
|
|
5250
|
+
function getNativeJsonHeadersOverride(config, platform, serverName) {
|
|
5251
|
+
const override = getNativeMcpServerOverride(config, platform, serverName);
|
|
5252
|
+
return readStringRecord(override?.headers);
|
|
5253
|
+
}
|
|
5254
|
+
function getNativeCodexMcpEntryOverride(config, serverName) {
|
|
5255
|
+
const override = getNativeMcpServerOverride(config, "codex", serverName);
|
|
5256
|
+
if (!override) return void 0;
|
|
5257
|
+
const entry = {};
|
|
5258
|
+
const bearerTokenEnv = firstString(override.bearer_token_env_var, override.bearerTokenEnvVar);
|
|
5259
|
+
if (bearerTokenEnv) entry.bearer_token_env_var = bearerTokenEnv;
|
|
5260
|
+
const envHttpHeaders = readStringRecord(override.env_http_headers ?? override.envHttpHeaders);
|
|
5261
|
+
if (envHttpHeaders) entry.env_http_headers = envHttpHeaders;
|
|
5262
|
+
const httpHeaders = readStringRecord(override.http_headers ?? override.httpHeaders);
|
|
5263
|
+
if (httpHeaders) entry.http_headers = httpHeaders;
|
|
5264
|
+
const auth = asRecord(override.auth);
|
|
5265
|
+
if (auth) entry.auth = cloneRecord(auth);
|
|
5266
|
+
return Object.keys(entry).length > 0 ? entry : void 0;
|
|
5267
|
+
}
|
|
5268
|
+
function collectNativeMcpAuthUserConfigEntries(config, platforms, existingFields) {
|
|
5269
|
+
const next = [];
|
|
5270
|
+
const seenKeys = new Set(existingFields.map((field) => field.key));
|
|
5271
|
+
const seenEnvVars = new Set(
|
|
5272
|
+
existingFields.map((field) => field.envVar ?? defaultUserConfigEnvVar(field.key))
|
|
5273
|
+
);
|
|
5274
|
+
const pushDerived = (envVar, platform, serverName) => {
|
|
5275
|
+
if (!envVar || seenEnvVars.has(envVar)) return;
|
|
5276
|
+
const key = envVarToKey(envVar);
|
|
5277
|
+
if (seenKeys.has(key)) return;
|
|
5278
|
+
seenEnvVars.add(envVar);
|
|
5279
|
+
seenKeys.add(key);
|
|
5280
|
+
next.push({
|
|
5281
|
+
key,
|
|
5282
|
+
title: envVar,
|
|
5283
|
+
description: `Derived from native ${platform} MCP auth for ${serverName}.`,
|
|
5284
|
+
type: "secret",
|
|
5285
|
+
required: true,
|
|
5286
|
+
envVar,
|
|
5287
|
+
targets: [platform]
|
|
5288
|
+
});
|
|
5289
|
+
};
|
|
5290
|
+
for (const platform of platforms) {
|
|
5291
|
+
const platformConfig = asRecord(config.platforms?.[platform]);
|
|
5292
|
+
const mcpServers = asRecord(platformConfig?.mcpServers);
|
|
5293
|
+
if (!mcpServers) continue;
|
|
5294
|
+
for (const [serverName, rawOverride] of Object.entries(mcpServers)) {
|
|
5295
|
+
const override = asRecord(rawOverride);
|
|
5296
|
+
if (!override) continue;
|
|
5297
|
+
const bearerTokenEnv = firstString(override.bearer_token_env_var, override.bearerTokenEnvVar);
|
|
5298
|
+
if (bearerTokenEnv) pushDerived(bearerTokenEnv, platform, serverName);
|
|
5299
|
+
for (const record of [
|
|
5300
|
+
readStringRecord(override.env_http_headers ?? override.envHttpHeaders),
|
|
5301
|
+
readStringRecord(override.headers),
|
|
5302
|
+
readStringRecord(override.http_headers ?? override.httpHeaders)
|
|
5303
|
+
]) {
|
|
5304
|
+
for (const value of Object.values(record ?? {})) {
|
|
5305
|
+
const envVar = extractEnvVar(value) ?? value;
|
|
5306
|
+
pushDerived(envVar, platform, serverName);
|
|
5307
|
+
}
|
|
5308
|
+
}
|
|
5309
|
+
}
|
|
5310
|
+
}
|
|
5311
|
+
return next;
|
|
5312
|
+
}
|
|
5313
|
+
|
|
5058
5314
|
// src/generators/base.ts
|
|
5059
5315
|
var Generator = class {
|
|
5060
5316
|
constructor(config, rootDir) {
|
|
@@ -5162,6 +5418,10 @@ var Generator = class {
|
|
|
5162
5418
|
entry.headers = headers;
|
|
5163
5419
|
}
|
|
5164
5420
|
}
|
|
5421
|
+
const nativeHeaders = getNativeJsonHeadersOverride(this.config, this.platform, name);
|
|
5422
|
+
if (nativeHeaders && this.getMcpAuthMode() !== "platform" && remoteServer.auth?.type !== "platform") {
|
|
5423
|
+
entry.headers = nativeHeaders;
|
|
5424
|
+
}
|
|
5165
5425
|
if (transformRemoteEntry) {
|
|
5166
5426
|
entry = transformRemoteEntry({ name, server: remoteServer, entry });
|
|
5167
5427
|
}
|
|
@@ -5211,9 +5471,40 @@ var Generator = class {
|
|
|
5211
5471
|
// src/generators/shared/claude-family.ts
|
|
5212
5472
|
import { resolve as resolve7 } from "path";
|
|
5213
5473
|
|
|
5474
|
+
// src/hook-events.ts
|
|
5475
|
+
var CURSOR_SUPPORTED_HOOK_EVENTS = [
|
|
5476
|
+
"sessionStart",
|
|
5477
|
+
"sessionEnd",
|
|
5478
|
+
"preToolUse",
|
|
5479
|
+
"postToolUse",
|
|
5480
|
+
"postToolUseFailure",
|
|
5481
|
+
"subagentStart",
|
|
5482
|
+
"subagentStop",
|
|
5483
|
+
"beforeShellExecution",
|
|
5484
|
+
"afterShellExecution",
|
|
5485
|
+
"beforeMCPExecution",
|
|
5486
|
+
"afterMCPExecution",
|
|
5487
|
+
"beforeReadFile",
|
|
5488
|
+
"afterFileEdit",
|
|
5489
|
+
"beforeSubmitPrompt",
|
|
5490
|
+
"preCompact",
|
|
5491
|
+
"stop",
|
|
5492
|
+
"afterAgentResponse",
|
|
5493
|
+
"afterAgentThought",
|
|
5494
|
+
"beforeTabFileRead",
|
|
5495
|
+
"afterTabFileEdit"
|
|
5496
|
+
];
|
|
5497
|
+
var PASCAL_CASE_HOOK_ALIASES = {
|
|
5498
|
+
beforeSubmitPrompt: "UserPromptSubmit"
|
|
5499
|
+
};
|
|
5500
|
+
function mapHookEventToPascalCase(event) {
|
|
5501
|
+
return PASCAL_CASE_HOOK_ALIASES[event] ?? event.charAt(0).toUpperCase() + event.slice(1);
|
|
5502
|
+
}
|
|
5503
|
+
|
|
5214
5504
|
// src/hook-translation-registry.ts
|
|
5215
5505
|
var HOOK_PLATFORM_REGISTRY = {
|
|
5216
5506
|
"claude-code": {
|
|
5507
|
+
supportedTypes: ["command", "http", "mcp_tool", "prompt", "agent"],
|
|
5217
5508
|
fields: {
|
|
5218
5509
|
prompt: {
|
|
5219
5510
|
mode: "preserve",
|
|
@@ -5237,6 +5528,9 @@ var HOOK_PLATFORM_REGISTRY = {
|
|
|
5237
5528
|
}
|
|
5238
5529
|
},
|
|
5239
5530
|
cursor: {
|
|
5531
|
+
supportedTypes: ["command", "prompt"],
|
|
5532
|
+
supportedEvents: CURSOR_SUPPORTED_HOOK_EVENTS,
|
|
5533
|
+
unsupportedEventReason: `Cursor currently documents only ${CURSOR_SUPPORTED_HOOK_EVENTS.join(", ")} for hook configuration.`,
|
|
5240
5534
|
fields: {
|
|
5241
5535
|
prompt: { mode: "preserve" },
|
|
5242
5536
|
matcher: { mode: "preserve" },
|
|
@@ -5245,6 +5539,7 @@ var HOOK_PLATFORM_REGISTRY = {
|
|
|
5245
5539
|
}
|
|
5246
5540
|
},
|
|
5247
5541
|
codex: {
|
|
5542
|
+
supportedTypes: ["command"],
|
|
5248
5543
|
supportedEvents: ["SessionStart", "PreToolUse", "PermissionRequest", "PostToolUse", "UserPromptSubmit", "Stop"],
|
|
5249
5544
|
unsupportedEventReason: "Codex currently documents only SessionStart, PreToolUse, PermissionRequest, PostToolUse, UserPromptSubmit, and Stop for hook configuration.",
|
|
5250
5545
|
fields: {
|
|
@@ -5255,6 +5550,7 @@ var HOOK_PLATFORM_REGISTRY = {
|
|
|
5255
5550
|
}
|
|
5256
5551
|
},
|
|
5257
5552
|
opencode: {
|
|
5553
|
+
supportedTypes: ["command"],
|
|
5258
5554
|
fields: {
|
|
5259
5555
|
prompt: { mode: "drop" },
|
|
5260
5556
|
matcher: { mode: "preserve" },
|
|
@@ -5322,6 +5618,9 @@ var HOOK_PLATFORM_REGISTRY = {
|
|
|
5322
5618
|
function getSupportedHookEvents(platform) {
|
|
5323
5619
|
return HOOK_PLATFORM_REGISTRY[platform]?.supportedEvents ?? [];
|
|
5324
5620
|
}
|
|
5621
|
+
function getSupportedHookTypes(platform) {
|
|
5622
|
+
return HOOK_PLATFORM_REGISTRY[platform]?.supportedTypes ?? [];
|
|
5623
|
+
}
|
|
5325
5624
|
function getUnsupportedHookEventReason(platform) {
|
|
5326
5625
|
return HOOK_PLATFORM_REGISTRY[platform]?.unsupportedEventReason ?? null;
|
|
5327
5626
|
}
|
|
@@ -5330,6 +5629,11 @@ function isHookEventSupported(platform, event) {
|
|
|
5330
5629
|
if (supportedEvents.length === 0) return true;
|
|
5331
5630
|
return supportedEvents.includes(event);
|
|
5332
5631
|
}
|
|
5632
|
+
function isHookTypeSupported(platform, type) {
|
|
5633
|
+
const supportedTypes = getSupportedHookTypes(platform);
|
|
5634
|
+
if (supportedTypes.length === 0) return true;
|
|
5635
|
+
return supportedTypes.includes(type);
|
|
5636
|
+
}
|
|
5333
5637
|
function getHookFieldCapability(platform, field) {
|
|
5334
5638
|
return HOOK_PLATFORM_REGISTRY[platform]?.fields[field] ?? { mode: "drop" };
|
|
5335
5639
|
}
|
|
@@ -5345,6 +5649,77 @@ function isHookFieldPreserved(platform, field, event) {
|
|
|
5345
5649
|
function getHookFieldSupportedEvents(platform, field) {
|
|
5346
5650
|
return getHookFieldCapability(platform, field).supportedEvents ?? [];
|
|
5347
5651
|
}
|
|
5652
|
+
function getHookTypeTranslationIssue(platform, type) {
|
|
5653
|
+
if (platform === "cursor") {
|
|
5654
|
+
return {
|
|
5655
|
+
code: "cursor-hook-type-unsupported",
|
|
5656
|
+
message: `Cursor does not document hook type "${type}". Pluxx currently preserves only command and prompt hooks on the Cursor hook surface.`
|
|
5657
|
+
};
|
|
5658
|
+
}
|
|
5659
|
+
if (platform === "codex") {
|
|
5660
|
+
return {
|
|
5661
|
+
code: "codex-hook-type-drop",
|
|
5662
|
+
message: `Codex currently bundles only command-hook entries from Pluxx. ${type} hooks will be dropped from the generated Codex bundle.`
|
|
5663
|
+
};
|
|
5664
|
+
}
|
|
5665
|
+
if (platform === "opencode") {
|
|
5666
|
+
return {
|
|
5667
|
+
code: "opencode-hook-type-drop",
|
|
5668
|
+
message: `The current OpenCode runtime wrapper only emits command hooks. ${type} hooks will be dropped from the generated OpenCode plugin.`
|
|
5669
|
+
};
|
|
5670
|
+
}
|
|
5671
|
+
return null;
|
|
5672
|
+
}
|
|
5673
|
+
function getPromptHookTranslationIssue(platform, event) {
|
|
5674
|
+
if (platform === "claude-code") {
|
|
5675
|
+
if (event && isHookFieldPreserved(platform, "prompt", event)) return null;
|
|
5676
|
+
const supportedEvents = getHookFieldSupportedEvents(platform, "prompt");
|
|
5677
|
+
return {
|
|
5678
|
+
code: "claude-prompt-hook-degrade",
|
|
5679
|
+
message: `Claude currently preserves prompt hooks only on ${supportedEvents.join(", ")}. Prompt hooks on ${event ?? "other events"} will be dropped from generated Claude output.`
|
|
5680
|
+
};
|
|
5681
|
+
}
|
|
5682
|
+
if (platform === "codex") {
|
|
5683
|
+
return {
|
|
5684
|
+
code: "codex-prompt-hook-drop",
|
|
5685
|
+
message: "Codex currently receives only command-hook entries from Pluxx. Prompt hooks will be dropped from the generated Codex bundle."
|
|
5686
|
+
};
|
|
5687
|
+
}
|
|
5688
|
+
if (platform === "opencode") {
|
|
5689
|
+
return {
|
|
5690
|
+
code: "opencode-prompt-hook-drop",
|
|
5691
|
+
message: "The current OpenCode runtime wrapper only emits command hooks. Prompt hooks will be dropped from the generated OpenCode plugin."
|
|
5692
|
+
};
|
|
5693
|
+
}
|
|
5694
|
+
return null;
|
|
5695
|
+
}
|
|
5696
|
+
function getHookFieldTranslationIssue(platform, field) {
|
|
5697
|
+
if (platform === "claude-code" && field === "failClosed") {
|
|
5698
|
+
return {
|
|
5699
|
+
code: "claude-hook-failclosed-degrade",
|
|
5700
|
+
message: "Claude hook entries currently drop `failClosed` in generated output. Keep this behavior host-specific or verify the generated hook bundle carefully."
|
|
5701
|
+
};
|
|
5702
|
+
}
|
|
5703
|
+
if (platform === "claude-code" && field === "loop_limit") {
|
|
5704
|
+
return {
|
|
5705
|
+
code: "claude-hook-loop-limit-degrade",
|
|
5706
|
+
message: "Claude outputs currently drop `loop_limit`. Recursive hook protection is not preserved there today."
|
|
5707
|
+
};
|
|
5708
|
+
}
|
|
5709
|
+
if (platform === "codex" && field === "loop_limit") {
|
|
5710
|
+
return {
|
|
5711
|
+
code: "codex-hook-loop-limit-drop",
|
|
5712
|
+
message: "Codex hook companions currently drop `loop_limit`. Only command, matcher, timeout, and failClosed survive there today."
|
|
5713
|
+
};
|
|
5714
|
+
}
|
|
5715
|
+
if (platform === "opencode" && field === "loop_limit") {
|
|
5716
|
+
return {
|
|
5717
|
+
code: "opencode-hook-loop-limit-drop",
|
|
5718
|
+
message: "OpenCode runtime hooks currently drop `loop_limit`. Recursive hook protection is still Cursor-first in Pluxx."
|
|
5719
|
+
};
|
|
5720
|
+
}
|
|
5721
|
+
return null;
|
|
5722
|
+
}
|
|
5348
5723
|
|
|
5349
5724
|
// src/generators/hooks-warning.ts
|
|
5350
5725
|
function warnDroppedHookFields(platform, event, entries) {
|
|
@@ -5374,36 +5749,6 @@ function warnDroppedHookFields(platform, event, entries) {
|
|
|
5374
5749
|
}
|
|
5375
5750
|
}
|
|
5376
5751
|
|
|
5377
|
-
// src/hook-events.ts
|
|
5378
|
-
var CURSOR_SUPPORTED_HOOK_EVENTS = [
|
|
5379
|
-
"sessionStart",
|
|
5380
|
-
"sessionEnd",
|
|
5381
|
-
"preToolUse",
|
|
5382
|
-
"postToolUse",
|
|
5383
|
-
"postToolUseFailure",
|
|
5384
|
-
"subagentStart",
|
|
5385
|
-
"subagentStop",
|
|
5386
|
-
"beforeShellExecution",
|
|
5387
|
-
"afterShellExecution",
|
|
5388
|
-
"beforeMCPExecution",
|
|
5389
|
-
"afterMCPExecution",
|
|
5390
|
-
"beforeReadFile",
|
|
5391
|
-
"afterFileEdit",
|
|
5392
|
-
"beforeSubmitPrompt",
|
|
5393
|
-
"preCompact",
|
|
5394
|
-
"stop",
|
|
5395
|
-
"afterAgentResponse",
|
|
5396
|
-
"afterAgentThought",
|
|
5397
|
-
"beforeTabFileRead",
|
|
5398
|
-
"afterTabFileEdit"
|
|
5399
|
-
];
|
|
5400
|
-
var PASCAL_CASE_HOOK_ALIASES = {
|
|
5401
|
-
beforeSubmitPrompt: "UserPromptSubmit"
|
|
5402
|
-
};
|
|
5403
|
-
function mapHookEventToPascalCase(event) {
|
|
5404
|
-
return PASCAL_CASE_HOOK_ALIASES[event] ?? event.charAt(0).toUpperCase() + event.slice(1);
|
|
5405
|
-
}
|
|
5406
|
-
|
|
5407
5752
|
// src/readiness.ts
|
|
5408
5753
|
function getRuntimeReadinessPlan(readiness) {
|
|
5409
5754
|
if (!readiness || readiness.dependencies.length === 0 && readiness.gates.length === 0) {
|
|
@@ -6144,7 +6489,10 @@ async function writeMcpConfig(config, platform, writeJson) {
|
|
|
6144
6489
|
mcpServers[name] = entry;
|
|
6145
6490
|
continue;
|
|
6146
6491
|
}
|
|
6147
|
-
|
|
6492
|
+
const nativeHeaders = getNativeJsonHeadersOverride(config, platform, name);
|
|
6493
|
+
if (nativeHeaders) {
|
|
6494
|
+
entry.headers = nativeHeaders;
|
|
6495
|
+
} else if (server.auth?.type === "bearer" && server.auth.envVar) {
|
|
6148
6496
|
entry.headers = {
|
|
6149
6497
|
Authorization: `Bearer \${${server.auth.envVar}}`
|
|
6150
6498
|
};
|
|
@@ -6338,7 +6686,7 @@ function defaultMapEventName(event) {
|
|
|
6338
6686
|
}
|
|
6339
6687
|
|
|
6340
6688
|
// src/generators/claude-code/index.ts
|
|
6341
|
-
import { existsSync as
|
|
6689
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync2, readFileSync as readFileSync6, readdirSync as readdirSync4, writeFileSync } from "fs";
|
|
6342
6690
|
import { basename as basename2, join as join2 } from "path";
|
|
6343
6691
|
|
|
6344
6692
|
// src/delegation.ts
|
|
@@ -6380,10 +6728,204 @@ function asMap2(value) {
|
|
|
6380
6728
|
return value;
|
|
6381
6729
|
}
|
|
6382
6730
|
|
|
6383
|
-
// src/
|
|
6731
|
+
// src/commands.ts
|
|
6384
6732
|
import { existsSync as existsSync7, readdirSync as readdirSync2, readFileSync as readFileSync4, statSync as statSync2 } from "fs";
|
|
6385
6733
|
import { relative as relative2, resolve as resolve8 } from "path";
|
|
6386
6734
|
function unquote(value) {
|
|
6735
|
+
const trimmed = value.trim();
|
|
6736
|
+
if (trimmed.length >= 2 && (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'"))) {
|
|
6737
|
+
return { value: trimmed.slice(1, -1), quoted: true };
|
|
6738
|
+
}
|
|
6739
|
+
return { value: trimmed, quoted: false };
|
|
6740
|
+
}
|
|
6741
|
+
function firstHeading2(content) {
|
|
6742
|
+
const lines = content.split(/\r?\n/);
|
|
6743
|
+
for (const line of lines) {
|
|
6744
|
+
const match = line.match(/^#\s+(.*)$/);
|
|
6745
|
+
if (match?.[1]?.trim()) return match[1].trim();
|
|
6746
|
+
}
|
|
6747
|
+
return void 0;
|
|
6748
|
+
}
|
|
6749
|
+
function splitMarkdownFrontmatter2(content) {
|
|
6750
|
+
const lines = content.split(/\r?\n/);
|
|
6751
|
+
if (lines[0]?.trim() !== "---") {
|
|
6752
|
+
return {
|
|
6753
|
+
frontmatterLines: [],
|
|
6754
|
+
body: content
|
|
6755
|
+
};
|
|
6756
|
+
}
|
|
6757
|
+
let endIndex = -1;
|
|
6758
|
+
for (let i = 1; i < lines.length; i += 1) {
|
|
6759
|
+
if (lines[i].trim() === "---") {
|
|
6760
|
+
endIndex = i;
|
|
6761
|
+
break;
|
|
6762
|
+
}
|
|
6763
|
+
}
|
|
6764
|
+
if (endIndex === -1) {
|
|
6765
|
+
return {
|
|
6766
|
+
frontmatterLines: [],
|
|
6767
|
+
body: content
|
|
6768
|
+
};
|
|
6769
|
+
}
|
|
6770
|
+
return {
|
|
6771
|
+
frontmatterLines: lines.slice(1, endIndex),
|
|
6772
|
+
body: lines.slice(endIndex + 1).join("\n")
|
|
6773
|
+
};
|
|
6774
|
+
}
|
|
6775
|
+
function stripYamlScalar(value) {
|
|
6776
|
+
return unquote(value).value.trim();
|
|
6777
|
+
}
|
|
6778
|
+
function parseFrontmatterFields(frontmatterLines) {
|
|
6779
|
+
const fields = /* @__PURE__ */ new Map();
|
|
6780
|
+
for (const line of frontmatterLines) {
|
|
6781
|
+
const trimmed = line.trim();
|
|
6782
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
6783
|
+
const match = /^([A-Za-z0-9_-]+)\s*:\s*(.*)$/.exec(trimmed);
|
|
6784
|
+
if (!match) continue;
|
|
6785
|
+
const key = match[1];
|
|
6786
|
+
const rawValue = match[2];
|
|
6787
|
+
const parsed = unquote(rawValue);
|
|
6788
|
+
fields.set(key, {
|
|
6789
|
+
key,
|
|
6790
|
+
value: parsed.value,
|
|
6791
|
+
rawValue: rawValue.trim(),
|
|
6792
|
+
quoted: parsed.quoted
|
|
6793
|
+
});
|
|
6794
|
+
}
|
|
6795
|
+
return fields;
|
|
6796
|
+
}
|
|
6797
|
+
function parseCommandFrontmatterDescription(frontmatterLines) {
|
|
6798
|
+
for (const line of frontmatterLines) {
|
|
6799
|
+
const match = /^description:\s*(.+)\s*$/i.exec(line.trim());
|
|
6800
|
+
if (match?.[1]) {
|
|
6801
|
+
return stripYamlScalar(match[1]);
|
|
6802
|
+
}
|
|
6803
|
+
}
|
|
6804
|
+
return void 0;
|
|
6805
|
+
}
|
|
6806
|
+
function parseCommandFrontmatterString(frontmatterFields, key) {
|
|
6807
|
+
return frontmatterFields.get(key)?.value;
|
|
6808
|
+
}
|
|
6809
|
+
function parseCommandFrontmatterBoolean(frontmatterFields, key) {
|
|
6810
|
+
const value = parseCommandFrontmatterString(frontmatterFields, key);
|
|
6811
|
+
if (!value) return void 0;
|
|
6812
|
+
if (/^true$/i.test(value)) return true;
|
|
6813
|
+
if (/^false$/i.test(value)) return false;
|
|
6814
|
+
return void 0;
|
|
6815
|
+
}
|
|
6816
|
+
function parseInlineStringArray(rawValue) {
|
|
6817
|
+
const trimmed = rawValue.trim();
|
|
6818
|
+
if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) return [];
|
|
6819
|
+
const inner = trimmed.slice(1, -1).trim();
|
|
6820
|
+
if (!inner) return [];
|
|
6821
|
+
return inner.split(",").map((part) => stripYamlScalar(part)).map((part) => part.trim()).filter(Boolean);
|
|
6822
|
+
}
|
|
6823
|
+
function parseCommandFrontmatterStringArray(frontmatterLines, frontmatterFields, key) {
|
|
6824
|
+
const rawValue = frontmatterFields.get(key)?.rawValue;
|
|
6825
|
+
if (rawValue) {
|
|
6826
|
+
const inlineArray = parseInlineStringArray(rawValue);
|
|
6827
|
+
if (inlineArray.length > 0) return inlineArray;
|
|
6828
|
+
if (rawValue.trim()) return [stripYamlScalar(rawValue)];
|
|
6829
|
+
}
|
|
6830
|
+
const lineIndex = frontmatterLines.findIndex((line) => new RegExp(`^${key}:\\s*$`, "i").test(line.trim()));
|
|
6831
|
+
if (lineIndex === -1) return [];
|
|
6832
|
+
const values = [];
|
|
6833
|
+
for (let index = lineIndex + 1; index < frontmatterLines.length; index += 1) {
|
|
6834
|
+
const itemMatch = /^\s*-\s+(.+)$/.exec(frontmatterLines[index]);
|
|
6835
|
+
if (!itemMatch?.[1]) break;
|
|
6836
|
+
const value = stripYamlScalar(itemMatch[1]).trim();
|
|
6837
|
+
if (value) values.push(value);
|
|
6838
|
+
}
|
|
6839
|
+
return values;
|
|
6840
|
+
}
|
|
6841
|
+
function inferCommandSkillLinks(body) {
|
|
6842
|
+
const skills = /* @__PURE__ */ new Set();
|
|
6843
|
+
for (const match of body.matchAll(/Use the `([^`]+)` skill\./g)) {
|
|
6844
|
+
const skill = match[1]?.trim();
|
|
6845
|
+
if (skill) skills.add(skill);
|
|
6846
|
+
}
|
|
6847
|
+
return [...skills];
|
|
6848
|
+
}
|
|
6849
|
+
function walkMarkdownFiles2(dir) {
|
|
6850
|
+
const entries = readdirSync2(dir);
|
|
6851
|
+
const files = [];
|
|
6852
|
+
for (const entry of entries) {
|
|
6853
|
+
const fullPath = resolve8(dir, entry);
|
|
6854
|
+
const stat = statSync2(fullPath);
|
|
6855
|
+
if (stat.isDirectory()) {
|
|
6856
|
+
files.push(...walkMarkdownFiles2(fullPath));
|
|
6857
|
+
continue;
|
|
6858
|
+
}
|
|
6859
|
+
if (stat.isFile() && entry.endsWith(".md")) {
|
|
6860
|
+
files.push(fullPath);
|
|
6861
|
+
}
|
|
6862
|
+
}
|
|
6863
|
+
return files;
|
|
6864
|
+
}
|
|
6865
|
+
function readCanonicalCommandFiles(commandsDir) {
|
|
6866
|
+
if (!commandsDir || !existsSync7(commandsDir)) return [];
|
|
6867
|
+
return walkMarkdownFiles2(commandsDir).sort((a, b) => a.localeCompare(b)).map((filePath) => {
|
|
6868
|
+
const content = readFileSync4(filePath, "utf-8");
|
|
6869
|
+
const { frontmatterLines, body } = splitMarkdownFrontmatter2(content);
|
|
6870
|
+
const frontmatterFields = parseFrontmatterFields(frontmatterLines);
|
|
6871
|
+
const relativeStem = relative2(commandsDir, filePath).replace(/\\/g, "/").replace(/\.md$/i, "");
|
|
6872
|
+
const commandId = relativeStem.replace(/\//g, "-").toLowerCase();
|
|
6873
|
+
const title = parseCommandFrontmatterString(frontmatterFields, "title") ?? firstHeading2(body) ?? commandId;
|
|
6874
|
+
const skills = [
|
|
6875
|
+
.../* @__PURE__ */ new Set([
|
|
6876
|
+
...parseCommandFrontmatterStringArray(frontmatterLines, frontmatterFields, "skills"),
|
|
6877
|
+
...(() => {
|
|
6878
|
+
const skill = parseCommandFrontmatterString(frontmatterFields, "skill");
|
|
6879
|
+
return skill ? [skill] : [];
|
|
6880
|
+
})(),
|
|
6881
|
+
...inferCommandSkillLinks(body)
|
|
6882
|
+
])
|
|
6883
|
+
];
|
|
6884
|
+
return {
|
|
6885
|
+
filePath,
|
|
6886
|
+
relativeStem,
|
|
6887
|
+
commandId,
|
|
6888
|
+
frontmatterLines,
|
|
6889
|
+
frontmatterFields,
|
|
6890
|
+
title,
|
|
6891
|
+
description: parseCommandFrontmatterDescription(frontmatterLines),
|
|
6892
|
+
whenToUse: parseCommandFrontmatterString(frontmatterFields, "when_to_use"),
|
|
6893
|
+
argumentHint: parseCommandFrontmatterString(frontmatterFields, "argument-hint"),
|
|
6894
|
+
arguments: parseCommandFrontmatterStringArray(frontmatterLines, frontmatterFields, "arguments"),
|
|
6895
|
+
examples: parseCommandFrontmatterStringArray(frontmatterLines, frontmatterFields, "examples"),
|
|
6896
|
+
...skills[0] ? { skill: skills[0] } : {},
|
|
6897
|
+
skills,
|
|
6898
|
+
agent: parseCommandFrontmatterString(frontmatterFields, "agent"),
|
|
6899
|
+
subtask: parseCommandFrontmatterBoolean(frontmatterFields, "subtask"),
|
|
6900
|
+
model: parseCommandFrontmatterString(frontmatterFields, "model"),
|
|
6901
|
+
context: parseCommandFrontmatterString(frontmatterFields, "context"),
|
|
6902
|
+
body: body.trim()
|
|
6903
|
+
};
|
|
6904
|
+
});
|
|
6905
|
+
}
|
|
6906
|
+
function getCanonicalCommandMetadata(command2) {
|
|
6907
|
+
return {
|
|
6908
|
+
commandId: command2.commandId,
|
|
6909
|
+
title: command2.title,
|
|
6910
|
+
...command2.description ? { description: command2.description } : {},
|
|
6911
|
+
...command2.whenToUse ? { whenToUse: command2.whenToUse } : {},
|
|
6912
|
+
...command2.argumentHint ? { argumentHint: command2.argumentHint } : {},
|
|
6913
|
+
...command2.arguments.length > 0 ? { arguments: [...command2.arguments] } : { arguments: [] },
|
|
6914
|
+
...command2.examples.length > 0 ? { examples: [...command2.examples] } : { examples: [] },
|
|
6915
|
+
...command2.skill ? { skill: command2.skill } : {},
|
|
6916
|
+
skills: [...command2.skills],
|
|
6917
|
+
...command2.agent ? { agent: command2.agent } : {},
|
|
6918
|
+
...typeof command2.subtask === "boolean" ? { subtask: command2.subtask } : {},
|
|
6919
|
+
...command2.model ? { model: command2.model } : {},
|
|
6920
|
+
...command2.context ? { context: command2.context } : {},
|
|
6921
|
+
template: command2.body
|
|
6922
|
+
};
|
|
6923
|
+
}
|
|
6924
|
+
|
|
6925
|
+
// src/skills.ts
|
|
6926
|
+
import { existsSync as existsSync8, readdirSync as readdirSync3, readFileSync as readFileSync5, statSync as statSync3 } from "fs";
|
|
6927
|
+
import { relative as relative3, resolve as resolve9 } from "path";
|
|
6928
|
+
function unquote2(value) {
|
|
6387
6929
|
const trimmed = value.trim();
|
|
6388
6930
|
if (trimmed.length >= 2) {
|
|
6389
6931
|
if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
@@ -6392,14 +6934,14 @@ function unquote(value) {
|
|
|
6392
6934
|
}
|
|
6393
6935
|
return { value: trimmed, quoted: false };
|
|
6394
6936
|
}
|
|
6395
|
-
function
|
|
6937
|
+
function firstHeading3(content) {
|
|
6396
6938
|
for (const line of content.split(/\r?\n/)) {
|
|
6397
6939
|
const match = /^#\s+(.+)$/.exec(line.trim());
|
|
6398
6940
|
if (match?.[1]?.trim()) return match[1].trim();
|
|
6399
6941
|
}
|
|
6400
6942
|
return void 0;
|
|
6401
6943
|
}
|
|
6402
|
-
function
|
|
6944
|
+
function splitMarkdownFrontmatter3(content) {
|
|
6403
6945
|
const lines = content.split(/\r?\n/);
|
|
6404
6946
|
if (lines[0]?.trim() !== "---") {
|
|
6405
6947
|
return {
|
|
@@ -6428,7 +6970,7 @@ function splitMarkdownFrontmatter2(content) {
|
|
|
6428
6970
|
body: lines.slice(endIndex + 1).join("\n")
|
|
6429
6971
|
};
|
|
6430
6972
|
}
|
|
6431
|
-
function
|
|
6973
|
+
function parseFrontmatterFields2(frontmatterLines) {
|
|
6432
6974
|
const fields = /* @__PURE__ */ new Map();
|
|
6433
6975
|
for (const line of frontmatterLines) {
|
|
6434
6976
|
const trimmed = line.trim();
|
|
@@ -6437,7 +6979,7 @@ function parseFrontmatterFields(frontmatterLines) {
|
|
|
6437
6979
|
if (!match) continue;
|
|
6438
6980
|
const key = match[1];
|
|
6439
6981
|
const rawValue = match[2];
|
|
6440
|
-
const parsed =
|
|
6982
|
+
const parsed = unquote2(rawValue);
|
|
6441
6983
|
fields.set(key, {
|
|
6442
6984
|
key,
|
|
6443
6985
|
value: parsed.value,
|
|
@@ -6453,7 +6995,7 @@ function parseAllowedTools(hasValidFrontmatter, frontmatterLines, frontmatterFie
|
|
|
6453
6995
|
const inlineValue = inlineField?.rawValue.trim() ?? "";
|
|
6454
6996
|
if (inlineValue) {
|
|
6455
6997
|
const raw = inlineValue.startsWith("[") && inlineValue.endsWith("]") ? inlineValue.slice(1, -1) : inlineValue;
|
|
6456
|
-
return raw.split(",").map((part) =>
|
|
6998
|
+
return raw.split(",").map((part) => unquote2(part).value).map((part) => part.trim()).filter(Boolean);
|
|
6457
6999
|
}
|
|
6458
7000
|
const lineIndex = frontmatterLines.findIndex((line) => /^allowed-tools:\s*$/i.test(line.trim()));
|
|
6459
7001
|
if (lineIndex === -1) return [];
|
|
@@ -6461,7 +7003,7 @@ function parseAllowedTools(hasValidFrontmatter, frontmatterLines, frontmatterFie
|
|
|
6461
7003
|
for (let index = lineIndex + 1; index < frontmatterLines.length; index += 1) {
|
|
6462
7004
|
const itemMatch = /^\s*-\s+(.+)$/.exec(frontmatterLines[index]);
|
|
6463
7005
|
if (!itemMatch?.[1]) break;
|
|
6464
|
-
const value =
|
|
7006
|
+
const value = unquote2(itemMatch[1]).value.trim();
|
|
6465
7007
|
if (value) tools.push(value);
|
|
6466
7008
|
}
|
|
6467
7009
|
return tools;
|
|
@@ -6472,17 +7014,17 @@ function parseBooleanField(frontmatterFields, key) {
|
|
|
6472
7014
|
if (value === "false") return false;
|
|
6473
7015
|
return void 0;
|
|
6474
7016
|
}
|
|
6475
|
-
function
|
|
7017
|
+
function parseInlineStringArray2(rawValue) {
|
|
6476
7018
|
const trimmed = rawValue.trim();
|
|
6477
7019
|
if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) return [];
|
|
6478
7020
|
const inner = trimmed.slice(1, -1).trim();
|
|
6479
7021
|
if (!inner) return [];
|
|
6480
|
-
return inner.split(",").map((part) =>
|
|
7022
|
+
return inner.split(",").map((part) => unquote2(part).value.trim()).filter(Boolean);
|
|
6481
7023
|
}
|
|
6482
7024
|
function parseStringArrayField(frontmatterFields, key) {
|
|
6483
7025
|
const rawValue = frontmatterFields.get(key)?.rawValue;
|
|
6484
7026
|
if (!rawValue) return [];
|
|
6485
|
-
return
|
|
7027
|
+
return parseInlineStringArray2(rawValue);
|
|
6486
7028
|
}
|
|
6487
7029
|
function parseJsonField(frontmatterFields, key) {
|
|
6488
7030
|
const rawValue = frontmatterFields.get(key)?.rawValue?.trim();
|
|
@@ -6499,8 +7041,8 @@ function parseSkillMarkdown(content) {
|
|
|
6499
7041
|
hasValidFrontmatter,
|
|
6500
7042
|
frontmatterLines,
|
|
6501
7043
|
body
|
|
6502
|
-
} =
|
|
6503
|
-
const frontmatterFields =
|
|
7044
|
+
} = splitMarkdownFrontmatter3(content);
|
|
7045
|
+
const frontmatterFields = parseFrontmatterFields2(frontmatterLines);
|
|
6504
7046
|
return {
|
|
6505
7047
|
hasValidFrontmatter,
|
|
6506
7048
|
frontmatterLines,
|
|
@@ -6521,16 +7063,16 @@ function parseSkillMarkdown(content) {
|
|
|
6521
7063
|
hooks: parseJsonField(frontmatterFields, "hooks"),
|
|
6522
7064
|
paths: parseStringArrayField(frontmatterFields, "paths"),
|
|
6523
7065
|
shell: frontmatterFields.get("shell")?.value,
|
|
6524
|
-
firstHeading:
|
|
7066
|
+
firstHeading: firstHeading3(body)
|
|
6525
7067
|
};
|
|
6526
7068
|
}
|
|
6527
7069
|
function walkSkillFiles(skillsDir) {
|
|
6528
|
-
if (!skillsDir || !
|
|
6529
|
-
const entries =
|
|
7070
|
+
if (!skillsDir || !existsSync8(skillsDir)) return [];
|
|
7071
|
+
const entries = readdirSync3(skillsDir);
|
|
6530
7072
|
const files = [];
|
|
6531
7073
|
for (const entry of entries) {
|
|
6532
|
-
const fullPath =
|
|
6533
|
-
const stat =
|
|
7074
|
+
const fullPath = resolve9(skillsDir, entry);
|
|
7075
|
+
const stat = statSync3(fullPath);
|
|
6534
7076
|
if (stat.isDirectory()) {
|
|
6535
7077
|
files.push(...walkSkillFiles(fullPath));
|
|
6536
7078
|
continue;
|
|
@@ -6542,12 +7084,12 @@ function walkSkillFiles(skillsDir) {
|
|
|
6542
7084
|
return files;
|
|
6543
7085
|
}
|
|
6544
7086
|
function readSkillMarkdownFile(filePath) {
|
|
6545
|
-
return parseSkillMarkdown(
|
|
7087
|
+
return parseSkillMarkdown(readFileSync5(filePath, "utf-8"));
|
|
6546
7088
|
}
|
|
6547
7089
|
function readCanonicalSkillFiles(skillsDir) {
|
|
6548
|
-
if (!skillsDir || !
|
|
7090
|
+
if (!skillsDir || !existsSync8(skillsDir)) return [];
|
|
6549
7091
|
return walkSkillFiles(skillsDir).sort((a, b) => a.localeCompare(b)).map((filePath) => {
|
|
6550
|
-
const relativeDir =
|
|
7092
|
+
const relativeDir = relative3(skillsDir, filePath).replace(/\\/g, "/").replace(/\/SKILL\.md$/i, "");
|
|
6551
7093
|
return {
|
|
6552
7094
|
filePath,
|
|
6553
7095
|
relativeDir,
|
|
@@ -6560,6 +7102,48 @@ function serializeSkillMarkdown(frontmatterLines, body) {
|
|
|
6560
7102
|
return ["---", ...frontmatterLines, "---", body ? `
|
|
6561
7103
|
${body.replace(/^\n/, "")}` : ""].join("\n");
|
|
6562
7104
|
}
|
|
7105
|
+
function walkSupportFiles(dir) {
|
|
7106
|
+
const entries = readdirSync3(dir, { withFileTypes: true });
|
|
7107
|
+
const files = [];
|
|
7108
|
+
for (const entry of entries) {
|
|
7109
|
+
const fullPath = resolve9(dir, entry.name);
|
|
7110
|
+
if (entry.isDirectory()) {
|
|
7111
|
+
files.push(...walkSupportFiles(fullPath));
|
|
7112
|
+
continue;
|
|
7113
|
+
}
|
|
7114
|
+
if (entry.isFile()) {
|
|
7115
|
+
files.push(fullPath);
|
|
7116
|
+
}
|
|
7117
|
+
}
|
|
7118
|
+
return files;
|
|
7119
|
+
}
|
|
7120
|
+
function getCanonicalSkillMetadata(skill) {
|
|
7121
|
+
const skillDir = resolve9(skill.filePath, "..");
|
|
7122
|
+
const supportPaths = walkSupportFiles(skillDir).filter((fullPath) => fullPath !== skill.filePath).map((fullPath) => relative3(skillDir, fullPath).replace(/\\/g, "/")).sort((a, b) => a.localeCompare(b));
|
|
7123
|
+
return {
|
|
7124
|
+
dirName: skill.dirName,
|
|
7125
|
+
title: skill.firstHeading ?? skill.name ?? skill.dirName,
|
|
7126
|
+
...skill.description ? { description: skill.description } : {},
|
|
7127
|
+
...skill.whenToUse ? { whenToUse: skill.whenToUse } : {},
|
|
7128
|
+
...skill.argumentHint ? { argumentHint: skill.argumentHint } : {},
|
|
7129
|
+
arguments: [...skill.arguments],
|
|
7130
|
+
...typeof skill.disableModelInvocation === "boolean" ? { disableModelInvocation: skill.disableModelInvocation } : {},
|
|
7131
|
+
...typeof skill.userInvocable === "boolean" ? { userInvocable: skill.userInvocable } : {},
|
|
7132
|
+
allowedTools: [...skill.allowedTools],
|
|
7133
|
+
...skill.model ? { model: skill.model } : {},
|
|
7134
|
+
...skill.effort ? { effort: skill.effort } : {},
|
|
7135
|
+
...skill.context ? { context: skill.context } : {},
|
|
7136
|
+
...skill.agent ? { agent: skill.agent } : {},
|
|
7137
|
+
...skill.hooks !== void 0 ? { hooks: skill.hooks } : {},
|
|
7138
|
+
paths: [...skill.paths],
|
|
7139
|
+
...skill.shell ? { shell: skill.shell } : {},
|
|
7140
|
+
body: skill.body,
|
|
7141
|
+
supportPaths,
|
|
7142
|
+
helperScripts: supportPaths.filter((path) => path.startsWith("scripts/")),
|
|
7143
|
+
examplePaths: supportPaths.filter((path) => path.startsWith("examples/")),
|
|
7144
|
+
referencePaths: supportPaths.filter((path) => !path.startsWith("examples/") && !path.startsWith("scripts/"))
|
|
7145
|
+
};
|
|
7146
|
+
}
|
|
6563
7147
|
|
|
6564
7148
|
// src/generators/claude-code/index.ts
|
|
6565
7149
|
var ClaudeCodeGenerator = class extends Generator {
|
|
@@ -6592,8 +7176,8 @@ var ClaudeCodeGenerator = class extends Generator {
|
|
|
6592
7176
|
const wrappedSkills = this.collectCommandWrappedSkills();
|
|
6593
7177
|
for (const skill of collidingSkills) {
|
|
6594
7178
|
const outputPath = join2(this.outDir, "skills", skill.dirName, "SKILL.md");
|
|
6595
|
-
if (!
|
|
6596
|
-
const current =
|
|
7179
|
+
if (!existsSync9(outputPath)) continue;
|
|
7180
|
+
const current = readFileSync6(outputPath, "utf-8");
|
|
6597
7181
|
const hiddenName = buildHiddenSkillName(skill.effectiveName);
|
|
6598
7182
|
const rewritten = rewriteClaudeSkillVisibility(current, {
|
|
6599
7183
|
nameOverride: hiddenName,
|
|
@@ -6606,8 +7190,8 @@ var ClaudeCodeGenerator = class extends Generator {
|
|
|
6606
7190
|
for (const skill of wrappedSkills) {
|
|
6607
7191
|
if (collidingSkills.some((entry) => entry.dirName === skill.dirName)) continue;
|
|
6608
7192
|
const outputPath = join2(this.outDir, "skills", skill.dirName, "SKILL.md");
|
|
6609
|
-
if (!
|
|
6610
|
-
const current =
|
|
7193
|
+
if (!existsSync9(outputPath)) continue;
|
|
7194
|
+
const current = readFileSync6(outputPath, "utf-8");
|
|
6611
7195
|
const rewritten = rewriteClaudeSkillVisibility(current, {
|
|
6612
7196
|
userInvocable: false
|
|
6613
7197
|
});
|
|
@@ -6682,7 +7266,7 @@ ${bodyParts.join("\n").trim()}
|
|
|
6682
7266
|
if (!this.config.commands) return [];
|
|
6683
7267
|
const commandsSrc = this.resolveConfigPath(this.config.commands, "commands");
|
|
6684
7268
|
const skillsSrc = this.resolveConfigPath(this.config.skills, "skills");
|
|
6685
|
-
if (!
|
|
7269
|
+
if (!existsSync9(commandsSrc) || !existsSync9(skillsSrc)) return [];
|
|
6686
7270
|
const commandNames = collectTopLevelCommandNames(commandsSrc);
|
|
6687
7271
|
const collidingSkills = [];
|
|
6688
7272
|
for (const skill of readCanonicalSkillFiles(skillsSrc)) {
|
|
@@ -6697,7 +7281,7 @@ ${bodyParts.join("\n").trim()}
|
|
|
6697
7281
|
if (!this.config.commands) return [];
|
|
6698
7282
|
const commandsSrc = this.resolveConfigPath(this.config.commands, "commands");
|
|
6699
7283
|
const skillsSrc = this.resolveConfigPath(this.config.skills, "skills");
|
|
6700
|
-
if (!
|
|
7284
|
+
if (!existsSync9(commandsSrc) || !existsSync9(skillsSrc)) return [];
|
|
6701
7285
|
const referencedSkills = collectWrappedSkillNames(commandsSrc);
|
|
6702
7286
|
if (referencedSkills.size === 0) return [];
|
|
6703
7287
|
const wrappedSkills = [];
|
|
@@ -6712,7 +7296,7 @@ ${bodyParts.join("\n").trim()}
|
|
|
6712
7296
|
};
|
|
6713
7297
|
function collectTopLevelCommandNames(commandsRoot) {
|
|
6714
7298
|
const commandNames = /* @__PURE__ */ new Set();
|
|
6715
|
-
for (const entry of
|
|
7299
|
+
for (const entry of readdirSync4(commandsRoot, { withFileTypes: true })) {
|
|
6716
7300
|
if (entry.isFile() && entry.name.toLowerCase().endsWith(".md")) {
|
|
6717
7301
|
commandNames.add(basename2(entry.name, ".md"));
|
|
6718
7302
|
}
|
|
@@ -6721,11 +7305,9 @@ function collectTopLevelCommandNames(commandsRoot) {
|
|
|
6721
7305
|
}
|
|
6722
7306
|
function collectWrappedSkillNames(commandsRoot) {
|
|
6723
7307
|
const wrappedSkills = /* @__PURE__ */ new Set();
|
|
6724
|
-
for (const
|
|
6725
|
-
|
|
6726
|
-
const
|
|
6727
|
-
for (const match of content.matchAll(/Use the `([^`]+)` skill\./g)) {
|
|
6728
|
-
const skillName = match[1]?.trim();
|
|
7308
|
+
for (const command2 of readCanonicalCommandFiles(commandsRoot)) {
|
|
7309
|
+
const metadata = getCanonicalCommandMetadata(command2);
|
|
7310
|
+
for (const skillName of metadata.skills) {
|
|
6729
7311
|
if (skillName) wrappedSkills.add(skillName);
|
|
6730
7312
|
}
|
|
6731
7313
|
}
|
|
@@ -6811,6 +7393,70 @@ function asMap3(value) {
|
|
|
6811
7393
|
return value;
|
|
6812
7394
|
}
|
|
6813
7395
|
|
|
7396
|
+
// src/agent-translation-registry.ts
|
|
7397
|
+
var HAS_BOOLEAN = (value) => typeof value === "boolean";
|
|
7398
|
+
var AGENT_TRANSLATION_PROFILES = {
|
|
7399
|
+
cursor: {
|
|
7400
|
+
degradedFields: [
|
|
7401
|
+
{ key: "mode", present: (metadata) => !!metadata.mode },
|
|
7402
|
+
{ key: "hidden", present: (metadata) => metadata.hidden },
|
|
7403
|
+
{ key: "model_reasoning_effort", present: (metadata) => !!metadata.modelReasoningEffort },
|
|
7404
|
+
{ key: "sandbox_mode", present: (metadata) => !!metadata.sandboxMode },
|
|
7405
|
+
{ key: "temperature", present: (metadata) => typeof metadata.temperature === "number" },
|
|
7406
|
+
{ key: "steps", present: (metadata) => typeof metadata.steps === "number" },
|
|
7407
|
+
{ key: "disable", present: (metadata) => HAS_BOOLEAN(metadata.disabled) },
|
|
7408
|
+
{ key: "color", present: (metadata) => !!metadata.color },
|
|
7409
|
+
{ key: "topP", present: (metadata) => typeof metadata.topP === "number" },
|
|
7410
|
+
{ key: "skills", present: (metadata) => !!metadata.skills },
|
|
7411
|
+
{ key: "memory", present: (metadata) => !!metadata.memory },
|
|
7412
|
+
{ key: "background", present: (metadata) => HAS_BOOLEAN(metadata.background) },
|
|
7413
|
+
{ key: "isolation", present: (metadata) => !!metadata.isolation },
|
|
7414
|
+
{ key: "permission", present: (metadata) => !!metadata.permission },
|
|
7415
|
+
{ key: "tools", present: (metadata) => metadata.tools !== void 0 }
|
|
7416
|
+
],
|
|
7417
|
+
guidanceSurfaces: ["subagent framing", "generated notes"],
|
|
7418
|
+
message: (fields) => `Agent fields ${fields.map((field) => `"${field}"`).join(", ")} are not preserved as first-class Cursor agent frontmatter today. Pluxx translates that intent through subagent framing and generated notes instead.`
|
|
7419
|
+
},
|
|
7420
|
+
codex: {
|
|
7421
|
+
degradedFields: [
|
|
7422
|
+
{ key: "mode", present: (metadata) => !!metadata.mode },
|
|
7423
|
+
{ key: "hidden", present: (metadata) => metadata.hidden },
|
|
7424
|
+
{ key: "temperature", present: (metadata) => typeof metadata.temperature === "number" },
|
|
7425
|
+
{ key: "steps", present: (metadata) => typeof metadata.steps === "number" },
|
|
7426
|
+
{ key: "disable", present: (metadata) => HAS_BOOLEAN(metadata.disabled) },
|
|
7427
|
+
{ key: "color", present: (metadata) => !!metadata.color },
|
|
7428
|
+
{ key: "topP", present: (metadata) => typeof metadata.topP === "number" },
|
|
7429
|
+
{ key: "skills", present: (metadata) => !!metadata.skills },
|
|
7430
|
+
{ key: "memory", present: (metadata) => !!metadata.memory },
|
|
7431
|
+
{ key: "background", present: (metadata) => HAS_BOOLEAN(metadata.background) },
|
|
7432
|
+
{ key: "isolation", present: (metadata) => !!metadata.isolation },
|
|
7433
|
+
{ key: "permission", present: (metadata) => !!metadata.permission },
|
|
7434
|
+
{ key: "tools", present: (metadata) => metadata.tools !== void 0 }
|
|
7435
|
+
],
|
|
7436
|
+
guidanceSurfaces: ["developer instructions", "generated companion surfaces"],
|
|
7437
|
+
message: (fields) => `Agent fields ${fields.map((field) => `"${field}"`).join(", ")} are not native Codex TOML fields today. Pluxx keeps the specialist behavior, but translates that intent through developer instructions and companion surfaces instead.`
|
|
7438
|
+
},
|
|
7439
|
+
opencode: {
|
|
7440
|
+
degradedFields: [
|
|
7441
|
+
{ key: "model_reasoning_effort", present: (metadata) => !!metadata.modelReasoningEffort },
|
|
7442
|
+
{ key: "sandbox_mode", present: (metadata) => !!metadata.sandboxMode },
|
|
7443
|
+
{ key: "skills", present: (metadata) => !!metadata.skills },
|
|
7444
|
+
{ key: "memory", present: (metadata) => !!metadata.memory },
|
|
7445
|
+
{ key: "background", present: (metadata) => HAS_BOOLEAN(metadata.background) },
|
|
7446
|
+
{ key: "isolation", present: (metadata) => !!metadata.isolation }
|
|
7447
|
+
],
|
|
7448
|
+
guidanceSurfaces: ["agent config notes", "runtime/developer guidance"],
|
|
7449
|
+
message: (fields) => `Agent fields ${fields.map((field) => `"${field}"`).join(", ")} are not native OpenCode agent config fields today. Pluxx keeps the specialist behavior, but translates that intent through agent config notes and surrounding runtime guidance instead.`
|
|
7450
|
+
}
|
|
7451
|
+
};
|
|
7452
|
+
function getTranslatedAgentFields(platform, metadata) {
|
|
7453
|
+
return AGENT_TRANSLATION_PROFILES[platform].degradedFields.filter((field) => field.present(metadata)).map((field) => field.key);
|
|
7454
|
+
}
|
|
7455
|
+
function getAgentTranslationMessage(platform, fields) {
|
|
7456
|
+
if (fields.length === 0) return null;
|
|
7457
|
+
return AGENT_TRANSLATION_PROFILES[platform].message(fields);
|
|
7458
|
+
}
|
|
7459
|
+
|
|
6814
7460
|
// src/generators/cursor/index.ts
|
|
6815
7461
|
var CursorGenerator = class extends Generator {
|
|
6816
7462
|
platform = "cursor";
|
|
@@ -6901,6 +7547,12 @@ var CursorGenerator = class extends Generator {
|
|
|
6901
7547
|
}
|
|
6902
7548
|
for (const [event, entries] of Object.entries(this.config.hooks)) {
|
|
6903
7549
|
if (!entries) continue;
|
|
7550
|
+
if (!isHookEventSupported("cursor", event)) {
|
|
7551
|
+
console.warn(
|
|
7552
|
+
`[pluxx] cursor generator dropped unsupported hook event "${event}". ${getUnsupportedHookEventReason("cursor") ?? `Supported: ${getSupportedHookEvents("cursor").join(", ")}`}`
|
|
7553
|
+
);
|
|
7554
|
+
continue;
|
|
7555
|
+
}
|
|
6904
7556
|
const filteredEntries = entries.filter((entry) => {
|
|
6905
7557
|
if (usesPlatformManagedAuth && entry.type !== "prompt" && entry.command?.includes("check-env.sh")) {
|
|
6906
7558
|
return false;
|
|
@@ -6990,7 +7642,7 @@ var CursorGenerator = class extends Generator {
|
|
|
6990
7642
|
frontmatter.push(`model: ${JSON.stringify(metadata.model)}`);
|
|
6991
7643
|
}
|
|
6992
7644
|
frontmatter.push("---");
|
|
6993
|
-
const translatedNotes = buildCursorAgentTranslationNotes(agent.frontmatter);
|
|
7645
|
+
const translatedNotes = buildCursorAgentTranslationNotes(agent.frontmatter, metadata);
|
|
6994
7646
|
const bodyParts = [
|
|
6995
7647
|
...translatedNotes,
|
|
6996
7648
|
agent.body
|
|
@@ -7005,8 +7657,12 @@ ${bodyParts.join("\n\n").trim()}
|
|
|
7005
7657
|
}
|
|
7006
7658
|
}
|
|
7007
7659
|
};
|
|
7008
|
-
function buildCursorAgentTranslationNotes(frontmatter) {
|
|
7009
|
-
|
|
7660
|
+
function buildCursorAgentTranslationNotes(frontmatter, metadata) {
|
|
7661
|
+
const notes = [
|
|
7662
|
+
getAgentTranslationMessage("cursor", getTranslatedAgentFields("cursor", metadata)),
|
|
7663
|
+
...buildDelegationBehaviorNotes(frontmatter)
|
|
7664
|
+
].filter((note) => Boolean(note));
|
|
7665
|
+
return notes.map(
|
|
7010
7666
|
(note) => `Cursor translation note: ${note.charAt(0).toLowerCase()}${note.slice(1)}`
|
|
7011
7667
|
);
|
|
7012
7668
|
}
|
|
@@ -7014,125 +7670,57 @@ function buildCursorAgentTranslationNotes(frontmatter) {
|
|
|
7014
7670
|
// src/generators/codex/index.ts
|
|
7015
7671
|
import { relative as relative4 } from "path";
|
|
7016
7672
|
|
|
7017
|
-
// src/
|
|
7018
|
-
|
|
7019
|
-
|
|
7020
|
-
|
|
7021
|
-
|
|
7022
|
-
|
|
7023
|
-
|
|
7024
|
-
|
|
7025
|
-
}
|
|
7026
|
-
|
|
7027
|
-
}
|
|
7028
|
-
|
|
7029
|
-
|
|
7030
|
-
|
|
7031
|
-
|
|
7032
|
-
frontmatterLines: [],
|
|
7033
|
-
body: content
|
|
7034
|
-
};
|
|
7035
|
-
}
|
|
7036
|
-
let endIndex = -1;
|
|
7037
|
-
for (let i = 1; i < lines.length; i += 1) {
|
|
7038
|
-
if (lines[i].trim() === "---") {
|
|
7039
|
-
endIndex = i;
|
|
7040
|
-
break;
|
|
7041
|
-
}
|
|
7042
|
-
}
|
|
7043
|
-
if (endIndex === -1) {
|
|
7044
|
-
return {
|
|
7045
|
-
frontmatterLines: [],
|
|
7046
|
-
body: content
|
|
7047
|
-
};
|
|
7048
|
-
}
|
|
7049
|
-
return {
|
|
7050
|
-
frontmatterLines: lines.slice(1, endIndex),
|
|
7051
|
-
body: lines.slice(endIndex + 1).join("\n")
|
|
7052
|
-
};
|
|
7673
|
+
// src/command-translation-registry.ts
|
|
7674
|
+
var CODEX_COMMAND_FIELD_DESCRIPTORS = [
|
|
7675
|
+
{ label: "when_to_use", present: (metadata) => Boolean(metadata.whenToUse) },
|
|
7676
|
+
{ label: "argument-hint", present: (metadata) => Boolean(metadata.argumentHint) },
|
|
7677
|
+
{ label: "arguments", present: (metadata) => metadata.arguments.length > 0 },
|
|
7678
|
+
{ label: "examples", present: (metadata) => metadata.examples.length > 0 },
|
|
7679
|
+
{ label: "skill", present: (metadata) => Boolean(metadata.skill) },
|
|
7680
|
+
{ label: "skills", present: (metadata) => metadata.skills.length > 0 },
|
|
7681
|
+
{ label: "agent", present: (metadata) => Boolean(metadata.agent) },
|
|
7682
|
+
{ label: "subtask", present: (metadata) => typeof metadata.subtask === "boolean" },
|
|
7683
|
+
{ label: "model", present: (metadata) => Boolean(metadata.model) },
|
|
7684
|
+
{ label: "context", present: (metadata) => Boolean(metadata.context) }
|
|
7685
|
+
];
|
|
7686
|
+
function getCodexCommandGuidanceNote() {
|
|
7687
|
+
return "Codex does not currently document plugin-packaged slash-command parity. Pluxx keeps canonical command intent through AGENTS.md routing guidance and `.codex/commands.generated.json`.";
|
|
7053
7688
|
}
|
|
7054
|
-
function
|
|
7055
|
-
|
|
7056
|
-
|
|
7057
|
-
return trimmed.slice(1, -1).trim();
|
|
7058
|
-
}
|
|
7059
|
-
return trimmed;
|
|
7689
|
+
function getTranslatedCommandFields(platform, metadata) {
|
|
7690
|
+
if (platform !== "codex") return [];
|
|
7691
|
+
return CODEX_COMMAND_FIELD_DESCRIPTORS.filter((descriptor) => descriptor.present(metadata)).map((descriptor) => descriptor.label);
|
|
7060
7692
|
}
|
|
7061
|
-
function
|
|
7062
|
-
|
|
7063
|
-
|
|
7064
|
-
|
|
7065
|
-
return stripYamlScalar(match[1]);
|
|
7066
|
-
}
|
|
7693
|
+
function getCommandTranslationMessage(platform, degradedFields) {
|
|
7694
|
+
if (degradedFields.length === 0) return void 0;
|
|
7695
|
+
if (platform === "codex") {
|
|
7696
|
+
return `Command fields ${degradedFields.map((field) => `"${field}"`).join(", ")} are not native Codex plugin slash-command fields today. ${getCodexCommandGuidanceNote()}`;
|
|
7067
7697
|
}
|
|
7068
7698
|
return void 0;
|
|
7069
7699
|
}
|
|
7070
|
-
function
|
|
7071
|
-
const
|
|
7072
|
-
|
|
7073
|
-
|
|
7074
|
-
|
|
7075
|
-
|
|
7076
|
-
}
|
|
7700
|
+
function buildCodexCommandRoutingEntry(metadata) {
|
|
7701
|
+
const distinctSkills = metadata.skills.filter((skillId, index, skills) => skills.indexOf(skillId) === index);
|
|
7702
|
+
const hasOnlyPrimarySkill = distinctSkills.length === 1 && distinctSkills[0] === metadata.skill;
|
|
7703
|
+
const routingBits = [
|
|
7704
|
+
metadata.argumentHint ? `arguments: ${metadata.argumentHint}` : null,
|
|
7705
|
+
metadata.skill ? `skill: ${metadata.skill}` : null,
|
|
7706
|
+
distinctSkills.length > 0 && !hasOnlyPrimarySkill ? `skills: ${distinctSkills.join(", ")}` : null,
|
|
7707
|
+
metadata.agent ? `agent: ${metadata.agent}` : null,
|
|
7708
|
+
typeof metadata.subtask === "boolean" ? `subtask: ${metadata.subtask ? "yes" : "no"}` : null,
|
|
7709
|
+
metadata.model ? `model: ${metadata.model}` : null
|
|
7710
|
+
].filter(Boolean);
|
|
7711
|
+
const lines = [
|
|
7712
|
+
`- \`/${metadata.commandId}\` - ${metadata.description ?? metadata.title}${routingBits.length > 0 ? ` (${routingBits.join("; ")})` : ""}`
|
|
7713
|
+
];
|
|
7714
|
+
if (metadata.whenToUse) {
|
|
7715
|
+
lines.push(` - When to use: ${metadata.whenToUse}`);
|
|
7077
7716
|
}
|
|
7078
|
-
|
|
7079
|
-
}
|
|
7080
|
-
function parseCommandFrontmatterBoolean(frontmatterLines, key) {
|
|
7081
|
-
const value = parseCommandFrontmatterString(frontmatterLines, key);
|
|
7082
|
-
if (!value) return void 0;
|
|
7083
|
-
if (/^true$/i.test(value)) return true;
|
|
7084
|
-
if (/^false$/i.test(value)) return false;
|
|
7085
|
-
return void 0;
|
|
7086
|
-
}
|
|
7087
|
-
function walkMarkdownFiles2(dir) {
|
|
7088
|
-
const entries = readdirSync4(dir);
|
|
7089
|
-
const files = [];
|
|
7090
|
-
for (const entry of entries) {
|
|
7091
|
-
const fullPath = resolve9(dir, entry);
|
|
7092
|
-
const stat = statSync3(fullPath);
|
|
7093
|
-
if (stat.isDirectory()) {
|
|
7094
|
-
files.push(...walkMarkdownFiles2(fullPath));
|
|
7095
|
-
continue;
|
|
7096
|
-
}
|
|
7097
|
-
if (stat.isFile() && entry.endsWith(".md")) {
|
|
7098
|
-
files.push(fullPath);
|
|
7099
|
-
}
|
|
7717
|
+
if (metadata.examples.length > 0) {
|
|
7718
|
+
lines.push(` - Examples: ${metadata.examples.map((example) => `\`${example}\``).join(", ")}`);
|
|
7100
7719
|
}
|
|
7101
|
-
|
|
7102
|
-
}
|
|
7103
|
-
|
|
7104
|
-
|
|
7105
|
-
return walkMarkdownFiles2(commandsDir).sort((a, b) => a.localeCompare(b)).map((filePath) => {
|
|
7106
|
-
const content = readFileSync6(filePath, "utf-8");
|
|
7107
|
-
const { frontmatterLines, body } = splitMarkdownFrontmatter3(content);
|
|
7108
|
-
const relativeStem = relative3(commandsDir, filePath).replace(/\\/g, "/").replace(/\.md$/i, "");
|
|
7109
|
-
const commandId = relativeStem.replace(/\//g, "-").toLowerCase();
|
|
7110
|
-
const title = firstHeading3(body) ?? commandId;
|
|
7111
|
-
return {
|
|
7112
|
-
filePath,
|
|
7113
|
-
relativeStem,
|
|
7114
|
-
commandId,
|
|
7115
|
-
title,
|
|
7116
|
-
description: parseCommandFrontmatterDescription(frontmatterLines),
|
|
7117
|
-
argumentHint: parseCommandFrontmatterString(frontmatterLines, "argument-hint"),
|
|
7118
|
-
agent: parseCommandFrontmatterString(frontmatterLines, "agent"),
|
|
7119
|
-
subtask: parseCommandFrontmatterBoolean(frontmatterLines, "subtask"),
|
|
7120
|
-
model: parseCommandFrontmatterString(frontmatterLines, "model"),
|
|
7121
|
-
body: body.trim()
|
|
7122
|
-
};
|
|
7123
|
-
});
|
|
7124
|
-
}
|
|
7125
|
-
function getCanonicalCommandMetadata(command2) {
|
|
7126
|
-
return {
|
|
7127
|
-
commandId: command2.commandId,
|
|
7128
|
-
title: command2.title,
|
|
7129
|
-
...command2.description ? { description: command2.description } : {},
|
|
7130
|
-
...command2.argumentHint ? { argumentHint: command2.argumentHint } : {},
|
|
7131
|
-
...command2.agent ? { agent: command2.agent } : {},
|
|
7132
|
-
...typeof command2.subtask === "boolean" ? { subtask: command2.subtask } : {},
|
|
7133
|
-
...command2.model ? { model: command2.model } : {},
|
|
7134
|
-
template: command2.body
|
|
7135
|
-
};
|
|
7720
|
+
if (metadata.context) {
|
|
7721
|
+
lines.push(` - Context hint: ${metadata.context}`);
|
|
7722
|
+
}
|
|
7723
|
+
return lines;
|
|
7136
7724
|
}
|
|
7137
7725
|
|
|
7138
7726
|
// src/generators/codex/index.ts
|
|
@@ -7148,7 +7736,10 @@ var CodexGenerator = class extends Generator {
|
|
|
7148
7736
|
const entry = {
|
|
7149
7737
|
url: server.url
|
|
7150
7738
|
};
|
|
7151
|
-
|
|
7739
|
+
const nativeOverride = getNativeCodexMcpEntryOverride(this.config, name);
|
|
7740
|
+
if (nativeOverride) {
|
|
7741
|
+
Object.assign(entry, nativeOverride);
|
|
7742
|
+
} else if (server.auth?.type === "bearer" && server.auth.envVar) {
|
|
7152
7743
|
entry.bearer_token_env_var = server.auth.envVar;
|
|
7153
7744
|
} else if (server.auth?.type === "header" && server.auth.envVar) {
|
|
7154
7745
|
const isBearerAuthorizationHeader = server.auth.headerName === "Authorization" && server.auth.headerTemplate === "Bearer ${value}";
|
|
@@ -7176,6 +7767,7 @@ var CodexGenerator = class extends Generator {
|
|
|
7176
7767
|
this.generateHooksCompanion(),
|
|
7177
7768
|
this.generateReadinessCompanion(),
|
|
7178
7769
|
this.generateCommandsCompanion(),
|
|
7770
|
+
this.generateSkillsCompanion(),
|
|
7179
7771
|
this.generatePermissionsCompanion()
|
|
7180
7772
|
]);
|
|
7181
7773
|
this.copySkills();
|
|
@@ -7293,7 +7885,13 @@ var CodexGenerator = class extends Generator {
|
|
|
7293
7885
|
lines.push(`sandbox_mode = ${JSON.stringify(metadata.sandboxMode)}`);
|
|
7294
7886
|
}
|
|
7295
7887
|
const delegationNotes = buildDelegationBehaviorNotes(agent.frontmatter);
|
|
7888
|
+
const fieldTranslationNote = getAgentTranslationMessage("codex", getTranslatedAgentFields("codex", metadata));
|
|
7296
7889
|
const developerInstructions = [
|
|
7890
|
+
...fieldTranslationNote ? [
|
|
7891
|
+
"Host translation note:",
|
|
7892
|
+
`- ${fieldTranslationNote}`,
|
|
7893
|
+
""
|
|
7894
|
+
] : [],
|
|
7297
7895
|
...delegationNotes.length > 0 ? [
|
|
7298
7896
|
"Delegation contract:",
|
|
7299
7897
|
...delegationNotes.map((note) => `- ${note}`),
|
|
@@ -7334,7 +7932,18 @@ var CodexGenerator = class extends Generator {
|
|
|
7334
7932
|
const codexEvent = mapHookEventToPascalCase(event);
|
|
7335
7933
|
const mappedEntries = [];
|
|
7336
7934
|
for (const entry of entries) {
|
|
7337
|
-
|
|
7935
|
+
const entryType = entry.type ?? "command";
|
|
7936
|
+
if (!isHookTypeSupported("codex", entryType)) {
|
|
7937
|
+
const issue = entryType === "command" ? null : getHookTypeTranslationIssue("codex", entryType);
|
|
7938
|
+
unsupported.push({
|
|
7939
|
+
canonicalEvent: event,
|
|
7940
|
+
codexEvent,
|
|
7941
|
+
type: entryType,
|
|
7942
|
+
reason: issue?.message ?? `Codex currently bundles only command-hook entries from Pluxx. ${entryType} hooks will be dropped from the generated Codex bundle.`
|
|
7943
|
+
});
|
|
7944
|
+
continue;
|
|
7945
|
+
}
|
|
7946
|
+
if (!entry.command) continue;
|
|
7338
7947
|
nextWrapperIndex += 1;
|
|
7339
7948
|
const relativePath = `hooks/pluxx-hook-command-${nextWrapperIndex}.sh`;
|
|
7340
7949
|
await this.writeFile(
|
|
@@ -7407,22 +8016,62 @@ var CodexGenerator = class extends Generator {
|
|
|
7407
8016
|
await this.writeJson(".codex/commands.generated.json", {
|
|
7408
8017
|
model: "pluxx.commands.v1",
|
|
7409
8018
|
nativeSurface: "degraded-to-guidance",
|
|
7410
|
-
note:
|
|
8019
|
+
note: `${getCodexCommandGuidanceNote()} Use these canonical command entries as workflow routing guidance alongside AGENTS.md.`,
|
|
7411
8020
|
commands: commands.map((command2) => {
|
|
7412
8021
|
const metadata = getCanonicalCommandMetadata(command2);
|
|
7413
8022
|
return {
|
|
7414
8023
|
id: metadata.commandId,
|
|
7415
8024
|
title: metadata.title,
|
|
7416
8025
|
...metadata.description ? { description: metadata.description } : {},
|
|
8026
|
+
...metadata.whenToUse ? { whenToUse: metadata.whenToUse } : {},
|
|
7417
8027
|
...metadata.argumentHint ? { argumentHint: metadata.argumentHint } : {},
|
|
8028
|
+
...metadata.arguments.length > 0 ? { arguments: metadata.arguments } : {},
|
|
8029
|
+
...metadata.examples.length > 0 ? { examples: metadata.examples } : {},
|
|
8030
|
+
...metadata.skill ? { skill: metadata.skill } : {},
|
|
8031
|
+
...metadata.skills.length > 0 ? { skills: metadata.skills } : {},
|
|
7418
8032
|
...metadata.agent ? { agent: metadata.agent } : {},
|
|
7419
8033
|
...typeof metadata.subtask === "boolean" ? { subtask: metadata.subtask } : {},
|
|
7420
8034
|
...metadata.model ? { model: metadata.model } : {},
|
|
8035
|
+
...metadata.context ? { context: metadata.context } : {},
|
|
7421
8036
|
template: metadata.template
|
|
7422
8037
|
};
|
|
7423
8038
|
})
|
|
7424
8039
|
});
|
|
7425
8040
|
}
|
|
8041
|
+
async generateSkillsCompanion() {
|
|
8042
|
+
if (!this.config.skills) return;
|
|
8043
|
+
const skillsDir = this.resolveConfigPath(this.config.skills, "skills");
|
|
8044
|
+
const skills = readCanonicalSkillFiles(skillsDir);
|
|
8045
|
+
if (skills.length === 0) return;
|
|
8046
|
+
await this.writeJson(".codex/skills.generated.json", {
|
|
8047
|
+
model: "pluxx.skills.v1",
|
|
8048
|
+
nativeSurface: "compatibility-skill-files-plus-guidance",
|
|
8049
|
+
note: "Codex preserves canonical SKILL.md files, but richer discovery and support-file metadata may also need command or AGENTS.md guidance. Use this companion to keep that richer canonical metadata visible after translation.",
|
|
8050
|
+
skills: skills.map((skill) => {
|
|
8051
|
+
const metadata = getCanonicalSkillMetadata(skill);
|
|
8052
|
+
return {
|
|
8053
|
+
id: metadata.dirName,
|
|
8054
|
+
title: metadata.title,
|
|
8055
|
+
...metadata.description ? { description: metadata.description } : {},
|
|
8056
|
+
...metadata.whenToUse ? { whenToUse: metadata.whenToUse } : {},
|
|
8057
|
+
...metadata.argumentHint ? { argumentHint: metadata.argumentHint } : {},
|
|
8058
|
+
...metadata.arguments.length > 0 ? { arguments: metadata.arguments } : {},
|
|
8059
|
+
...typeof metadata.disableModelInvocation === "boolean" ? { disableModelInvocation: metadata.disableModelInvocation } : {},
|
|
8060
|
+
...typeof metadata.userInvocable === "boolean" ? { userInvocable: metadata.userInvocable } : {},
|
|
8061
|
+
...metadata.allowedTools.length > 0 ? { allowedTools: metadata.allowedTools } : {},
|
|
8062
|
+
...metadata.model ? { model: metadata.model } : {},
|
|
8063
|
+
...metadata.effort ? { effort: metadata.effort } : {},
|
|
8064
|
+
...metadata.context ? { context: metadata.context } : {},
|
|
8065
|
+
...metadata.agent ? { agent: metadata.agent } : {},
|
|
8066
|
+
...metadata.paths.length > 0 ? { paths: metadata.paths } : {},
|
|
8067
|
+
...metadata.shell ? { shell: metadata.shell } : {},
|
|
8068
|
+
...metadata.helperScripts.length > 0 ? { helperScripts: metadata.helperScripts } : {},
|
|
8069
|
+
...metadata.examplePaths.length > 0 ? { examplePaths: metadata.examplePaths } : {},
|
|
8070
|
+
...metadata.referencePaths.length > 0 ? { referencePaths: metadata.referencePaths } : {}
|
|
8071
|
+
};
|
|
8072
|
+
})
|
|
8073
|
+
});
|
|
8074
|
+
}
|
|
7426
8075
|
buildCodexCommandRoutingSection() {
|
|
7427
8076
|
if (!this.config.commands) return null;
|
|
7428
8077
|
const commandsDir = this.resolveConfigPath(this.config.commands, "commands");
|
|
@@ -7433,10 +8082,7 @@ var CodexGenerator = class extends Generator {
|
|
|
7433
8082
|
"",
|
|
7434
8083
|
"This plugin defines canonical command entrypoints. Codex does not package them as native slash commands today, so route those requests through the matching workflow directly.",
|
|
7435
8084
|
"",
|
|
7436
|
-
...commands.
|
|
7437
|
-
const metadata = getCanonicalCommandMetadata(command2);
|
|
7438
|
-
return `- \`/${metadata.commandId}\` - ${metadata.description ?? metadata.title}${metadata.argumentHint ? ` (arguments: ${metadata.argumentHint})` : ""}`;
|
|
7439
|
-
})
|
|
8085
|
+
...commands.flatMap((command2) => buildCodexCommandRoutingEntry(getCanonicalCommandMetadata(command2)))
|
|
7440
8086
|
];
|
|
7441
8087
|
return lines.join("\n");
|
|
7442
8088
|
}
|
|
@@ -7451,7 +8097,8 @@ var OpenCodeGenerator = class extends Generator {
|
|
|
7451
8097
|
await Promise.all([
|
|
7452
8098
|
this.generatePackageJson(),
|
|
7453
8099
|
this.generatePluginWrapper(),
|
|
7454
|
-
this.generateReadinessRuntime()
|
|
8100
|
+
this.generateReadinessRuntime(),
|
|
8101
|
+
this.generateSkillsCompanion()
|
|
7455
8102
|
]);
|
|
7456
8103
|
this.copySkills();
|
|
7457
8104
|
this.copyCommands();
|
|
@@ -7769,6 +8416,40 @@ var OpenCodeGenerator = class extends Generator {
|
|
|
7769
8416
|
lines.push("");
|
|
7770
8417
|
await this.writeFile("index.ts", lines.join("\n"));
|
|
7771
8418
|
}
|
|
8419
|
+
async generateSkillsCompanion() {
|
|
8420
|
+
if (!this.config.skills) return;
|
|
8421
|
+
const skillsDir = this.resolveConfigPath(this.config.skills, "skills");
|
|
8422
|
+
const skills = readCanonicalSkillFiles(skillsDir);
|
|
8423
|
+
if (skills.length === 0) return;
|
|
8424
|
+
await this.writeJson("skills.generated.json", {
|
|
8425
|
+
model: "pluxx.skills.v1",
|
|
8426
|
+
nativeSurface: "skill-files-plus-runtime-guidance",
|
|
8427
|
+
note: "OpenCode preserves canonical skill files, but richer discovery, helper-script, and support-file metadata may still need command, agent, or runtime guidance. This companion keeps that richer canonical metadata visible after translation.",
|
|
8428
|
+
skills: skills.map((skill) => {
|
|
8429
|
+
const metadata = getCanonicalSkillMetadata(skill);
|
|
8430
|
+
return {
|
|
8431
|
+
id: metadata.dirName,
|
|
8432
|
+
title: metadata.title,
|
|
8433
|
+
...metadata.description ? { description: metadata.description } : {},
|
|
8434
|
+
...metadata.whenToUse ? { whenToUse: metadata.whenToUse } : {},
|
|
8435
|
+
...metadata.argumentHint ? { argumentHint: metadata.argumentHint } : {},
|
|
8436
|
+
...metadata.arguments.length > 0 ? { arguments: metadata.arguments } : {},
|
|
8437
|
+
...typeof metadata.disableModelInvocation === "boolean" ? { disableModelInvocation: metadata.disableModelInvocation } : {},
|
|
8438
|
+
...typeof metadata.userInvocable === "boolean" ? { userInvocable: metadata.userInvocable } : {},
|
|
8439
|
+
...metadata.allowedTools.length > 0 ? { allowedTools: metadata.allowedTools } : {},
|
|
8440
|
+
...metadata.model ? { model: metadata.model } : {},
|
|
8441
|
+
...metadata.effort ? { effort: metadata.effort } : {},
|
|
8442
|
+
...metadata.context ? { context: metadata.context } : {},
|
|
8443
|
+
...metadata.agent ? { agent: metadata.agent } : {},
|
|
8444
|
+
...metadata.paths.length > 0 ? { paths: metadata.paths } : {},
|
|
8445
|
+
...metadata.shell ? { shell: metadata.shell } : {},
|
|
8446
|
+
...metadata.helperScripts.length > 0 ? { helperScripts: metadata.helperScripts } : {},
|
|
8447
|
+
...metadata.examplePaths.length > 0 ? { examplePaths: metadata.examplePaths } : {},
|
|
8448
|
+
...metadata.referencePaths.length > 0 ? { referencePaths: metadata.referencePaths } : {}
|
|
8449
|
+
};
|
|
8450
|
+
})
|
|
8451
|
+
});
|
|
8452
|
+
}
|
|
7772
8453
|
async generateReadinessRuntime() {
|
|
7773
8454
|
if (!this.config.readiness) return;
|
|
7774
8455
|
await this.writeFile("runtime/pluxx-readiness.mjs", buildGeneratedReadinessScript(this.config.readiness));
|
|
@@ -7810,10 +8491,16 @@ var OpenCodeGenerator = class extends Generator {
|
|
|
7810
8491
|
output[metadata.commandId] = {
|
|
7811
8492
|
template: metadata.template,
|
|
7812
8493
|
...metadata.description ? { description: metadata.description } : {},
|
|
8494
|
+
...metadata.whenToUse ? { whenToUse: metadata.whenToUse } : {},
|
|
7813
8495
|
...metadata.argumentHint ? { argumentHint: metadata.argumentHint } : {},
|
|
8496
|
+
...metadata.arguments.length > 0 ? { arguments: metadata.arguments } : {},
|
|
8497
|
+
...metadata.examples.length > 0 ? { examples: metadata.examples } : {},
|
|
8498
|
+
...metadata.skill ? { skill: metadata.skill } : {},
|
|
8499
|
+
...metadata.skills.length > 0 ? { skills: metadata.skills } : {},
|
|
7814
8500
|
...metadata.agent ? { agent: metadata.agent } : {},
|
|
7815
8501
|
...typeof metadata.subtask === "boolean" ? { subtask: metadata.subtask } : {},
|
|
7816
|
-
...metadata.model ? { model: metadata.model } : {}
|
|
8502
|
+
...metadata.model ? { model: metadata.model } : {},
|
|
8503
|
+
...metadata.context ? { context: metadata.context } : {}
|
|
7817
8504
|
};
|
|
7818
8505
|
}
|
|
7819
8506
|
return output;
|
|
@@ -9132,8 +9819,8 @@ var CORE_FOUR_PRIMITIVE_CAPABILITIES = {
|
|
|
9132
9819
|
},
|
|
9133
9820
|
commands: {
|
|
9134
9821
|
mode: "degrade",
|
|
9135
|
-
nativeSurfaces: ["skills/", "AGENTS.md"],
|
|
9136
|
-
notes: "Current Codex docs do not document plugin-packaged slash-command parity."
|
|
9822
|
+
nativeSurfaces: ["skills/", "AGENTS.md", ".codex/commands.generated.json"],
|
|
9823
|
+
notes: "Current Codex docs do not document plugin-packaged slash-command parity, so Pluxx keeps canonical command intent through routing guidance plus a generated companion mirror."
|
|
9137
9824
|
},
|
|
9138
9825
|
agents: {
|
|
9139
9826
|
mode: "translate",
|
|
@@ -9293,7 +9980,7 @@ function renderPrimitiveTranslationSummary(summary) {
|
|
|
9293
9980
|
}
|
|
9294
9981
|
|
|
9295
9982
|
// src/branding-completeness.ts
|
|
9296
|
-
function
|
|
9983
|
+
function asRecord2(value) {
|
|
9297
9984
|
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
9298
9985
|
return value;
|
|
9299
9986
|
}
|
|
@@ -9305,7 +9992,7 @@ function hasNonEmptyStringArray(value) {
|
|
|
9305
9992
|
}
|
|
9306
9993
|
function getBrandingCompletenessWarnings(config) {
|
|
9307
9994
|
const warnings = [];
|
|
9308
|
-
const codexInterface =
|
|
9995
|
+
const codexInterface = asRecord2(config.platforms?.codex?.interface);
|
|
9309
9996
|
if (config.targets.includes("codex")) {
|
|
9310
9997
|
const codexHasIcon = hasNonEmptyString(config.brand?.icon) || hasNonEmptyString(codexInterface?.composerIcon) || hasNonEmptyString(codexInterface?.logo);
|
|
9311
9998
|
const codexHasScreenshots = hasNonEmptyStringArray(config.brand?.screenshots) || hasNonEmptyStringArray(codexInterface?.screenshots);
|
|
@@ -9347,6 +10034,54 @@ function getBrandingCompletenessWarnings(config) {
|
|
|
9347
10034
|
return warnings;
|
|
9348
10035
|
}
|
|
9349
10036
|
|
|
10037
|
+
// src/skill-translation-registry.ts
|
|
10038
|
+
var CODEX_SKILL_FIELD_TRANSLATIONS = {
|
|
10039
|
+
when_to_use: "Pluxx currently keeps `when_to_use` as compatibility metadata in Codex skill files and may also surface the same discovery hint through command or routing guidance, because Codex does not document an equivalent skill discovery field.",
|
|
10040
|
+
"argument-hint": "Pluxx currently translates `argument-hint` into Codex command or routing guidance rather than documented Codex skill frontmatter.",
|
|
10041
|
+
arguments: "Pluxx currently translates skill `arguments` into Codex command or routing guidance rather than documented Codex skill frontmatter.",
|
|
10042
|
+
"user-invocable": "Pluxx currently keeps `user-invocable` as compatibility metadata in Codex skill files because Codex does not document an equivalent skill visibility field.",
|
|
10043
|
+
"allowed-tools": "Pluxx currently translates `allowed-tools` into Codex permission companions or external config rather than documented Codex skill frontmatter.",
|
|
10044
|
+
model: "Pluxx currently translates skill `model` intent through Codex custom agents, routing guidance, or model overrides rather than documented Codex skill frontmatter.",
|
|
10045
|
+
effort: "Pluxx currently translates skill `effort` intent through Codex custom-agent model reasoning controls or surrounding model guidance rather than documented Codex skill frontmatter.",
|
|
10046
|
+
context: "Pluxx currently treats skill `context` as companion instruction intent because Codex does not document an equivalent skill frontmatter field.",
|
|
10047
|
+
agent: "Pluxx currently translates skill `agent` intent through Codex custom agents or routing guidance rather than documented Codex skill frontmatter.",
|
|
10048
|
+
hooks: "Pluxx currently translates skill-local `hooks` intent through bundled Codex hooks, where skill-local attachment is lost.",
|
|
10049
|
+
paths: "Pluxx currently translates skill `paths` into surrounding instruction or routing context because Codex does not document an equivalent skill frontmatter field.",
|
|
10050
|
+
shell: "Pluxx currently translates skill `shell` intent through command or runtime guidance because Codex does not document an equivalent skill frontmatter field."
|
|
10051
|
+
};
|
|
10052
|
+
var OPENCODE_SKILL_FIELD_TRANSLATIONS = {
|
|
10053
|
+
when_to_use: "Pluxx currently keeps `when_to_use` as compatibility metadata in OpenCode skill files and may also surface the same discovery hint through commands or neighboring runtime guidance, because OpenCode does not document an equivalent skill discovery field.",
|
|
10054
|
+
"argument-hint": "Pluxx currently translates `argument-hint` into OpenCode commands or runtime command metadata rather than documented OpenCode skill frontmatter.",
|
|
10055
|
+
arguments: "Pluxx currently translates skill `arguments` into OpenCode commands or runtime command metadata rather than documented OpenCode skill frontmatter.",
|
|
10056
|
+
"user-invocable": "Pluxx currently keeps `user-invocable` as compatibility metadata in OpenCode skill files because OpenCode does not document an equivalent skill visibility field.",
|
|
10057
|
+
"allowed-tools": "Pluxx currently translates `allowed-tools` into OpenCode permission config rather than documented OpenCode skill frontmatter.",
|
|
10058
|
+
model: "Pluxx currently translates skill `model` intent through OpenCode agent/config model selection rather than documented OpenCode skill frontmatter.",
|
|
10059
|
+
effort: "Pluxx currently translates skill `effort` intent through OpenCode model configuration or agent/runtime controls rather than documented OpenCode skill frontmatter.",
|
|
10060
|
+
context: "Pluxx currently translates skill `context` through runtime instruction injection or neighboring config because OpenCode does not document an equivalent skill frontmatter field.",
|
|
10061
|
+
agent: "Pluxx currently translates skill `agent` intent through OpenCode agents or runtime routing rather than documented OpenCode skill frontmatter.",
|
|
10062
|
+
hooks: "Pluxx currently translates skill-local `hooks` intent through OpenCode runtime event handlers, where skill-local attachment is lost.",
|
|
10063
|
+
paths: "Pluxx currently translates skill `paths` into runtime or instruction context because OpenCode does not document an equivalent skill frontmatter field.",
|
|
10064
|
+
shell: "Pluxx currently translates skill `shell` intent through runtime code or command execution guidance because OpenCode does not document an equivalent skill frontmatter field."
|
|
10065
|
+
};
|
|
10066
|
+
function getSkillFrontmatterTranslationIssue(platform, key, supportedFields) {
|
|
10067
|
+
if (platform === "cursor") {
|
|
10068
|
+
return {
|
|
10069
|
+
code: "cursor-skill-frontmatter-unsupported",
|
|
10070
|
+
message: `Skill frontmatter field "${key}" is not supported by Cursor. Supported: ${supportedFields.join(", ")}`
|
|
10071
|
+
};
|
|
10072
|
+
}
|
|
10073
|
+
if (platform === "codex") {
|
|
10074
|
+
return {
|
|
10075
|
+
code: "codex-skill-frontmatter-translation",
|
|
10076
|
+
message: CODEX_SKILL_FIELD_TRANSLATIONS[key] ?? `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.`
|
|
10077
|
+
};
|
|
10078
|
+
}
|
|
10079
|
+
return {
|
|
10080
|
+
code: "opencode-skill-frontmatter-translation",
|
|
10081
|
+
message: OPENCODE_SKILL_FIELD_TRANSLATIONS[key] ?? `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.`
|
|
10082
|
+
};
|
|
10083
|
+
}
|
|
10084
|
+
|
|
9350
10085
|
// src/runtime-script-contract.ts
|
|
9351
10086
|
var INSTALLER_OWNED_CHECK_ENV_PATH = "scripts/check-env.sh";
|
|
9352
10087
|
var RUNTIME_SCRIPT_ROLE_PATHS = {
|
|
@@ -9493,7 +10228,7 @@ function isCodexManifestRelativePath(path) {
|
|
|
9493
10228
|
if (relativePath.includes("..")) return false;
|
|
9494
10229
|
return !relativePath.startsWith("/") && !relativePath.startsWith("\\");
|
|
9495
10230
|
}
|
|
9496
|
-
function
|
|
10231
|
+
function asRecord3(value) {
|
|
9497
10232
|
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
9498
10233
|
return value;
|
|
9499
10234
|
}
|
|
@@ -9660,7 +10395,7 @@ function lintBrandMetadata(config, issues) {
|
|
|
9660
10395
|
}
|
|
9661
10396
|
function lintCodexOverrides(config, issues) {
|
|
9662
10397
|
if (!isCodexTargetEnabled(config)) return;
|
|
9663
|
-
const iface =
|
|
10398
|
+
const iface = asRecord3(config.platforms?.codex?.interface);
|
|
9664
10399
|
if (!iface) return;
|
|
9665
10400
|
if (typeof iface.brandColor === "string" && !HEX_COLOR_REGEX.test(iface.brandColor)) {
|
|
9666
10401
|
pushIssue(issues, {
|
|
@@ -10202,45 +10937,44 @@ function lintAgentTranslationExplainability(dir, config, issues) {
|
|
|
10202
10937
|
const metadata = getCanonicalAgentMetadata(agent);
|
|
10203
10938
|
const relativePath = relative7(dir, agent.filePath).replace(/\\/g, "/");
|
|
10204
10939
|
if (config.targets.includes("cursor")) {
|
|
10205
|
-
const cursorDegradedFields =
|
|
10206
|
-
|
|
10207
|
-
if (
|
|
10208
|
-
if (metadata.sandboxMode) cursorDegradedFields.push("sandbox_mode");
|
|
10209
|
-
if (metadata.permission) cursorDegradedFields.push("permission");
|
|
10210
|
-
if (metadata.tools !== void 0) cursorDegradedFields.push("tools");
|
|
10211
|
-
if (metadata.memory) cursorDegradedFields.push("memory");
|
|
10212
|
-
if (typeof metadata.background === "boolean") cursorDegradedFields.push("background");
|
|
10213
|
-
if (metadata.isolation) cursorDegradedFields.push("isolation");
|
|
10214
|
-
if (cursorDegradedFields.length > 0) {
|
|
10940
|
+
const cursorDegradedFields = getTranslatedAgentFields("cursor", metadata);
|
|
10941
|
+
const message = getAgentTranslationMessage("cursor", cursorDegradedFields);
|
|
10942
|
+
if (message) {
|
|
10215
10943
|
pushIssue(issues, {
|
|
10216
10944
|
level: "warning",
|
|
10217
10945
|
code: "cursor-agent-translation",
|
|
10218
|
-
message
|
|
10946
|
+
message,
|
|
10219
10947
|
file: relativePath,
|
|
10220
10948
|
platform: "Cursor"
|
|
10221
10949
|
});
|
|
10222
10950
|
}
|
|
10223
10951
|
}
|
|
10224
10952
|
if (config.targets.includes("codex")) {
|
|
10225
|
-
const codexDegradedFields =
|
|
10226
|
-
|
|
10227
|
-
if (
|
|
10228
|
-
if (metadata.tools !== void 0) codexDegradedFields.push("tools");
|
|
10229
|
-
if (metadata.skills) codexDegradedFields.push("skills");
|
|
10230
|
-
if (metadata.memory) codexDegradedFields.push("memory");
|
|
10231
|
-
if (typeof metadata.background === "boolean") codexDegradedFields.push("background");
|
|
10232
|
-
if (metadata.isolation) codexDegradedFields.push("isolation");
|
|
10233
|
-
if (metadata.color) codexDegradedFields.push("color");
|
|
10234
|
-
if (codexDegradedFields.length > 0) {
|
|
10953
|
+
const codexDegradedFields = getTranslatedAgentFields("codex", metadata);
|
|
10954
|
+
const message = getAgentTranslationMessage("codex", codexDegradedFields);
|
|
10955
|
+
if (message) {
|
|
10235
10956
|
pushIssue(issues, {
|
|
10236
10957
|
level: "warning",
|
|
10237
10958
|
code: "codex-agent-translation",
|
|
10238
|
-
message
|
|
10959
|
+
message,
|
|
10239
10960
|
file: relativePath,
|
|
10240
10961
|
platform: "Codex"
|
|
10241
10962
|
});
|
|
10242
10963
|
}
|
|
10243
10964
|
}
|
|
10965
|
+
if (config.targets.includes("opencode")) {
|
|
10966
|
+
const opencodeDegradedFields = getTranslatedAgentFields("opencode", metadata);
|
|
10967
|
+
const message = getAgentTranslationMessage("opencode", opencodeDegradedFields);
|
|
10968
|
+
if (message) {
|
|
10969
|
+
pushIssue(issues, {
|
|
10970
|
+
level: "warning",
|
|
10971
|
+
code: "opencode-agent-translation",
|
|
10972
|
+
message,
|
|
10973
|
+
file: relativePath,
|
|
10974
|
+
platform: "OpenCode"
|
|
10975
|
+
});
|
|
10976
|
+
}
|
|
10977
|
+
}
|
|
10244
10978
|
}
|
|
10245
10979
|
}
|
|
10246
10980
|
function lintCommandTranslationExplainability(dir, config, issues) {
|
|
@@ -10250,15 +10984,13 @@ function lintCommandTranslationExplainability(dir, config, issues) {
|
|
|
10250
10984
|
const metadata = getCanonicalCommandMetadata(command2);
|
|
10251
10985
|
const relativePath = relative7(dir, command2.filePath).replace(/\\/g, "/");
|
|
10252
10986
|
if (config.targets.includes("codex")) {
|
|
10253
|
-
const degradedFields =
|
|
10254
|
-
|
|
10255
|
-
if (
|
|
10256
|
-
if (metadata.model) degradedFields.push("model");
|
|
10257
|
-
if (degradedFields.length > 0) {
|
|
10987
|
+
const degradedFields = getTranslatedCommandFields("codex", metadata);
|
|
10988
|
+
const message = getCommandTranslationMessage("codex", degradedFields);
|
|
10989
|
+
if (degradedFields.length > 0 && message) {
|
|
10258
10990
|
pushIssue(issues, {
|
|
10259
10991
|
level: "warning",
|
|
10260
10992
|
code: "codex-command-translation",
|
|
10261
|
-
message
|
|
10993
|
+
message,
|
|
10262
10994
|
file: relativePath,
|
|
10263
10995
|
platform: "Codex"
|
|
10264
10996
|
});
|
|
@@ -10340,9 +11072,9 @@ function lintLegacyCommandsDir(dir, config, issues) {
|
|
|
10340
11072
|
}
|
|
10341
11073
|
function lintCodexAgentsConfig(config, issues) {
|
|
10342
11074
|
if (!isCodexTargetEnabled(config)) return;
|
|
10343
|
-
const codexOverrides =
|
|
11075
|
+
const codexOverrides = asRecord3(config.platforms?.codex);
|
|
10344
11076
|
if (!codexOverrides) return;
|
|
10345
|
-
const agents =
|
|
11077
|
+
const agents = asRecord3(codexOverrides.agents);
|
|
10346
11078
|
if (!agents) return;
|
|
10347
11079
|
if (typeof agents.max_threads === "number" && agents.max_threads < CODEX_RULES.agents.maxThreadsMin) {
|
|
10348
11080
|
pushIssue(issues, {
|
|
@@ -10366,8 +11098,8 @@ function lintCodexAgentsConfig(config, issues) {
|
|
|
10366
11098
|
function lintCodexHooksExternalConfig(config, issues) {
|
|
10367
11099
|
if (!isCodexTargetEnabled(config) || !config.hooks) return;
|
|
10368
11100
|
if (Object.keys(config.hooks).length === 0) return;
|
|
10369
|
-
const codexOverrides =
|
|
10370
|
-
const features = codexOverrides ?
|
|
11101
|
+
const codexOverrides = asRecord3(config.platforms?.codex);
|
|
11102
|
+
const features = codexOverrides ? asRecord3(codexOverrides.features) : null;
|
|
10371
11103
|
const hasPluxxCodexHooksFlag = features && features.codex_hooks === true;
|
|
10372
11104
|
if (hasPluxxCodexHooksFlag) return;
|
|
10373
11105
|
pushIssue(issues, {
|
|
@@ -10423,12 +11155,14 @@ function lintRuntimeReadiness(config, issues) {
|
|
|
10423
11155
|
}
|
|
10424
11156
|
function lintCursorHooks(config, issues) {
|
|
10425
11157
|
if (!config.targets.includes("cursor") || !config.hooks) return;
|
|
11158
|
+
const supportedCursorEvents = getSupportedHookEvents("cursor");
|
|
11159
|
+
const unsupportedCursorEventReason = getUnsupportedHookEventReason("cursor");
|
|
10426
11160
|
for (const [hookEvent, hookEntries] of Object.entries(config.hooks)) {
|
|
10427
|
-
if (!
|
|
11161
|
+
if (!isHookEventSupported("cursor", hookEvent)) {
|
|
10428
11162
|
pushIssue(issues, {
|
|
10429
11163
|
level: "warning",
|
|
10430
11164
|
code: "cursor-hook-event-unknown",
|
|
10431
|
-
message: `Cursor does not support hook event "${hookEvent}". Supported: ${
|
|
11165
|
+
message: unsupportedCursorEventReason ? `Cursor does not support hook event "${hookEvent}". ${unsupportedCursorEventReason}` : `Cursor does not support hook event "${hookEvent}". Supported: ${supportedCursorEvents.join(", ")}`,
|
|
10432
11166
|
file: "pluxx.config.ts",
|
|
10433
11167
|
platform: "Cursor"
|
|
10434
11168
|
});
|
|
@@ -10437,11 +11171,12 @@ function lintCursorHooks(config, issues) {
|
|
|
10437
11171
|
for (const entry of hookEntries) {
|
|
10438
11172
|
if (!entry || typeof entry !== "object") continue;
|
|
10439
11173
|
const rec = entry;
|
|
10440
|
-
if (
|
|
11174
|
+
if (typeof rec.type === "string" && !isHookTypeSupported("cursor", rec.type)) {
|
|
11175
|
+
const issue = getHookTypeTranslationIssue("cursor", rec.type);
|
|
10441
11176
|
pushIssue(issues, {
|
|
10442
11177
|
level: "warning",
|
|
10443
|
-
code: "cursor-hook-type-unsupported",
|
|
10444
|
-
message: `Cursor does not document hook type "${String(rec.type)}". Pluxx currently preserves only command and prompt hooks on the Cursor hook surface.`,
|
|
11178
|
+
code: issue?.code ?? "cursor-hook-type-unsupported",
|
|
11179
|
+
message: issue?.message ?? `Cursor does not document hook type "${String(rec.type)}". Pluxx currently preserves only command and prompt hooks on the Cursor hook surface.`,
|
|
10445
11180
|
file: "pluxx.config.ts",
|
|
10446
11181
|
platform: "Cursor"
|
|
10447
11182
|
});
|
|
@@ -10473,50 +11208,16 @@ function lintCursorSkillFrontmatter(config, skillFiles, issues, frontmatterCache
|
|
|
10473
11208
|
for (const [key] of parsed.frontmatterFields) {
|
|
10474
11209
|
for (const [target, supported] of supportedByTarget.entries()) {
|
|
10475
11210
|
if (supported.has(key)) continue;
|
|
10476
|
-
|
|
10477
|
-
|
|
10478
|
-
|
|
10479
|
-
|
|
10480
|
-
|
|
10481
|
-
platform: "Cursor"
|
|
10482
|
-
};
|
|
10483
|
-
} else if (target === "codex") {
|
|
10484
|
-
const codexFieldTranslation = {
|
|
10485
|
-
"argument-hint": "Pluxx currently translates `argument-hint` into Codex command or routing guidance rather than documented Codex skill frontmatter.",
|
|
10486
|
-
arguments: "Pluxx currently translates skill `arguments` into Codex command or routing guidance rather than documented Codex skill frontmatter.",
|
|
10487
|
-
"allowed-tools": "Pluxx currently translates `allowed-tools` into Codex permission companions or external config rather than documented Codex skill frontmatter.",
|
|
10488
|
-
context: "Pluxx currently treats skill `context` as companion instruction intent because Codex does not document an equivalent skill frontmatter field.",
|
|
10489
|
-
agent: "Pluxx currently translates skill `agent` intent through Codex custom agents or routing guidance rather than documented Codex skill frontmatter.",
|
|
10490
|
-
hooks: "Pluxx currently translates skill-local `hooks` intent through bundled Codex hooks, where skill-local attachment is lost.",
|
|
10491
|
-
paths: "Pluxx currently translates skill `paths` into surrounding instruction or routing context because Codex does not document an equivalent skill frontmatter field.",
|
|
10492
|
-
shell: "Pluxx currently translates skill `shell` intent through command or runtime guidance because Codex does not document an equivalent skill frontmatter field."
|
|
10493
|
-
};
|
|
10494
|
-
issue = {
|
|
10495
|
-
code: "codex-skill-frontmatter-translation",
|
|
10496
|
-
message: codexFieldTranslation[key] ?? `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.`,
|
|
10497
|
-
platform: "Codex"
|
|
10498
|
-
};
|
|
10499
|
-
} else {
|
|
10500
|
-
const opencodeFieldTranslation = {
|
|
10501
|
-
"argument-hint": "Pluxx currently translates `argument-hint` into OpenCode commands or runtime command metadata rather than documented OpenCode skill frontmatter.",
|
|
10502
|
-
arguments: "Pluxx currently translates skill `arguments` into OpenCode commands or runtime command metadata rather than documented OpenCode skill frontmatter.",
|
|
10503
|
-
"allowed-tools": "Pluxx currently translates `allowed-tools` into OpenCode permission config rather than documented OpenCode skill frontmatter.",
|
|
10504
|
-
context: "Pluxx currently translates skill `context` through runtime instruction injection or neighboring config because OpenCode does not document an equivalent skill frontmatter field.",
|
|
10505
|
-
agent: "Pluxx currently translates skill `agent` intent through OpenCode agents or runtime routing rather than documented OpenCode skill frontmatter.",
|
|
10506
|
-
hooks: "Pluxx currently translates skill-local `hooks` intent through OpenCode runtime event handlers, where skill-local attachment is lost.",
|
|
10507
|
-
paths: "Pluxx currently translates skill `paths` into runtime or instruction context because OpenCode does not document an equivalent skill frontmatter field.",
|
|
10508
|
-
shell: "Pluxx currently translates skill `shell` intent through runtime code or command execution guidance because OpenCode does not document an equivalent skill frontmatter field."
|
|
10509
|
-
};
|
|
10510
|
-
issue = {
|
|
10511
|
-
code: "opencode-skill-frontmatter-translation",
|
|
10512
|
-
message: opencodeFieldTranslation[key] ?? `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.`,
|
|
10513
|
-
platform: "OpenCode"
|
|
10514
|
-
};
|
|
10515
|
-
}
|
|
11211
|
+
const issue = getSkillFrontmatterTranslationIssue(
|
|
11212
|
+
target,
|
|
11213
|
+
key,
|
|
11214
|
+
[...supported]
|
|
11215
|
+
);
|
|
10516
11216
|
pushIssue(issues, {
|
|
10517
11217
|
level: "warning",
|
|
10518
11218
|
file: skillFile,
|
|
10519
|
-
...issue
|
|
11219
|
+
...issue,
|
|
11220
|
+
platform: target === "cursor" ? "Cursor" : target === "codex" ? "Codex" : "OpenCode"
|
|
10520
11221
|
});
|
|
10521
11222
|
}
|
|
10522
11223
|
}
|
|
@@ -10535,69 +11236,111 @@ function lintHookFieldTranslations(config, issues) {
|
|
|
10535
11236
|
);
|
|
10536
11237
|
if (hasPromptHooks) {
|
|
10537
11238
|
if (config.targets.includes("claude-code")) {
|
|
10538
|
-
|
|
10539
|
-
|
|
10540
|
-
|
|
10541
|
-
|
|
10542
|
-
|
|
10543
|
-
|
|
10544
|
-
|
|
11239
|
+
const warnedEvents = /* @__PURE__ */ new Set();
|
|
11240
|
+
for (const [event, entries] of Object.entries(config.hooks)) {
|
|
11241
|
+
if (!(entries ?? []).some((entry) => entry.type === "prompt")) continue;
|
|
11242
|
+
const mappedEvent = mapHookEventToPascalCase(event);
|
|
11243
|
+
const issue = getPromptHookTranslationIssue("claude-code", mappedEvent);
|
|
11244
|
+
if (!issue || warnedEvents.has(mappedEvent)) continue;
|
|
11245
|
+
warnedEvents.add(mappedEvent);
|
|
11246
|
+
pushIssue(issues, {
|
|
11247
|
+
level: "warning",
|
|
11248
|
+
file: "pluxx.config.ts",
|
|
11249
|
+
platform: "claude-code",
|
|
11250
|
+
...issue
|
|
11251
|
+
});
|
|
11252
|
+
}
|
|
10545
11253
|
}
|
|
10546
11254
|
if (config.targets.includes("codex")) {
|
|
10547
|
-
|
|
10548
|
-
|
|
10549
|
-
|
|
10550
|
-
|
|
10551
|
-
|
|
10552
|
-
|
|
10553
|
-
|
|
11255
|
+
const issue = getPromptHookTranslationIssue("codex");
|
|
11256
|
+
if (issue) {
|
|
11257
|
+
pushIssue(issues, {
|
|
11258
|
+
level: "warning",
|
|
11259
|
+
file: "pluxx.config.ts",
|
|
11260
|
+
platform: "codex",
|
|
11261
|
+
...issue
|
|
11262
|
+
});
|
|
11263
|
+
}
|
|
10554
11264
|
}
|
|
10555
11265
|
if (config.targets.includes("opencode")) {
|
|
11266
|
+
const issue = getPromptHookTranslationIssue("opencode");
|
|
11267
|
+
if (issue) {
|
|
11268
|
+
pushIssue(issues, {
|
|
11269
|
+
level: "warning",
|
|
11270
|
+
file: "pluxx.config.ts",
|
|
11271
|
+
platform: "opencode",
|
|
11272
|
+
...issue
|
|
11273
|
+
});
|
|
11274
|
+
}
|
|
11275
|
+
}
|
|
11276
|
+
}
|
|
11277
|
+
if (hasFailClosed && config.targets.includes("claude-code")) {
|
|
11278
|
+
const issue = getHookFieldTranslationIssue("claude-code", "failClosed");
|
|
11279
|
+
if (issue) {
|
|
10556
11280
|
pushIssue(issues, {
|
|
10557
11281
|
level: "warning",
|
|
10558
|
-
code: "opencode-prompt-hook-drop",
|
|
10559
|
-
message: "The current OpenCode runtime wrapper only emits command hooks. Prompt hooks will be dropped from the generated OpenCode plugin.",
|
|
10560
11282
|
file: "pluxx.config.ts",
|
|
10561
|
-
platform: "
|
|
11283
|
+
platform: "claude-code",
|
|
11284
|
+
...issue
|
|
10562
11285
|
});
|
|
10563
11286
|
}
|
|
10564
11287
|
}
|
|
10565
|
-
if (hasFailClosed && config.targets.includes("claude-code")) {
|
|
10566
|
-
pushIssue(issues, {
|
|
10567
|
-
level: "warning",
|
|
10568
|
-
code: "claude-hook-failclosed-degrade",
|
|
10569
|
-
message: "Claude hook entries currently drop `failClosed` in generated output. Keep this behavior host-specific or verify the generated hook bundle carefully.",
|
|
10570
|
-
file: "pluxx.config.ts",
|
|
10571
|
-
platform: "claude-code"
|
|
10572
|
-
});
|
|
10573
|
-
}
|
|
10574
11288
|
if (hasLoopLimit) {
|
|
10575
11289
|
if (config.targets.includes("claude-code")) {
|
|
10576
|
-
|
|
10577
|
-
|
|
10578
|
-
|
|
10579
|
-
|
|
10580
|
-
|
|
10581
|
-
|
|
10582
|
-
|
|
11290
|
+
const issue = getHookFieldTranslationIssue("claude-code", "loop_limit");
|
|
11291
|
+
if (issue) {
|
|
11292
|
+
pushIssue(issues, {
|
|
11293
|
+
level: "warning",
|
|
11294
|
+
file: "pluxx.config.ts",
|
|
11295
|
+
platform: "claude-code",
|
|
11296
|
+
...issue
|
|
11297
|
+
});
|
|
11298
|
+
}
|
|
10583
11299
|
}
|
|
10584
11300
|
if (config.targets.includes("codex")) {
|
|
10585
|
-
|
|
10586
|
-
|
|
10587
|
-
|
|
10588
|
-
|
|
10589
|
-
|
|
10590
|
-
|
|
10591
|
-
|
|
11301
|
+
const issue = getHookFieldTranslationIssue("codex", "loop_limit");
|
|
11302
|
+
if (issue) {
|
|
11303
|
+
pushIssue(issues, {
|
|
11304
|
+
level: "warning",
|
|
11305
|
+
file: "pluxx.config.ts",
|
|
11306
|
+
platform: "codex",
|
|
11307
|
+
...issue
|
|
11308
|
+
});
|
|
11309
|
+
}
|
|
10592
11310
|
}
|
|
10593
11311
|
if (config.targets.includes("opencode")) {
|
|
10594
|
-
|
|
10595
|
-
|
|
10596
|
-
|
|
10597
|
-
|
|
10598
|
-
|
|
10599
|
-
|
|
10600
|
-
|
|
11312
|
+
const issue = getHookFieldTranslationIssue("opencode", "loop_limit");
|
|
11313
|
+
if (issue) {
|
|
11314
|
+
pushIssue(issues, {
|
|
11315
|
+
level: "warning",
|
|
11316
|
+
file: "pluxx.config.ts",
|
|
11317
|
+
platform: "opencode",
|
|
11318
|
+
...issue
|
|
11319
|
+
});
|
|
11320
|
+
}
|
|
11321
|
+
}
|
|
11322
|
+
}
|
|
11323
|
+
}
|
|
11324
|
+
function lintHookTypeTranslations(config, issues) {
|
|
11325
|
+
if (!config.hooks) return;
|
|
11326
|
+
const seen = /* @__PURE__ */ new Set();
|
|
11327
|
+
for (const entries of Object.values(config.hooks)) {
|
|
11328
|
+
for (const entry of entries ?? []) {
|
|
11329
|
+
if (!entry.type || entry.type === "command") continue;
|
|
11330
|
+
for (const target of ["codex", "opencode"]) {
|
|
11331
|
+
if (!config.targets.includes(target) || isHookTypeSupported(target, entry.type)) continue;
|
|
11332
|
+
const key = `${target}:${entry.type}`;
|
|
11333
|
+
if (seen.has(key)) continue;
|
|
11334
|
+
seen.add(key);
|
|
11335
|
+
const issue = getHookTypeTranslationIssue(target, entry.type);
|
|
11336
|
+
if (!issue) continue;
|
|
11337
|
+
pushIssue(issues, {
|
|
11338
|
+
level: "warning",
|
|
11339
|
+
file: "pluxx.config.ts",
|
|
11340
|
+
platform: target,
|
|
11341
|
+
...issue
|
|
11342
|
+
});
|
|
11343
|
+
}
|
|
10601
11344
|
}
|
|
10602
11345
|
}
|
|
10603
11346
|
}
|
|
@@ -10606,7 +11349,7 @@ function lintCodexCommandGuidance(config, issues) {
|
|
|
10606
11349
|
pushIssue(issues, {
|
|
10607
11350
|
level: "warning",
|
|
10608
11351
|
code: "codex-commands-routing-guidance",
|
|
10609
|
-
message:
|
|
11352
|
+
message: `${getCodexCommandGuidanceNote()} Pluxx will degrade commands into skills plus those routing surfaces today.`,
|
|
10610
11353
|
file: "pluxx.config.ts",
|
|
10611
11354
|
platform: "codex"
|
|
10612
11355
|
});
|
|
@@ -10816,6 +11559,7 @@ async function lintProject(dir = process.cwd(), options = {}) {
|
|
|
10816
11559
|
lintPermissions(lintConfig, issues);
|
|
10817
11560
|
lintPrimitiveTranslations(lintConfig, issues);
|
|
10818
11561
|
lintHookFieldTranslations(lintConfig, issues);
|
|
11562
|
+
lintHookTypeTranslations(lintConfig, issues);
|
|
10819
11563
|
lintCodexCommandGuidance(lintConfig, issues);
|
|
10820
11564
|
lintCursorHooks(lintConfig, issues);
|
|
10821
11565
|
lintCursorRuleContentLimits(lintConfig, issues);
|
|
@@ -10902,131 +11646,6 @@ import { resolve as resolve14 } from "path";
|
|
|
10902
11646
|
import { existsSync as existsSync17, lstatSync as lstatSync2 } from "fs";
|
|
10903
11647
|
import { mkdir as mkdir2 } from "fs/promises";
|
|
10904
11648
|
import { basename as basename5, dirname as dirname4, relative as relative8, resolve as resolve13 } from "path";
|
|
10905
|
-
|
|
10906
|
-
// src/user-config.ts
|
|
10907
|
-
var ENV_VAR_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
10908
|
-
var PLACEHOLDER_SECRET_PATTERNS = [
|
|
10909
|
-
/\bdummy\b/i,
|
|
10910
|
-
/\bplaceholder\b/i,
|
|
10911
|
-
/\bexample\b/i,
|
|
10912
|
-
/\bchangeme\b/i,
|
|
10913
|
-
/\breplace[_ -]?me\b/i,
|
|
10914
|
-
/\byour[_ -]?(api[_ -]?)?key\b/i,
|
|
10915
|
-
/\bapi[_ -]?key[_ -]?here\b/i,
|
|
10916
|
-
/\btoken[_ -]?here\b/i
|
|
10917
|
-
];
|
|
10918
|
-
function normalizeUserConfigKey(value) {
|
|
10919
|
-
return value.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2").replace(/[._/\s]+/g, "-").toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
|
|
10920
|
-
}
|
|
10921
|
-
function humanizeUserConfigLabel(value) {
|
|
10922
|
-
return value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/[._/]+/g, " ").replace(/-/g, " ").split(/\s+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()).join(" ");
|
|
10923
|
-
}
|
|
10924
|
-
function defaultUserConfigEnvVar(key) {
|
|
10925
|
-
return key.replace(/[^A-Za-z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
|
|
10926
|
-
}
|
|
10927
|
-
function extractEnvReference(value) {
|
|
10928
|
-
if (!value) return void 0;
|
|
10929
|
-
const match = value.match(/^\$\{([A-Za-z_][A-Za-z0-9_]*)\}$/);
|
|
10930
|
-
return match?.[1];
|
|
10931
|
-
}
|
|
10932
|
-
function isPlaceholderSecretValue(value) {
|
|
10933
|
-
if (typeof value !== "string") return false;
|
|
10934
|
-
const normalized = value.trim();
|
|
10935
|
-
if (normalized === "") return false;
|
|
10936
|
-
return PLACEHOLDER_SECRET_PATTERNS.some((pattern) => pattern.test(normalized));
|
|
10937
|
-
}
|
|
10938
|
-
function isRuntimePlatformManaged(config, target, server) {
|
|
10939
|
-
if (server.transport === "stdio") return false;
|
|
10940
|
-
if (server.auth?.type === "platform") {
|
|
10941
|
-
return true;
|
|
10942
|
-
}
|
|
10943
|
-
if (target === "claude-code") {
|
|
10944
|
-
return config.platforms?.["claude-code"]?.mcpAuth === "platform";
|
|
10945
|
-
}
|
|
10946
|
-
if (target === "cursor") {
|
|
10947
|
-
return config.platforms?.cursor?.mcpAuth === "platform";
|
|
10948
|
-
}
|
|
10949
|
-
return false;
|
|
10950
|
-
}
|
|
10951
|
-
function targetApplies(entry, target) {
|
|
10952
|
-
return !entry.targets || entry.targets.includes(target);
|
|
10953
|
-
}
|
|
10954
|
-
function dedupeUserConfigEntries(entries) {
|
|
10955
|
-
const deduped = [];
|
|
10956
|
-
const seenKeys = /* @__PURE__ */ new Set();
|
|
10957
|
-
const seenEnvVars = /* @__PURE__ */ new Set();
|
|
10958
|
-
for (const entry of entries) {
|
|
10959
|
-
const envVar = entry.envVar?.trim();
|
|
10960
|
-
if (seenKeys.has(entry.key)) continue;
|
|
10961
|
-
if (envVar && seenEnvVars.has(envVar)) continue;
|
|
10962
|
-
deduped.push(entry);
|
|
10963
|
-
seenKeys.add(entry.key);
|
|
10964
|
-
if (envVar) seenEnvVars.add(envVar);
|
|
10965
|
-
}
|
|
10966
|
-
return deduped;
|
|
10967
|
-
}
|
|
10968
|
-
function collectUserConfigEntries(config, platforms = config.targets) {
|
|
10969
|
-
const explicitEntries = (config.userConfig ?? []).filter((entry) => !entry.targets || entry.targets.some((target) => platforms.includes(target))).map((entry) => ({ ...entry, source: "explicit" }));
|
|
10970
|
-
const derivedEntries = [];
|
|
10971
|
-
for (const [serverName, server] of Object.entries(config.mcp ?? {})) {
|
|
10972
|
-
const applicableTargets = platforms.filter((target) => !isRuntimePlatformManaged(config, target, server));
|
|
10973
|
-
if (server.auth?.type && server.auth.type !== "none" && server.auth.type !== "platform" && ENV_VAR_NAME.test(server.auth.envVar) && applicableTargets.length > 0) {
|
|
10974
|
-
derivedEntries.push({
|
|
10975
|
-
key: normalizeUserConfigKey(server.auth.envVar),
|
|
10976
|
-
title: humanizeUserConfigLabel(server.auth.envVar),
|
|
10977
|
-
description: `Authentication credential for the ${serverName} MCP server.`,
|
|
10978
|
-
type: "secret",
|
|
10979
|
-
required: true,
|
|
10980
|
-
envVar: server.auth.envVar,
|
|
10981
|
-
targets: applicableTargets,
|
|
10982
|
-
source: "mcp-auth"
|
|
10983
|
-
});
|
|
10984
|
-
}
|
|
10985
|
-
if (server.transport === "stdio") {
|
|
10986
|
-
for (const [key, rawValue] of Object.entries(server.env ?? {})) {
|
|
10987
|
-
const envVar = extractEnvReference(rawValue) ?? (ENV_VAR_NAME.test(key) ? key : void 0);
|
|
10988
|
-
if (!envVar || !ENV_VAR_NAME.test(envVar)) continue;
|
|
10989
|
-
if (extractEnvReference(rawValue) === void 0 && rawValue !== "") {
|
|
10990
|
-
continue;
|
|
10991
|
-
}
|
|
10992
|
-
derivedEntries.push({
|
|
10993
|
-
key: normalizeUserConfigKey(envVar),
|
|
10994
|
-
title: humanizeUserConfigLabel(envVar),
|
|
10995
|
-
description: `Environment value required to launch the ${serverName} stdio MCP server.`,
|
|
10996
|
-
type: "secret",
|
|
10997
|
-
required: true,
|
|
10998
|
-
envVar,
|
|
10999
|
-
targets: applicableTargets.length > 0 ? applicableTargets : platforms,
|
|
11000
|
-
source: "mcp-env"
|
|
11001
|
-
});
|
|
11002
|
-
}
|
|
11003
|
-
}
|
|
11004
|
-
}
|
|
11005
|
-
return dedupeUserConfigEntries([
|
|
11006
|
-
...explicitEntries,
|
|
11007
|
-
...derivedEntries
|
|
11008
|
-
]);
|
|
11009
|
-
}
|
|
11010
|
-
function resolveUserConfigEntriesForTarget(entries, target) {
|
|
11011
|
-
return entries.filter(({ field }) => targetApplies(field, target));
|
|
11012
|
-
}
|
|
11013
|
-
function buildUserConfigEnvMap(entries) {
|
|
11014
|
-
const env = {};
|
|
11015
|
-
for (const entry of entries) {
|
|
11016
|
-
if (!entry.envVar) continue;
|
|
11017
|
-
env[entry.envVar] = String(entry.value);
|
|
11018
|
-
}
|
|
11019
|
-
return env;
|
|
11020
|
-
}
|
|
11021
|
-
function buildUserConfigValueMap(entries) {
|
|
11022
|
-
const values = {};
|
|
11023
|
-
for (const entry of entries) {
|
|
11024
|
-
values[entry.field.key] = entry.value;
|
|
11025
|
-
}
|
|
11026
|
-
return values;
|
|
11027
|
-
}
|
|
11028
|
-
|
|
11029
|
-
// src/cli/init-from-mcp.ts
|
|
11030
11649
|
var MCP_SKILL_GROUPINGS = ["workflow", "tool"];
|
|
11031
11650
|
var MCP_HOOK_MODES = ["none", "safe"];
|
|
11032
11651
|
var MCP_RUNTIME_AUTH_MODES = ["inline", "platform"];
|
|
@@ -11243,17 +11862,29 @@ async function planMcpScaffold(options) {
|
|
|
11243
11862
|
const instructionsPath = resolve13(options.rootDir, "INSTRUCTIONS.md");
|
|
11244
11863
|
const skillRoot = resolve13(options.rootDir, "skills");
|
|
11245
11864
|
const commandsRoot = resolve13(options.rootDir, "commands");
|
|
11865
|
+
const basePlatforms = runtimeAuthMode === "platform" ? {
|
|
11866
|
+
"claude-code": { mcpAuth: "platform" },
|
|
11867
|
+
cursor: { mcpAuth: "platform" }
|
|
11868
|
+
} : void 0;
|
|
11869
|
+
const platforms = mergePlatformConfigMaps(basePlatforms, options.platformOverrides);
|
|
11246
11870
|
const userConfigSource = {
|
|
11247
11871
|
targets: options.targets,
|
|
11248
11872
|
mcp: {
|
|
11249
11873
|
[serverName]: options.source
|
|
11250
11874
|
},
|
|
11251
|
-
platforms
|
|
11252
|
-
"claude-code": { mcpAuth: "platform" },
|
|
11253
|
-
cursor: { mcpAuth: "platform" }
|
|
11254
|
-
} : void 0
|
|
11875
|
+
platforms
|
|
11255
11876
|
};
|
|
11256
|
-
const
|
|
11877
|
+
const baseUserConfig = collectUserConfigEntries(userConfigSource);
|
|
11878
|
+
const userConfig = [
|
|
11879
|
+
...baseUserConfig,
|
|
11880
|
+
...collectNativeMcpAuthUserConfigEntries(userConfigSource, options.targets, baseUserConfig)
|
|
11881
|
+
].map((entry) => {
|
|
11882
|
+
if ("source" in entry) {
|
|
11883
|
+
const { source: _source, ...rest } = entry;
|
|
11884
|
+
return rest;
|
|
11885
|
+
}
|
|
11886
|
+
return entry;
|
|
11887
|
+
});
|
|
11257
11888
|
const skillDirectories = [];
|
|
11258
11889
|
const commandFiles = [];
|
|
11259
11890
|
const generatedFiles = ["pluxx.config.ts", "./INSTRUCTIONS.md"];
|
|
@@ -11283,6 +11914,7 @@ async function planMcpScaffold(options) {
|
|
|
11283
11914
|
passthroughPaths,
|
|
11284
11915
|
runtimeAuthMode,
|
|
11285
11916
|
permissions,
|
|
11917
|
+
platforms,
|
|
11286
11918
|
commandsPath: plannedCommands.length > 0 ? "./commands/" : void 0
|
|
11287
11919
|
})
|
|
11288
11920
|
);
|
|
@@ -11521,6 +12153,7 @@ function buildSkillContent(skill) {
|
|
|
11521
12153
|
function buildCommandContent(skill, existingContent) {
|
|
11522
12154
|
const description = truncate(cleanSingleLineText(skill.description), 140);
|
|
11523
12155
|
const argumentHint = inferCommandArgumentHint(skill);
|
|
12156
|
+
const argumentNames = inferCommandArgumentNames(skill);
|
|
11524
12157
|
const entryBlurb = buildCommandEntryBlurb(skill);
|
|
11525
12158
|
const hasRelatedResources = skill.resources.length > 0 || skill.resourceTemplates.length > 0;
|
|
11526
12159
|
const hasRelatedPrompts = skill.prompts.length > 0;
|
|
@@ -11539,7 +12172,10 @@ function buildCommandContent(skill, existingContent) {
|
|
|
11539
12172
|
const generatedContent = [
|
|
11540
12173
|
"---",
|
|
11541
12174
|
`description: ${JSON.stringify(description)}`,
|
|
12175
|
+
`when_to_use: ${JSON.stringify(entryBlurb)}`,
|
|
11542
12176
|
`argument-hint: ${formatArgumentHintFrontmatter(argumentHint)}`,
|
|
12177
|
+
`arguments: ${JSON.stringify(argumentNames)}`,
|
|
12178
|
+
`skill: ${JSON.stringify(skill.dirName)}`,
|
|
11543
12179
|
"---",
|
|
11544
12180
|
"",
|
|
11545
12181
|
entryBlurb,
|
|
@@ -11704,15 +12340,8 @@ function buildConfigTemplate(input) {
|
|
|
11704
12340
|
const permissionsBlock = input.permissions ? `
|
|
11705
12341
|
permissions: ${serializePermissions(input.permissions)},
|
|
11706
12342
|
` : "";
|
|
11707
|
-
const platformsBlock = input.
|
|
11708
|
-
platforms: {
|
|
11709
|
-
'claude-code': {
|
|
11710
|
-
mcpAuth: 'platform',
|
|
11711
|
-
},
|
|
11712
|
-
cursor: {
|
|
11713
|
-
mcpAuth: 'platform',
|
|
11714
|
-
},
|
|
11715
|
-
},
|
|
12343
|
+
const platformsBlock = input.platforms ? `
|
|
12344
|
+
platforms: ${serializePlatformOverrides(input.platforms)},
|
|
11716
12345
|
` : "";
|
|
11717
12346
|
return `import { definePlugin } from 'pluxx'
|
|
11718
12347
|
|
|
@@ -11861,6 +12490,88 @@ function serializePermissions(permissions) {
|
|
|
11861
12490
|
${entries}
|
|
11862
12491
|
}`;
|
|
11863
12492
|
}
|
|
12493
|
+
function mergePlatformConfigMaps(base, extra) {
|
|
12494
|
+
const merged = {
|
|
12495
|
+
...base ?? {},
|
|
12496
|
+
...extra ?? {}
|
|
12497
|
+
};
|
|
12498
|
+
if (base?.["claude-code"] || extra?.["claude-code"]) {
|
|
12499
|
+
merged["claude-code"] = {
|
|
12500
|
+
...base?.["claude-code"] ?? {},
|
|
12501
|
+
...extra?.["claude-code"] ?? {},
|
|
12502
|
+
...base?.["claude-code"]?.mcpServers || extra?.["claude-code"]?.mcpServers ? {
|
|
12503
|
+
mcpServers: {
|
|
12504
|
+
...base?.["claude-code"]?.mcpServers ?? {},
|
|
12505
|
+
...extra?.["claude-code"]?.mcpServers ?? {}
|
|
12506
|
+
}
|
|
12507
|
+
} : {}
|
|
12508
|
+
};
|
|
12509
|
+
}
|
|
12510
|
+
if (base?.cursor || extra?.cursor) {
|
|
12511
|
+
merged.cursor = {
|
|
12512
|
+
...base?.cursor ?? {},
|
|
12513
|
+
...extra?.cursor ?? {},
|
|
12514
|
+
...base?.cursor?.mcpServers || extra?.cursor?.mcpServers ? {
|
|
12515
|
+
mcpServers: {
|
|
12516
|
+
...base?.cursor?.mcpServers ?? {},
|
|
12517
|
+
...extra?.cursor?.mcpServers ?? {}
|
|
12518
|
+
}
|
|
12519
|
+
} : {}
|
|
12520
|
+
};
|
|
12521
|
+
}
|
|
12522
|
+
if (base?.codex || extra?.codex) {
|
|
12523
|
+
merged.codex = {
|
|
12524
|
+
...base?.codex ?? {},
|
|
12525
|
+
...extra?.codex ?? {},
|
|
12526
|
+
...base?.codex?.mcpServers || extra?.codex?.mcpServers ? {
|
|
12527
|
+
mcpServers: {
|
|
12528
|
+
...base?.codex?.mcpServers ?? {},
|
|
12529
|
+
...extra?.codex?.mcpServers ?? {}
|
|
12530
|
+
}
|
|
12531
|
+
} : {}
|
|
12532
|
+
};
|
|
12533
|
+
}
|
|
12534
|
+
if (base?.opencode || extra?.opencode) {
|
|
12535
|
+
merged.opencode = {
|
|
12536
|
+
...base?.opencode ?? {},
|
|
12537
|
+
...extra?.opencode ?? {},
|
|
12538
|
+
...base?.opencode?.mcpServers || extra?.opencode?.mcpServers ? {
|
|
12539
|
+
mcpServers: {
|
|
12540
|
+
...base?.opencode?.mcpServers ?? {},
|
|
12541
|
+
...extra?.opencode?.mcpServers ?? {}
|
|
12542
|
+
}
|
|
12543
|
+
} : {}
|
|
12544
|
+
};
|
|
12545
|
+
}
|
|
12546
|
+
return Object.keys(merged).length > 0 ? merged : void 0;
|
|
12547
|
+
}
|
|
12548
|
+
function serializePlatformOverrides(platforms) {
|
|
12549
|
+
return serializeTsLiteral(platforms, 1);
|
|
12550
|
+
}
|
|
12551
|
+
function serializeTsLiteral(value, indentLevel) {
|
|
12552
|
+
const indent = " ".repeat(indentLevel);
|
|
12553
|
+
const nextIndent = " ".repeat(indentLevel + 1);
|
|
12554
|
+
if (Array.isArray(value)) {
|
|
12555
|
+
if (value.length === 0) return "[]";
|
|
12556
|
+
return `[
|
|
12557
|
+
${value.map((item) => `${nextIndent}${serializeTsLiteral(item, indentLevel + 1)}`).join(",\n")}
|
|
12558
|
+
${indent}]`;
|
|
12559
|
+
}
|
|
12560
|
+
if (value && typeof value === "object") {
|
|
12561
|
+
const entries = Object.entries(value).filter(([, entryValue]) => entryValue !== void 0);
|
|
12562
|
+
if (entries.length === 0) return "{}";
|
|
12563
|
+
return `{
|
|
12564
|
+
${entries.map(([key, entryValue]) => `${nextIndent}${serializeTsKey(key)}: ${serializeTsLiteral(entryValue, indentLevel + 1)}`).join(",\n")}
|
|
12565
|
+
${indent}}`;
|
|
12566
|
+
}
|
|
12567
|
+
if (typeof value === "string") {
|
|
12568
|
+
return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
12569
|
+
}
|
|
12570
|
+
return JSON.stringify(value);
|
|
12571
|
+
}
|
|
12572
|
+
function serializeTsKey(key) {
|
|
12573
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? key : `'${key}'`;
|
|
12574
|
+
}
|
|
11864
12575
|
var MUTATING_PREFIXES = [
|
|
11865
12576
|
"create",
|
|
11866
12577
|
"add",
|
|
@@ -12374,6 +13085,13 @@ function buildInstructionSkillSummary(skill) {
|
|
|
12374
13085
|
return `\`${skill.dirName}\`: ${description} Primary tools: ${toolNames}.${resourceNote}${promptNote}`;
|
|
12375
13086
|
}
|
|
12376
13087
|
function inferCommandArgumentHint(skill) {
|
|
13088
|
+
const fieldHints = inferCommandArgumentNames(skill);
|
|
13089
|
+
if (fieldHints.length === 0) {
|
|
13090
|
+
return "[request]";
|
|
13091
|
+
}
|
|
13092
|
+
return fieldHints.slice(0, 2).map((hint) => `[${hint}]`).join(" ");
|
|
13093
|
+
}
|
|
13094
|
+
function inferCommandArgumentNames(skill) {
|
|
12377
13095
|
const fieldHints = /* @__PURE__ */ new Set();
|
|
12378
13096
|
for (const tool of skill.tools) {
|
|
12379
13097
|
const fields = getTopLevelSchemaFields(tool.inputSchema);
|
|
@@ -12386,9 +13104,9 @@ function inferCommandArgumentHint(skill) {
|
|
|
12386
13104
|
if (fieldHints.size >= 2) break;
|
|
12387
13105
|
}
|
|
12388
13106
|
if (fieldHints.size === 0) {
|
|
12389
|
-
return "
|
|
13107
|
+
return ["request"];
|
|
12390
13108
|
}
|
|
12391
|
-
return [...fieldHints].slice(0, 2)
|
|
13109
|
+
return [...fieldHints].slice(0, 2);
|
|
12392
13110
|
}
|
|
12393
13111
|
function buildCommandEntryBlurb(skill) {
|
|
12394
13112
|
const intent = inferSkillIntentPhrase(skill);
|
|
@@ -14798,12 +15516,21 @@ function loadManualAgentProjectModel(rootDir, config) {
|
|
|
14798
15516
|
const skillsDir = config.skills ? resolve17(rootDir, config.skills) : void 0;
|
|
14799
15517
|
const commandsDir = config.commands ? resolve17(rootDir, config.commands) : void 0;
|
|
14800
15518
|
const skills = readCanonicalSkillFiles2(rootDir, skillsDir);
|
|
14801
|
-
const commands = readCanonicalCommandFiles(commandsDir).map((command2) =>
|
|
14802
|
-
|
|
14803
|
-
|
|
14804
|
-
|
|
14805
|
-
|
|
14806
|
-
|
|
15519
|
+
const commands = readCanonicalCommandFiles(commandsDir).map((command2) => {
|
|
15520
|
+
const metadata = getCanonicalCommandMetadata(command2);
|
|
15521
|
+
return {
|
|
15522
|
+
path: normalizeRelativePath(relative10(rootDir, command2.filePath)),
|
|
15523
|
+
title: metadata.title,
|
|
15524
|
+
description: metadata.description,
|
|
15525
|
+
whenToUse: metadata.whenToUse,
|
|
15526
|
+
argumentHint: metadata.argumentHint,
|
|
15527
|
+
...metadata.arguments.length > 0 ? { arguments: metadata.arguments } : {},
|
|
15528
|
+
...metadata.examples.length > 0 ? { examples: metadata.examples } : {},
|
|
15529
|
+
...metadata.skill ? { skill: metadata.skill } : {},
|
|
15530
|
+
...metadata.skills.length > 0 ? { skills: metadata.skills } : {},
|
|
15531
|
+
...metadata.agent ? { agent: metadata.agent } : {}
|
|
15532
|
+
};
|
|
15533
|
+
});
|
|
14807
15534
|
return {
|
|
14808
15535
|
sourceKind: "manual",
|
|
14809
15536
|
displayName,
|
|
@@ -14872,6 +15599,9 @@ function buildAgentContext(config, project, lint, contextSources, ingestion, doc
|
|
|
14872
15599
|
if (skill.description) {
|
|
14873
15600
|
lines.push(`- Description: ${skill.description}`);
|
|
14874
15601
|
}
|
|
15602
|
+
if ((skill.supportPaths?.length ?? 0) > 0) {
|
|
15603
|
+
lines.push(`- Related files: ${skill.supportPaths?.map((path) => `\`${path}\``).join(", ")}`);
|
|
15604
|
+
}
|
|
14875
15605
|
if (relatedResourceLabels.length > 0) {
|
|
14876
15606
|
lines.push(`- Related resources: ${relatedResourceLabels.join(", ")}`);
|
|
14877
15607
|
}
|
|
@@ -14884,7 +15614,18 @@ function buildAgentContext(config, project, lint, contextSources, ingestion, doc
|
|
|
14884
15614
|
lines.push("## Commands");
|
|
14885
15615
|
lines.push("");
|
|
14886
15616
|
for (const command2 of project.commands) {
|
|
14887
|
-
|
|
15617
|
+
const details = [
|
|
15618
|
+
command2.argumentHint ? `arguments: ${command2.argumentHint}` : null,
|
|
15619
|
+
command2.skill ? `skill: ${command2.skill}` : null,
|
|
15620
|
+
command2.agent ? `agent: ${command2.agent}` : null
|
|
15621
|
+
].filter(Boolean);
|
|
15622
|
+
lines.push(`- \`${command2.path}\`: ${command2.description ?? command2.title}${details.length > 0 ? ` (${details.join("; ")})` : ""}`);
|
|
15623
|
+
if (command2.whenToUse) {
|
|
15624
|
+
lines.push(` - When to use: ${command2.whenToUse}`);
|
|
15625
|
+
}
|
|
15626
|
+
if ((command2.examples?.length ?? 0) > 0) {
|
|
15627
|
+
lines.push(` - Examples: ${command2.examples?.map((example) => `\`${example}\``).join(", ")}`);
|
|
15628
|
+
}
|
|
14888
15629
|
}
|
|
14889
15630
|
lines.push("");
|
|
14890
15631
|
}
|
|
@@ -16301,13 +17042,17 @@ function normalizeRelativePath(path) {
|
|
|
16301
17042
|
return path.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
16302
17043
|
}
|
|
16303
17044
|
function readCanonicalSkillFiles2(rootDir, skillsDir) {
|
|
16304
|
-
return readCanonicalSkillFiles(skillsDir).map((skill) =>
|
|
16305
|
-
|
|
16306
|
-
|
|
16307
|
-
|
|
16308
|
-
|
|
16309
|
-
|
|
16310
|
-
|
|
17045
|
+
return readCanonicalSkillFiles(skillsDir).map((skill) => {
|
|
17046
|
+
const metadata = getCanonicalSkillMetadata(skill);
|
|
17047
|
+
return {
|
|
17048
|
+
dirName: normalizeRelativePath(skill.dirName),
|
|
17049
|
+
title: metadata.title,
|
|
17050
|
+
description: metadata.description,
|
|
17051
|
+
toolNames: [],
|
|
17052
|
+
path: normalizeRelativePath(relative10(rootDir, skill.filePath)),
|
|
17053
|
+
...metadata.supportPaths.length > 0 ? { supportPaths: metadata.supportPaths } : {}
|
|
17054
|
+
};
|
|
17055
|
+
});
|
|
16311
17056
|
}
|
|
16312
17057
|
function summarizeDiscoveryDescription(description, trailing) {
|
|
16313
17058
|
const base = description?.replace(/\s+/g, " ").trim().slice(0, 180);
|
|
@@ -16823,7 +17568,7 @@ ${additions.map((block) => `- ${block.replace(/\n/g, "\n ")}`).join("\n")}
|
|
|
16823
17568
|
}
|
|
16824
17569
|
|
|
16825
17570
|
// src/cli/doctor.ts
|
|
16826
|
-
import { spawn as spawn3 } from "child_process";
|
|
17571
|
+
import { spawn as spawn3, spawnSync as spawnSync2 } from "child_process";
|
|
16827
17572
|
import { accessSync, constants, existsSync as existsSync23, lstatSync as lstatSync3, readFileSync as readFileSync13, readdirSync as readdirSync10 } from "fs";
|
|
16828
17573
|
import { basename as basename6, dirname as dirname7, resolve as resolve19 } from "path";
|
|
16829
17574
|
|
|
@@ -16876,7 +17621,11 @@ function listHookCommands(hooks) {
|
|
|
16876
17621
|
return commands;
|
|
16877
17622
|
}
|
|
16878
17623
|
function planInstallUserConfig(config, platforms = config.targets) {
|
|
16879
|
-
const
|
|
17624
|
+
const baseEntries = collectUserConfigEntries(config, platforms);
|
|
17625
|
+
const entries = [
|
|
17626
|
+
...baseEntries,
|
|
17627
|
+
...collectNativeMcpAuthUserConfigEntries(config, platforms, baseEntries)
|
|
17628
|
+
];
|
|
16880
17629
|
return entries.map((field) => {
|
|
16881
17630
|
const envVar = field.envVar ?? defaultUserConfigEnvVar(field.key);
|
|
16882
17631
|
const envValue = process.env[envVar];
|
|
@@ -17330,7 +18079,10 @@ function patchInstalledMcpConfig(pluginDir, platform, config, entries) {
|
|
|
17330
18079
|
type: server.transport === "sse" ? "sse" : "http",
|
|
17331
18080
|
url: server.url
|
|
17332
18081
|
};
|
|
17333
|
-
|
|
18082
|
+
const nativeHeaders = getNativeJsonHeadersOverride(config, platform, name);
|
|
18083
|
+
if (!usesPlatformManagedAuth && nativeHeaders) {
|
|
18084
|
+
entry.headers = materializeEnvRecord(nativeHeaders, env);
|
|
18085
|
+
} else if (!usesPlatformManagedAuth && server.auth?.type === "bearer" && server.auth.envVar && env[server.auth.envVar]) {
|
|
17334
18086
|
entry.headers = {
|
|
17335
18087
|
Authorization: `Bearer ${env[server.auth.envVar]}`
|
|
17336
18088
|
};
|
|
@@ -17360,7 +18112,32 @@ function patchInstalledMcpConfig(pluginDir, platform, config, entries) {
|
|
|
17360
18112
|
const entry = {
|
|
17361
18113
|
url: server.url
|
|
17362
18114
|
};
|
|
17363
|
-
|
|
18115
|
+
const nativeOverride = getNativeCodexMcpEntryOverride(config, name);
|
|
18116
|
+
if (nativeOverride) {
|
|
18117
|
+
if (typeof nativeOverride.bearer_token_env_var === "string") {
|
|
18118
|
+
const envVar = nativeOverride.bearer_token_env_var;
|
|
18119
|
+
if (env[envVar]) {
|
|
18120
|
+
entry.bearer_token_env_var = envVar;
|
|
18121
|
+
}
|
|
18122
|
+
}
|
|
18123
|
+
if (nativeOverride.auth) {
|
|
18124
|
+
entry.auth = nativeOverride.auth;
|
|
18125
|
+
}
|
|
18126
|
+
if (nativeOverride.env_http_headers && typeof nativeOverride.env_http_headers === "object") {
|
|
18127
|
+
entry.http_headers = Object.fromEntries(
|
|
18128
|
+
Object.entries(nativeOverride.env_http_headers).filter(([, value]) => typeof value === "string").map(([headerName, envVar]) => [
|
|
18129
|
+
headerName,
|
|
18130
|
+
env[envVar] ?? `\${${envVar}}`
|
|
18131
|
+
])
|
|
18132
|
+
);
|
|
18133
|
+
}
|
|
18134
|
+
if (nativeOverride.http_headers && typeof nativeOverride.http_headers === "object") {
|
|
18135
|
+
entry.http_headers = materializeEnvRecord(
|
|
18136
|
+
nativeOverride.http_headers,
|
|
18137
|
+
env
|
|
18138
|
+
);
|
|
18139
|
+
}
|
|
18140
|
+
} else if (server.auth?.type === "bearer" && server.auth.envVar && env[server.auth.envVar]) {
|
|
17364
18141
|
entry.http_headers = {
|
|
17365
18142
|
Authorization: `Bearer ${env[server.auth.envVar]}`
|
|
17366
18143
|
};
|
|
@@ -18801,6 +19578,166 @@ function checkInstalledBundleIntegrity(checks, rootDir, layout) {
|
|
|
18801
19578
|
path: layout.manifestPath
|
|
18802
19579
|
});
|
|
18803
19580
|
}
|
|
19581
|
+
function parseInstalledPermissionRules(scriptSource) {
|
|
19582
|
+
const match = scriptSource.match(/const RULES = (\[[\s\S]*?\]);\nconst ACTION_PRIORITY/);
|
|
19583
|
+
if (!match?.[1]) {
|
|
19584
|
+
throw new Error("Could not locate embedded RULES payload in hooks/pluxx-permissions.mjs.");
|
|
19585
|
+
}
|
|
19586
|
+
const parsed = JSON.parse(match[1]);
|
|
19587
|
+
if (!Array.isArray(parsed)) {
|
|
19588
|
+
throw new Error("Embedded RULES payload is not an array.");
|
|
19589
|
+
}
|
|
19590
|
+
return parsed;
|
|
19591
|
+
}
|
|
19592
|
+
function materializePermissionPattern(pattern) {
|
|
19593
|
+
return pattern.replace(/\*\*/g, "probe/path").replace(/\*/g, "probe");
|
|
19594
|
+
}
|
|
19595
|
+
function buildInstalledPermissionProbe(platform, rule) {
|
|
19596
|
+
const value = materializePermissionPattern(rule.pattern);
|
|
19597
|
+
if (rule.kind === "Bash") {
|
|
19598
|
+
return {
|
|
19599
|
+
action: rule.action,
|
|
19600
|
+
raw: rule.raw,
|
|
19601
|
+
mode: platform === "cursor" ? "cursor-shell" : "claude-pretool",
|
|
19602
|
+
event: platform === "cursor" ? { command: value } : { tool_name: "Bash", tool_input: { command: value } }
|
|
19603
|
+
};
|
|
19604
|
+
}
|
|
19605
|
+
if (rule.kind === "Read") {
|
|
19606
|
+
return {
|
|
19607
|
+
action: rule.action,
|
|
19608
|
+
raw: rule.raw,
|
|
19609
|
+
mode: platform === "cursor" ? "cursor-read" : "claude-pretool",
|
|
19610
|
+
event: platform === "cursor" ? { file_path: value } : { tool_name: "Read", tool_input: { file_path: value } }
|
|
19611
|
+
};
|
|
19612
|
+
}
|
|
19613
|
+
if (rule.kind === "Edit") {
|
|
19614
|
+
return {
|
|
19615
|
+
action: rule.action,
|
|
19616
|
+
raw: rule.raw,
|
|
19617
|
+
mode: platform === "cursor" ? "cursor" : "claude-pretool",
|
|
19618
|
+
event: { tool_name: "Edit", tool_input: { file_path: value } }
|
|
19619
|
+
};
|
|
19620
|
+
}
|
|
19621
|
+
if (rule.kind === "Skill") {
|
|
19622
|
+
return {
|
|
19623
|
+
action: rule.action,
|
|
19624
|
+
raw: rule.raw,
|
|
19625
|
+
mode: platform === "cursor" ? "cursor" : "claude-pretool",
|
|
19626
|
+
event: { tool_name: "Skill", tool_input: { name: value } }
|
|
19627
|
+
};
|
|
19628
|
+
}
|
|
19629
|
+
if (rule.kind === "MCP") {
|
|
19630
|
+
const canonical = value.includes(".") ? value : `${value}.probe`;
|
|
19631
|
+
const dotIndex = canonical.indexOf(".");
|
|
19632
|
+
const server = canonical.slice(0, dotIndex).trim();
|
|
19633
|
+
const tool = canonical.slice(dotIndex + 1).trim();
|
|
19634
|
+
if (!server || !tool) return null;
|
|
19635
|
+
return {
|
|
19636
|
+
action: rule.action,
|
|
19637
|
+
raw: rule.raw,
|
|
19638
|
+
mode: platform === "cursor" ? "cursor-mcp" : "claude-pretool",
|
|
19639
|
+
event: platform === "cursor" ? { tool_name: `mcp__${server}__${tool.replace(/\./g, "__")}` } : { tool_name: `mcp__${server}__${tool.replace(/\./g, "__")}` }
|
|
19640
|
+
};
|
|
19641
|
+
}
|
|
19642
|
+
return null;
|
|
19643
|
+
}
|
|
19644
|
+
function extractPermissionDecision(platform, payload) {
|
|
19645
|
+
if (platform === "claude-code") {
|
|
19646
|
+
const hookSpecificOutput = payload.hookSpecificOutput;
|
|
19647
|
+
if (!hookSpecificOutput || typeof hookSpecificOutput !== "object") return void 0;
|
|
19648
|
+
const decision2 = hookSpecificOutput.permissionDecision;
|
|
19649
|
+
return typeof decision2 === "string" ? decision2 : void 0;
|
|
19650
|
+
}
|
|
19651
|
+
const decision = payload.permission;
|
|
19652
|
+
return typeof decision === "string" ? decision : void 0;
|
|
19653
|
+
}
|
|
19654
|
+
function smokeCheckInstalledPermissionHook(rootDir, layout) {
|
|
19655
|
+
if (layout.platform !== "claude-code" && layout.platform !== "cursor") {
|
|
19656
|
+
return null;
|
|
19657
|
+
}
|
|
19658
|
+
const scriptPath = resolve19(rootDir, "hooks/pluxx-permissions.mjs");
|
|
19659
|
+
if (!existsSync23(scriptPath)) {
|
|
19660
|
+
return null;
|
|
19661
|
+
}
|
|
19662
|
+
const rules = parseInstalledPermissionRules(readFileSync13(scriptPath, "utf-8"));
|
|
19663
|
+
const selected = /* @__PURE__ */ new Map();
|
|
19664
|
+
for (const rule of rules) {
|
|
19665
|
+
if (selected.has(rule.action)) continue;
|
|
19666
|
+
const probe = buildInstalledPermissionProbe(layout.platform, rule);
|
|
19667
|
+
if (!probe) continue;
|
|
19668
|
+
selected.set(rule.action, probe);
|
|
19669
|
+
}
|
|
19670
|
+
if (selected.size === 0) {
|
|
19671
|
+
return {
|
|
19672
|
+
ok: false,
|
|
19673
|
+
detail: "Permission hook script is present, but no supported canonical probe cases could be derived from its embedded rules."
|
|
19674
|
+
};
|
|
19675
|
+
}
|
|
19676
|
+
const failures = [];
|
|
19677
|
+
const successes = [];
|
|
19678
|
+
for (const probe of selected.values()) {
|
|
19679
|
+
const result = spawnSync2("node", [scriptPath, probe.mode], {
|
|
19680
|
+
cwd: rootDir,
|
|
19681
|
+
input: JSON.stringify(probe.event),
|
|
19682
|
+
encoding: "utf-8",
|
|
19683
|
+
env: process.env
|
|
19684
|
+
});
|
|
19685
|
+
if (result.status !== 0) {
|
|
19686
|
+
failures.push(`${probe.raw} exited ${result.status ?? "null"}${result.stderr ? `: ${result.stderr.trim()}` : ""}`);
|
|
19687
|
+
continue;
|
|
19688
|
+
}
|
|
19689
|
+
let payload;
|
|
19690
|
+
try {
|
|
19691
|
+
payload = JSON.parse(result.stdout || "{}");
|
|
19692
|
+
} catch (error) {
|
|
19693
|
+
failures.push(`${probe.raw} returned non-JSON output: ${error instanceof Error ? error.message : String(error)}`);
|
|
19694
|
+
continue;
|
|
19695
|
+
}
|
|
19696
|
+
const decision = extractPermissionDecision(layout.platform, payload);
|
|
19697
|
+
if (decision !== probe.action) {
|
|
19698
|
+
failures.push(`${probe.raw} returned ${decision ?? "no decision"} instead of ${probe.action}`);
|
|
19699
|
+
continue;
|
|
19700
|
+
}
|
|
19701
|
+
successes.push(`${probe.raw} -> ${decision}`);
|
|
19702
|
+
}
|
|
19703
|
+
return failures.length > 0 ? { ok: false, detail: failures.join("; ") } : { ok: true, detail: `Validated ${successes.join(", ")}` };
|
|
19704
|
+
}
|
|
19705
|
+
function checkInstalledPermissionHook(checks, rootDir, layout) {
|
|
19706
|
+
const permissionScriptPath = "hooks/pluxx-permissions.mjs";
|
|
19707
|
+
let scriptResult;
|
|
19708
|
+
try {
|
|
19709
|
+
scriptResult = smokeCheckInstalledPermissionHook(rootDir, layout);
|
|
19710
|
+
} catch (error) {
|
|
19711
|
+
addCheck2(checks, {
|
|
19712
|
+
level: "error",
|
|
19713
|
+
code: "consumer-permission-hook-invalid",
|
|
19714
|
+
title: "Installed bundled permission hook behavior is broken",
|
|
19715
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
19716
|
+
fix: "Rebuild or reinstall the plugin so the bundled permission hook script and hook wiring stay aligned with the declared permission rules.",
|
|
19717
|
+
path: permissionScriptPath
|
|
19718
|
+
});
|
|
19719
|
+
return;
|
|
19720
|
+
}
|
|
19721
|
+
if (!scriptResult) {
|
|
19722
|
+
addCheck2(checks, {
|
|
19723
|
+
level: "info",
|
|
19724
|
+
code: "consumer-permission-hook-not-applicable",
|
|
19725
|
+
title: "No bundled permission hook smoke check for this install",
|
|
19726
|
+
detail: layout.platform === "claude-code" || layout.platform === "cursor" ? "This installed bundle does not include hooks/pluxx-permissions.mjs." : "This platform does not currently use a bundled pluxx-permissions.mjs hook script for permission enforcement.",
|
|
19727
|
+
fix: "No action needed unless you expected bundled permission-hook enforcement in this installed bundle.",
|
|
19728
|
+
path: permissionScriptPath
|
|
19729
|
+
});
|
|
19730
|
+
return;
|
|
19731
|
+
}
|
|
19732
|
+
addCheck2(checks, {
|
|
19733
|
+
level: scriptResult.ok ? "success" : "error",
|
|
19734
|
+
code: scriptResult.ok ? "consumer-permission-hook-valid" : "consumer-permission-hook-invalid",
|
|
19735
|
+
title: scriptResult.ok ? "Installed bundled permission hook returns expected decisions" : "Installed bundled permission hook behavior is broken",
|
|
19736
|
+
detail: scriptResult.detail,
|
|
19737
|
+
fix: scriptResult.ok ? "No action needed." : "Rebuild or reinstall the plugin so the bundled permission hook script and hook wiring stay aligned with the declared permission rules.",
|
|
19738
|
+
path: permissionScriptPath
|
|
19739
|
+
});
|
|
19740
|
+
}
|
|
18804
19741
|
function findMissingInstalledStdioRuntimePaths(rootDir, stdioEntries) {
|
|
18805
19742
|
const missing = /* @__PURE__ */ new Set();
|
|
18806
19743
|
for (const server of stdioEntries) {
|
|
@@ -19068,6 +20005,7 @@ async function doctorConsumer(rootDir = process.cwd()) {
|
|
|
19068
20005
|
});
|
|
19069
20006
|
checkConsumerManifest(checks, rootDir, layout);
|
|
19070
20007
|
checkInstalledBundleIntegrity(checks, rootDir, layout);
|
|
20008
|
+
checkInstalledPermissionHook(checks, rootDir, layout);
|
|
19071
20009
|
checkInstalledUserConfig(checks, rootDir);
|
|
19072
20010
|
checkInstalledEnvValidation(checks, rootDir);
|
|
19073
20011
|
checkInstalledRuntimeScriptRoles(checks, rootDir);
|
|
@@ -19262,7 +20200,7 @@ async function runBuild(rootDir, targets) {
|
|
|
19262
20200
|
}
|
|
19263
20201
|
|
|
19264
20202
|
// src/cli/migrate.ts
|
|
19265
|
-
import { basename as basename7, relative as relative12, resolve as resolve21 } from "path";
|
|
20203
|
+
import { basename as basename7, dirname as dirname8, relative as relative12, resolve as resolve21 } from "path";
|
|
19266
20204
|
import { existsSync as existsSync24, readdirSync as readdirSync11, mkdirSync as mkdirSync5, cpSync as cpSync4, readFileSync as readFileSync14, writeFileSync as writeFileSync5 } from "fs";
|
|
19267
20205
|
function detectPlatform(pluginDir) {
|
|
19268
20206
|
const checks = [
|
|
@@ -19291,11 +20229,19 @@ function detectPlatform(pluginDir) {
|
|
|
19291
20229
|
} catch {
|
|
19292
20230
|
}
|
|
19293
20231
|
}
|
|
20232
|
+
if (existsSync24(resolve21(pluginDir, "CLAUDE.md"))) {
|
|
20233
|
+
return { platform: "claude-code" };
|
|
20234
|
+
}
|
|
19294
20235
|
return null;
|
|
19295
20236
|
}
|
|
19296
20237
|
function parseManifest(detection) {
|
|
20238
|
+
if (!detection.manifestPath) {
|
|
20239
|
+
return {};
|
|
20240
|
+
}
|
|
19297
20241
|
const raw = JSON.parse(readFileSync14(detection.manifestPath, "utf-8"));
|
|
19298
20242
|
const result = {};
|
|
20243
|
+
const brand = {};
|
|
20244
|
+
const platforms = {};
|
|
19299
20245
|
if (raw.name) result.name = raw.name;
|
|
19300
20246
|
if (raw.version) result.version = raw.version;
|
|
19301
20247
|
if (raw.description) result.description = raw.description;
|
|
@@ -19315,169 +20261,730 @@ function parseManifest(detection) {
|
|
|
19315
20261
|
};
|
|
19316
20262
|
}
|
|
19317
20263
|
}
|
|
20264
|
+
const homepage = typeof raw.homepage === "string" ? raw.homepage : void 0;
|
|
20265
|
+
if (homepage) brand.websiteURL = homepage;
|
|
20266
|
+
if (typeof raw.icon === "string") brand.icon = raw.icon;
|
|
20267
|
+
if (typeof raw.logo === "string") {
|
|
20268
|
+
brand.logo = raw.logo;
|
|
20269
|
+
if (!brand.icon) brand.icon = raw.logo;
|
|
20270
|
+
}
|
|
20271
|
+
const codexInterface = asRecord4(raw.interface);
|
|
20272
|
+
if (codexInterface) {
|
|
20273
|
+
if (typeof codexInterface.displayName === "string") brand.displayName = codexInterface.displayName;
|
|
20274
|
+
if (typeof codexInterface.shortDescription === "string") brand.shortDescription = codexInterface.shortDescription;
|
|
20275
|
+
if (typeof codexInterface.longDescription === "string") brand.longDescription = codexInterface.longDescription;
|
|
20276
|
+
if (typeof codexInterface.category === "string") brand.category = codexInterface.category;
|
|
20277
|
+
if (typeof codexInterface.brandColor === "string") brand.color = codexInterface.brandColor;
|
|
20278
|
+
if (typeof codexInterface.composerIcon === "string") brand.icon = codexInterface.composerIcon;
|
|
20279
|
+
if (typeof codexInterface.logo === "string") brand.logo = codexInterface.logo;
|
|
20280
|
+
if (Array.isArray(codexInterface.defaultPrompt)) {
|
|
20281
|
+
const prompts = codexInterface.defaultPrompt.filter((value) => typeof value === "string");
|
|
20282
|
+
if (prompts.length > 0) brand.defaultPrompts = prompts;
|
|
20283
|
+
}
|
|
20284
|
+
if (typeof codexInterface.websiteURL === "string") brand.websiteURL = codexInterface.websiteURL;
|
|
20285
|
+
if (typeof codexInterface.privacyPolicyURL === "string") brand.privacyPolicyURL = codexInterface.privacyPolicyURL;
|
|
20286
|
+
if (typeof codexInterface.termsOfServiceURL === "string") brand.termsOfServiceURL = codexInterface.termsOfServiceURL;
|
|
20287
|
+
if (Array.isArray(codexInterface.screenshots)) {
|
|
20288
|
+
const screenshots = codexInterface.screenshots.filter((value) => typeof value === "string");
|
|
20289
|
+
if (screenshots.length > 0) brand.screenshots = screenshots;
|
|
20290
|
+
}
|
|
20291
|
+
const remainingInterface = Object.fromEntries(
|
|
20292
|
+
Object.entries(codexInterface).filter(([key]) => !(/* @__PURE__ */ new Set([
|
|
20293
|
+
"displayName",
|
|
20294
|
+
"shortDescription",
|
|
20295
|
+
"longDescription",
|
|
20296
|
+
"category",
|
|
20297
|
+
"brandColor",
|
|
20298
|
+
"composerIcon",
|
|
20299
|
+
"logo",
|
|
20300
|
+
"defaultPrompt",
|
|
20301
|
+
"websiteURL",
|
|
20302
|
+
"privacyPolicyURL",
|
|
20303
|
+
"termsOfServiceURL",
|
|
20304
|
+
"screenshots"
|
|
20305
|
+
])).has(key))
|
|
20306
|
+
);
|
|
20307
|
+
if (Object.keys(remainingInterface).length > 0) {
|
|
20308
|
+
platforms.codex = {
|
|
20309
|
+
...platforms.codex ?? {},
|
|
20310
|
+
interface: remainingInterface
|
|
20311
|
+
};
|
|
20312
|
+
}
|
|
20313
|
+
}
|
|
20314
|
+
if (detection.platform === "cursor") {
|
|
20315
|
+
const marketplacePath = resolve21(dirname8(detection.manifestPath), "marketplace.json");
|
|
20316
|
+
if (existsSync24(marketplacePath)) {
|
|
20317
|
+
try {
|
|
20318
|
+
const marketplace = JSON.parse(readFileSync14(marketplacePath, "utf-8"));
|
|
20319
|
+
if (marketplace && typeof marketplace === "object") {
|
|
20320
|
+
platforms.cursor = {
|
|
20321
|
+
...platforms.cursor ?? {},
|
|
20322
|
+
marketplace
|
|
20323
|
+
};
|
|
20324
|
+
}
|
|
20325
|
+
} catch {
|
|
20326
|
+
}
|
|
20327
|
+
}
|
|
20328
|
+
}
|
|
20329
|
+
if (detection.platform === "codex") {
|
|
20330
|
+
const appPath = resolve21(dirname8(dirname8(detection.manifestPath)), ".app.json");
|
|
20331
|
+
if (existsSync24(appPath)) {
|
|
20332
|
+
try {
|
|
20333
|
+
const app = JSON.parse(readFileSync14(appPath, "utf-8"));
|
|
20334
|
+
if (app && typeof app === "object" && !Array.isArray(app)) {
|
|
20335
|
+
platforms.codex = {
|
|
20336
|
+
...platforms.codex ?? {},
|
|
20337
|
+
app
|
|
20338
|
+
};
|
|
20339
|
+
}
|
|
20340
|
+
} catch {
|
|
20341
|
+
}
|
|
20342
|
+
}
|
|
20343
|
+
}
|
|
20344
|
+
if (detection.platform === "opencode") {
|
|
20345
|
+
const pluginConfig = asRecord4(raw.plugin);
|
|
20346
|
+
if (pluginConfig) {
|
|
20347
|
+
platforms.opencode = {
|
|
20348
|
+
...platforms.opencode ?? {},
|
|
20349
|
+
plugin: pluginConfig
|
|
20350
|
+
};
|
|
20351
|
+
}
|
|
20352
|
+
}
|
|
20353
|
+
if (Object.keys(brand).length > 0) result.brand = brand;
|
|
20354
|
+
if (Object.keys(platforms).length > 0) result.platforms = platforms;
|
|
19318
20355
|
return result;
|
|
19319
20356
|
}
|
|
19320
20357
|
function parseMcp(pluginDir, detection) {
|
|
19321
|
-
const
|
|
19322
|
-
resolve21(pluginDir, ".mcp.json"),
|
|
19323
|
-
resolve21(pluginDir, "mcp.json")
|
|
20358
|
+
const mcpCandidates = [
|
|
20359
|
+
{ path: resolve21(pluginDir, ".mcp.json"), parser: "json" },
|
|
20360
|
+
{ path: resolve21(pluginDir, "mcp.json"), parser: "json" }
|
|
19324
20361
|
];
|
|
19325
|
-
|
|
19326
|
-
|
|
19327
|
-
|
|
19328
|
-
|
|
20362
|
+
if (detection.platform === "codex") {
|
|
20363
|
+
mcpCandidates.push({ path: resolve21(pluginDir, ".codex/config.toml"), parser: "toml" });
|
|
20364
|
+
}
|
|
20365
|
+
if (detection.platform === "opencode") {
|
|
20366
|
+
mcpCandidates.push({ path: resolve21(pluginDir, "opencode.json"), parser: "json" });
|
|
20367
|
+
mcpCandidates.push({ path: resolve21(pluginDir, ".opencode.json"), parser: "json" });
|
|
20368
|
+
}
|
|
20369
|
+
if (detection.manifestPath) {
|
|
20370
|
+
try {
|
|
20371
|
+
const manifest = JSON.parse(readFileSync14(detection.manifestPath, "utf-8"));
|
|
20372
|
+
if (manifest.mcpServers && typeof manifest.mcpServers === "string") {
|
|
20373
|
+
mcpCandidates.unshift({ path: resolve21(pluginDir, manifest.mcpServers), parser: "json" });
|
|
20374
|
+
}
|
|
20375
|
+
} catch {
|
|
20376
|
+
}
|
|
20377
|
+
}
|
|
20378
|
+
const mergedServers = {};
|
|
20379
|
+
const mergedWarnings = /* @__PURE__ */ new Set();
|
|
20380
|
+
let runtimeAuthMode = "inline";
|
|
20381
|
+
let platformOverrides;
|
|
20382
|
+
for (const candidate of mcpCandidates) {
|
|
20383
|
+
if (!existsSync24(candidate.path)) continue;
|
|
20384
|
+
const parsed = candidate.parser === "toml" ? parseTomlMcpFile(candidate.path, detection.platform) : parseJsonMcpFile(candidate.path, detection.platform);
|
|
20385
|
+
if (Object.keys(parsed.servers).length === 0) continue;
|
|
20386
|
+
for (const [serverName, server] of Object.entries(parsed.servers)) {
|
|
20387
|
+
const previous = mergedServers[serverName];
|
|
20388
|
+
const combinedWarnings = [.../* @__PURE__ */ new Set([...previous?.warnings ?? [], ...server.warnings ?? []])];
|
|
20389
|
+
mergedServers[serverName] = {
|
|
20390
|
+
...previous ?? {},
|
|
20391
|
+
...server,
|
|
20392
|
+
...combinedWarnings.length > 0 ? { warnings: combinedWarnings } : {}
|
|
20393
|
+
};
|
|
20394
|
+
}
|
|
20395
|
+
if (parsed.runtimeAuthMode === "platform") {
|
|
20396
|
+
runtimeAuthMode = "platform";
|
|
20397
|
+
}
|
|
20398
|
+
for (const warning of parsed.warnings) {
|
|
20399
|
+
mergedWarnings.add(warning);
|
|
20400
|
+
}
|
|
20401
|
+
platformOverrides = mergePlatformOverrideMaps(platformOverrides, parsed.platformOverrides);
|
|
20402
|
+
}
|
|
20403
|
+
if (Object.keys(mergedServers).length > 0) {
|
|
20404
|
+
return {
|
|
20405
|
+
servers: mergedServers,
|
|
20406
|
+
runtimeAuthMode,
|
|
20407
|
+
warnings: [...mergedWarnings],
|
|
20408
|
+
...platformOverrides ? { platformOverrides } : {}
|
|
20409
|
+
};
|
|
20410
|
+
}
|
|
20411
|
+
return {
|
|
20412
|
+
servers: {},
|
|
20413
|
+
runtimeAuthMode: "inline",
|
|
20414
|
+
warnings: []
|
|
20415
|
+
};
|
|
20416
|
+
}
|
|
20417
|
+
var HOOK_EVENT_MAP = {
|
|
20418
|
+
SessionStart: "sessionStart",
|
|
20419
|
+
SessionEnd: "sessionEnd",
|
|
20420
|
+
PreToolUse: "preToolUse",
|
|
20421
|
+
PostToolUse: "postToolUse",
|
|
20422
|
+
UserPromptSubmit: "beforeSubmitPrompt",
|
|
20423
|
+
BeforeShellExecution: "beforeShellExecution",
|
|
20424
|
+
AfterShellExecution: "afterShellExecution",
|
|
20425
|
+
BeforeMCPExecution: "beforeMCPExecution",
|
|
20426
|
+
AfterMCPExecution: "afterMCPExecution",
|
|
20427
|
+
AfterFileEdit: "afterFileEdit",
|
|
20428
|
+
BeforeReadFile: "beforeReadFile",
|
|
20429
|
+
BeforeSubmitPrompt: "beforeSubmitPrompt",
|
|
20430
|
+
Stop: "stop",
|
|
20431
|
+
// Also handle already-normalized names
|
|
20432
|
+
sessionStart: "sessionStart",
|
|
20433
|
+
sessionEnd: "sessionEnd",
|
|
20434
|
+
preToolUse: "preToolUse",
|
|
20435
|
+
postToolUse: "postToolUse"
|
|
20436
|
+
};
|
|
20437
|
+
function parseHooks(pluginDir, detection) {
|
|
20438
|
+
const hooksPaths = [
|
|
20439
|
+
resolve21(pluginDir, ".codex", "hooks.json"),
|
|
20440
|
+
resolve21(pluginDir, "hooks.json"),
|
|
20441
|
+
resolve21(pluginDir, "hooks", "hooks.json")
|
|
20442
|
+
];
|
|
20443
|
+
if (detection.manifestPath) {
|
|
20444
|
+
try {
|
|
20445
|
+
const manifest = JSON.parse(readFileSync14(detection.manifestPath, "utf-8"));
|
|
20446
|
+
if (manifest.hooks && typeof manifest.hooks === "string") {
|
|
20447
|
+
hooksPaths.unshift(resolve21(pluginDir, manifest.hooks));
|
|
20448
|
+
}
|
|
20449
|
+
} catch {
|
|
19329
20450
|
}
|
|
19330
|
-
} catch {
|
|
19331
20451
|
}
|
|
19332
|
-
for (const
|
|
19333
|
-
if (!existsSync24(
|
|
20452
|
+
for (const hooksPath of hooksPaths) {
|
|
20453
|
+
if (!existsSync24(hooksPath)) continue;
|
|
19334
20454
|
try {
|
|
19335
|
-
const raw = JSON.parse(readFileSync14(
|
|
19336
|
-
const
|
|
19337
|
-
if (!
|
|
20455
|
+
const raw = JSON.parse(readFileSync14(hooksPath, "utf-8"));
|
|
20456
|
+
const hooksObj = raw.hooks ?? raw;
|
|
20457
|
+
if (!hooksObj || typeof hooksObj !== "object") continue;
|
|
19338
20458
|
const result = {};
|
|
19339
|
-
for (const [
|
|
19340
|
-
const
|
|
19341
|
-
const
|
|
19342
|
-
if (
|
|
19343
|
-
|
|
19344
|
-
entry.
|
|
19345
|
-
|
|
19346
|
-
|
|
19347
|
-
|
|
19348
|
-
|
|
19349
|
-
|
|
19350
|
-
|
|
19351
|
-
|
|
19352
|
-
|
|
19353
|
-
if (cfg.headers && typeof cfg.headers === "object") {
|
|
19354
|
-
const headers = cfg.headers;
|
|
19355
|
-
const authHeader = headers["Authorization"] ?? headers["authorization"];
|
|
19356
|
-
if (authHeader) {
|
|
19357
|
-
const envMatch = authHeader.match(/\$\{(\w+)\}/);
|
|
19358
|
-
if (envMatch) {
|
|
19359
|
-
entry.auth = {
|
|
19360
|
-
type: "bearer",
|
|
19361
|
-
envVar: envMatch[1],
|
|
19362
|
-
headerTemplate: authHeader.replace(/\$\{\w+\}/, "${value}")
|
|
19363
|
-
};
|
|
20459
|
+
for (const [event, entries] of Object.entries(hooksObj)) {
|
|
20460
|
+
const normalizedEvent = HOOK_EVENT_MAP[event] ?? event;
|
|
20461
|
+
const hookEntries = [];
|
|
20462
|
+
if (!Array.isArray(entries)) continue;
|
|
20463
|
+
for (const entry of entries) {
|
|
20464
|
+
if (entry.hooks && Array.isArray(entry.hooks)) {
|
|
20465
|
+
for (const hook of entry.hooks) {
|
|
20466
|
+
const parsedHook = parseMigratedHookEntry(hook, entry.matcher);
|
|
20467
|
+
if (parsedHook) hookEntries.push(parsedHook);
|
|
20468
|
+
}
|
|
20469
|
+
} else {
|
|
20470
|
+
const parsedHook = parseMigratedHookEntry(entry);
|
|
20471
|
+
if (parsedHook) {
|
|
20472
|
+
hookEntries.push(parsedHook);
|
|
19364
20473
|
}
|
|
19365
20474
|
}
|
|
19366
20475
|
}
|
|
19367
|
-
if (
|
|
19368
|
-
|
|
19369
|
-
type: "bearer",
|
|
19370
|
-
envVar: cfg.bearer_token_env_var
|
|
19371
|
-
};
|
|
20476
|
+
if (hookEntries.length > 0) {
|
|
20477
|
+
result[normalizedEvent] = hookEntries;
|
|
19372
20478
|
}
|
|
19373
|
-
|
|
19374
|
-
|
|
19375
|
-
|
|
19376
|
-
|
|
19377
|
-
|
|
19378
|
-
|
|
19379
|
-
|
|
19380
|
-
|
|
19381
|
-
|
|
19382
|
-
|
|
19383
|
-
|
|
20479
|
+
}
|
|
20480
|
+
return result;
|
|
20481
|
+
} catch {
|
|
20482
|
+
continue;
|
|
20483
|
+
}
|
|
20484
|
+
}
|
|
20485
|
+
return {};
|
|
20486
|
+
}
|
|
20487
|
+
function parseMigratedHookEntry(raw, fallbackMatcher) {
|
|
20488
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
20489
|
+
const candidate = raw;
|
|
20490
|
+
const inferredType = candidate.type;
|
|
20491
|
+
const type = inferredType === "command" || inferredType === "http" || inferredType === "mcp_tool" || inferredType === "prompt" || inferredType === "agent" ? inferredType : typeof candidate.prompt === "string" ? "prompt" : typeof candidate.url === "string" ? "http" : typeof candidate.server === "string" || typeof candidate.tool === "string" ? "mcp_tool" : "command";
|
|
20492
|
+
const entry = { type };
|
|
20493
|
+
if (typeof candidate.command === "string") entry.command = candidate.command;
|
|
20494
|
+
if (typeof candidate.prompt === "string") entry.prompt = candidate.prompt;
|
|
20495
|
+
if (typeof candidate.model === "string") entry.model = candidate.model;
|
|
20496
|
+
if (typeof candidate.url === "string") entry.url = candidate.url;
|
|
20497
|
+
if (candidate.headers && typeof candidate.headers === "object" && !Array.isArray(candidate.headers)) {
|
|
20498
|
+
entry.headers = Object.fromEntries(
|
|
20499
|
+
Object.entries(candidate.headers).filter(([, value]) => typeof value === "string").map(([key, value]) => [key, value])
|
|
20500
|
+
);
|
|
20501
|
+
}
|
|
20502
|
+
if (Array.isArray(candidate.allowedEnvVars) && candidate.allowedEnvVars.every((value) => typeof value === "string")) {
|
|
20503
|
+
entry.allowedEnvVars = candidate.allowedEnvVars;
|
|
20504
|
+
}
|
|
20505
|
+
if (typeof candidate.server === "string") entry.server = candidate.server;
|
|
20506
|
+
if (typeof candidate.tool === "string") entry.tool = candidate.tool;
|
|
20507
|
+
if (candidate.input && typeof candidate.input === "object" && !Array.isArray(candidate.input)) {
|
|
20508
|
+
entry.input = candidate.input;
|
|
20509
|
+
}
|
|
20510
|
+
if (typeof candidate.if === "string") entry.if = candidate.if;
|
|
20511
|
+
if (typeof candidate.async === "boolean") entry.async = candidate.async;
|
|
20512
|
+
if (typeof candidate.asyncRewake === "boolean") entry.asyncRewake = candidate.asyncRewake;
|
|
20513
|
+
if (candidate.shell === "bash") entry.shell = "bash";
|
|
20514
|
+
if (typeof candidate.timeout === "number") entry.timeout = candidate.timeout;
|
|
20515
|
+
const matcher = candidate.matcher ?? fallbackMatcher;
|
|
20516
|
+
if (typeof matcher === "string") {
|
|
20517
|
+
entry.matcher = matcher;
|
|
20518
|
+
} else if (matcher && typeof matcher === "object" && !Array.isArray(matcher)) {
|
|
20519
|
+
entry.matcher = matcher;
|
|
20520
|
+
}
|
|
20521
|
+
if (typeof candidate.failClosed === "boolean") entry.failClosed = candidate.failClosed;
|
|
20522
|
+
if (typeof candidate.loop_limit === "number" || candidate.loop_limit === null) {
|
|
20523
|
+
entry.loop_limit = candidate.loop_limit;
|
|
20524
|
+
}
|
|
20525
|
+
if (type === "command" && !entry.command) return null;
|
|
20526
|
+
if (type === "http" && !entry.url) return null;
|
|
20527
|
+
if (type === "mcp_tool" && (!entry.server || !entry.tool)) return null;
|
|
20528
|
+
if ((type === "prompt" || type === "agent") && !entry.prompt) return null;
|
|
20529
|
+
return entry;
|
|
20530
|
+
}
|
|
20531
|
+
function findInstructions(pluginDir) {
|
|
20532
|
+
const candidates = [
|
|
20533
|
+
"CLAUDE.md",
|
|
20534
|
+
"AGENTS.md",
|
|
20535
|
+
"instructions.md",
|
|
20536
|
+
"INSTRUCTIONS.md",
|
|
20537
|
+
"README.md"
|
|
20538
|
+
];
|
|
20539
|
+
for (const candidate of candidates) {
|
|
20540
|
+
const filePath = resolve21(pluginDir, candidate);
|
|
20541
|
+
if (existsSync24(filePath)) {
|
|
20542
|
+
return `./${candidate}`;
|
|
20543
|
+
}
|
|
20544
|
+
}
|
|
20545
|
+
return void 0;
|
|
20546
|
+
}
|
|
20547
|
+
function parseCursorRules(pluginDir, detection) {
|
|
20548
|
+
if (detection.platform !== "cursor") return [];
|
|
20549
|
+
const manifest = detection.manifestPath ? tryReadJson(detection.manifestPath) : null;
|
|
20550
|
+
const configuredRulesDir = typeof manifest?.rules === "string" ? stripRelativePrefix(manifest.rules) : void 0;
|
|
20551
|
+
const candidates = [
|
|
20552
|
+
configuredRulesDir,
|
|
20553
|
+
"rules",
|
|
20554
|
+
".cursor/rules"
|
|
20555
|
+
].filter((value) => Boolean(value));
|
|
20556
|
+
for (const relativeDir of candidates) {
|
|
20557
|
+
const dirPath = resolve21(pluginDir, relativeDir);
|
|
20558
|
+
if (!existsSync24(dirPath)) continue;
|
|
20559
|
+
const entries = collectFiles(dirPath, (entry) => entry.endsWith(".mdc")).map((entry) => parseCursorRuleFile(pluginDir, entry)).filter((entry) => Boolean(entry));
|
|
20560
|
+
if (entries.length > 0) return entries;
|
|
20561
|
+
}
|
|
20562
|
+
return [];
|
|
20563
|
+
}
|
|
20564
|
+
function parseCursorRuleFile(pluginDir, filePath) {
|
|
20565
|
+
const content = readFileSync14(filePath, "utf-8");
|
|
20566
|
+
const { hasFrontmatter, frontmatterLines, body } = splitMarkdownFrontmatter5(content);
|
|
20567
|
+
if (!hasFrontmatter) return null;
|
|
20568
|
+
const description = parseTopLevelStringFrontmatter(frontmatterLines, "description") ?? titleCaseFromDirName(basename7(filePath, ".mdc"));
|
|
20569
|
+
const globs = parseTopLevelStringOrStringArrayFrontmatter(frontmatterLines, "globs");
|
|
20570
|
+
const alwaysApply = parseTopLevelBooleanFrontmatter(frontmatterLines, "alwaysApply");
|
|
20571
|
+
return {
|
|
20572
|
+
description,
|
|
20573
|
+
...globs !== void 0 ? { globs } : {},
|
|
20574
|
+
...alwaysApply !== void 0 ? { alwaysApply } : {},
|
|
20575
|
+
...body.trim() ? { content: body.trim() } : {},
|
|
20576
|
+
path: `./${relative12(pluginDir, filePath).replace(/\\/g, "/")}`
|
|
20577
|
+
};
|
|
20578
|
+
}
|
|
20579
|
+
function collectFiles(dir, predicate) {
|
|
20580
|
+
const results = [];
|
|
20581
|
+
const stack = [dir];
|
|
20582
|
+
while (stack.length > 0) {
|
|
20583
|
+
const current = stack.pop();
|
|
20584
|
+
for (const entry of readdirSync11(current, { withFileTypes: true })) {
|
|
20585
|
+
const fullPath = resolve21(current, entry.name);
|
|
20586
|
+
if (entry.isDirectory()) {
|
|
20587
|
+
stack.push(fullPath);
|
|
20588
|
+
continue;
|
|
20589
|
+
}
|
|
20590
|
+
if (entry.isFile() && predicate(entry.name)) {
|
|
20591
|
+
results.push(fullPath);
|
|
20592
|
+
}
|
|
20593
|
+
}
|
|
20594
|
+
}
|
|
20595
|
+
return results.sort((a, b) => a.localeCompare(b));
|
|
20596
|
+
}
|
|
20597
|
+
function findOpenCodeConfiguredInstructions(pluginDir) {
|
|
20598
|
+
const candidates = [resolve21(pluginDir, "opencode.json"), resolve21(pluginDir, ".opencode.json")];
|
|
20599
|
+
const discovered = /* @__PURE__ */ new Set();
|
|
20600
|
+
for (const path of candidates) {
|
|
20601
|
+
const raw = tryReadJson(path);
|
|
20602
|
+
if (!raw) continue;
|
|
20603
|
+
const instructions = raw.instructions;
|
|
20604
|
+
const values = typeof instructions === "string" ? [instructions] : Array.isArray(instructions) ? instructions.filter((value) => typeof value === "string") : [];
|
|
20605
|
+
for (const value of values) {
|
|
20606
|
+
if (value.startsWith("./") || value.startsWith("../")) {
|
|
20607
|
+
const normalized = `./${stripRelativePrefix(value)}`;
|
|
20608
|
+
if (existsSync24(resolve21(pluginDir, normalized))) {
|
|
20609
|
+
discovered.add(normalized);
|
|
20610
|
+
}
|
|
20611
|
+
}
|
|
20612
|
+
}
|
|
20613
|
+
}
|
|
20614
|
+
return [...discovered];
|
|
20615
|
+
}
|
|
20616
|
+
function findInstructionSources(pluginDir, detection) {
|
|
20617
|
+
const primary = findInstructions(pluginDir);
|
|
20618
|
+
const extraPaths = /* @__PURE__ */ new Set();
|
|
20619
|
+
const platformOverrides = {};
|
|
20620
|
+
if (detection.platform === "cursor") {
|
|
20621
|
+
const nestedAgents = collectFiles(pluginDir, (entry) => entry === "AGENTS.md").map((path) => `./${relative12(pluginDir, path).replace(/\\/g, "/")}`).filter((path) => path !== primary);
|
|
20622
|
+
if (nestedAgents.length > 0) {
|
|
20623
|
+
platformOverrides.cursor = {
|
|
20624
|
+
...platformOverrides.cursor ?? {},
|
|
20625
|
+
instructionSources: {
|
|
20626
|
+
...asRecord4(platformOverrides.cursor?.instructionSources) ?? {},
|
|
20627
|
+
nestedAgents
|
|
20628
|
+
}
|
|
20629
|
+
};
|
|
20630
|
+
for (const path of nestedAgents) extraPaths.add(path);
|
|
20631
|
+
}
|
|
20632
|
+
}
|
|
20633
|
+
if (detection.platform === "codex") {
|
|
20634
|
+
const overridePath = "./AGENTS.override.md";
|
|
20635
|
+
if (existsSync24(resolve21(pluginDir, stripRelativePrefix(overridePath)))) {
|
|
20636
|
+
platformOverrides.codex = {
|
|
20637
|
+
...platformOverrides.codex ?? {},
|
|
20638
|
+
instructionSources: {
|
|
20639
|
+
...asRecord4(platformOverrides.codex?.instructionSources) ?? {},
|
|
20640
|
+
override: overridePath
|
|
20641
|
+
}
|
|
20642
|
+
};
|
|
20643
|
+
if (overridePath !== primary) {
|
|
20644
|
+
extraPaths.add(overridePath);
|
|
20645
|
+
}
|
|
20646
|
+
}
|
|
20647
|
+
}
|
|
20648
|
+
if (detection.platform === "opencode") {
|
|
20649
|
+
const configured = findOpenCodeConfiguredInstructions(pluginDir);
|
|
20650
|
+
if (configured.length > 0) {
|
|
20651
|
+
platformOverrides.opencode = {
|
|
20652
|
+
...platformOverrides.opencode ?? {},
|
|
20653
|
+
instructionSources: {
|
|
20654
|
+
...asRecord4(platformOverrides.opencode?.instructionSources) ?? {},
|
|
20655
|
+
configured
|
|
20656
|
+
}
|
|
20657
|
+
};
|
|
20658
|
+
for (const path of configured) {
|
|
20659
|
+
if (path !== primary) extraPaths.add(path);
|
|
20660
|
+
}
|
|
20661
|
+
}
|
|
20662
|
+
}
|
|
20663
|
+
return {
|
|
20664
|
+
primary: primary ?? (detection.platform === "opencode" ? findOpenCodeConfiguredInstructions(pluginDir)[0] : void 0),
|
|
20665
|
+
extraPaths: [...extraPaths].sort(),
|
|
20666
|
+
...Object.keys(platformOverrides).length > 0 ? { platformOverrides } : {}
|
|
20667
|
+
};
|
|
20668
|
+
}
|
|
20669
|
+
function collectPackageEntryPaths(value, acc) {
|
|
20670
|
+
if (typeof value === "string") {
|
|
20671
|
+
if (value.startsWith("./") || value.startsWith("../")) {
|
|
20672
|
+
acc.add(`./${stripRelativePrefix(value)}`);
|
|
20673
|
+
}
|
|
20674
|
+
return;
|
|
20675
|
+
}
|
|
20676
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
20677
|
+
for (const nested of Object.values(value)) {
|
|
20678
|
+
collectPackageEntryPaths(nested, acc);
|
|
20679
|
+
}
|
|
20680
|
+
}
|
|
20681
|
+
function findOpenCodeDistributionSources(pluginDir, detection) {
|
|
20682
|
+
if (detection.platform !== "opencode" || !detection.manifestPath) {
|
|
20683
|
+
return { extraPaths: [] };
|
|
20684
|
+
}
|
|
20685
|
+
const pkg = tryReadJson(detection.manifestPath);
|
|
20686
|
+
if (!pkg) return { extraPaths: [] };
|
|
20687
|
+
const entrypoints = {};
|
|
20688
|
+
const extraPaths = /* @__PURE__ */ new Set();
|
|
20689
|
+
for (const key of ["main", "module", "types"]) {
|
|
20690
|
+
const value = pkg[key];
|
|
20691
|
+
if (typeof value === "string") {
|
|
20692
|
+
entrypoints[key] = value;
|
|
20693
|
+
collectPackageEntryPaths(value, extraPaths);
|
|
20694
|
+
}
|
|
20695
|
+
}
|
|
20696
|
+
if (pkg.exports !== void 0) {
|
|
20697
|
+
entrypoints.exports = pkg.exports;
|
|
20698
|
+
collectPackageEntryPaths(pkg.exports, extraPaths);
|
|
20699
|
+
}
|
|
20700
|
+
const fallbackEntries = ["./index.ts", "./index.js", "./src/index.ts", "./src/index.js"];
|
|
20701
|
+
if (Object.keys(entrypoints).length === 0) {
|
|
20702
|
+
for (const candidate of fallbackEntries) {
|
|
20703
|
+
if (existsSync24(resolve21(pluginDir, stripRelativePrefix(candidate)))) {
|
|
20704
|
+
entrypoints.entry = candidate;
|
|
20705
|
+
extraPaths.add(candidate);
|
|
20706
|
+
break;
|
|
20707
|
+
}
|
|
20708
|
+
}
|
|
20709
|
+
}
|
|
20710
|
+
const presentPaths = [...extraPaths].filter((entry) => existsSync24(resolve21(pluginDir, stripRelativePrefix(entry))));
|
|
20711
|
+
if (Object.keys(entrypoints).length === 0) {
|
|
20712
|
+
return { extraPaths: presentPaths };
|
|
20713
|
+
}
|
|
20714
|
+
return {
|
|
20715
|
+
extraPaths: presentPaths,
|
|
20716
|
+
platformOverrides: {
|
|
20717
|
+
opencode: {
|
|
20718
|
+
entrypoints
|
|
20719
|
+
}
|
|
20720
|
+
}
|
|
20721
|
+
};
|
|
20722
|
+
}
|
|
20723
|
+
function tryReadJson(path) {
|
|
20724
|
+
try {
|
|
20725
|
+
const parsed = JSON.parse(readFileSync14(path, "utf-8"));
|
|
20726
|
+
return asRecord4(parsed) ?? null;
|
|
20727
|
+
} catch {
|
|
20728
|
+
return null;
|
|
20729
|
+
}
|
|
20730
|
+
}
|
|
20731
|
+
function parseJsonMcpFile(path, platform) {
|
|
20732
|
+
const raw = tryReadJson(path);
|
|
20733
|
+
if (!raw) {
|
|
20734
|
+
return { servers: {}, runtimeAuthMode: "inline", warnings: [] };
|
|
20735
|
+
}
|
|
20736
|
+
const servers = extractJsonMcpServers(raw, platform, path);
|
|
20737
|
+
if (!servers) {
|
|
20738
|
+
return { servers: {}, runtimeAuthMode: "inline", warnings: [] };
|
|
20739
|
+
}
|
|
20740
|
+
return parseMcpServerRecords(servers, platform);
|
|
20741
|
+
}
|
|
20742
|
+
function extractJsonMcpServers(raw, platform, path) {
|
|
20743
|
+
if (platform === "opencode") {
|
|
20744
|
+
const mcp = asRecord4(raw.mcp);
|
|
20745
|
+
if (mcp) return mcp;
|
|
20746
|
+
}
|
|
20747
|
+
const mcpServers = asRecord4(raw.mcpServers);
|
|
20748
|
+
if (mcpServers) return mcpServers;
|
|
20749
|
+
if (typeof raw.mcpServers === "string") return null;
|
|
20750
|
+
if (path.endsWith("mcp.json") || path.endsWith(".mcp.json")) {
|
|
20751
|
+
return raw;
|
|
20752
|
+
}
|
|
20753
|
+
return null;
|
|
20754
|
+
}
|
|
20755
|
+
function parseTomlMcpFile(path, platform) {
|
|
20756
|
+
const content = readFileSync14(path, "utf-8");
|
|
20757
|
+
const servers = {};
|
|
20758
|
+
let currentServer;
|
|
20759
|
+
let currentSubtable;
|
|
20760
|
+
for (const rawLine of content.split(/\r?\n/)) {
|
|
20761
|
+
const line = stripTomlComment(rawLine).trim();
|
|
20762
|
+
if (!line) continue;
|
|
20763
|
+
const section = line.match(/^\[mcp_servers\.([A-Za-z0-9_.-]+)(?:\.([A-Za-z0-9_.-]+))?\]$/);
|
|
20764
|
+
if (section) {
|
|
20765
|
+
currentServer = section[1];
|
|
20766
|
+
currentSubtable = section[2];
|
|
20767
|
+
servers[currentServer] ??= {};
|
|
20768
|
+
if (currentSubtable) {
|
|
20769
|
+
const existing = servers[currentServer][currentSubtable];
|
|
20770
|
+
if (!existing || typeof existing !== "object" || Array.isArray(existing)) {
|
|
20771
|
+
servers[currentServer][currentSubtable] = {};
|
|
19384
20772
|
}
|
|
19385
|
-
result[name] = entry;
|
|
19386
20773
|
}
|
|
19387
|
-
|
|
19388
|
-
}
|
|
19389
|
-
|
|
20774
|
+
continue;
|
|
20775
|
+
}
|
|
20776
|
+
if (!currentServer) continue;
|
|
20777
|
+
const assignment = line.match(/^([A-Za-z0-9_-]+)\s*=\s*(.+)$/);
|
|
20778
|
+
if (!assignment) continue;
|
|
20779
|
+
const key = assignment[1];
|
|
20780
|
+
const value = parseTomlValue(assignment[2].trim());
|
|
20781
|
+
if (currentSubtable) {
|
|
20782
|
+
const table = servers[currentServer][currentSubtable];
|
|
20783
|
+
table[key] = value;
|
|
20784
|
+
} else {
|
|
20785
|
+
servers[currentServer][key] = value;
|
|
20786
|
+
}
|
|
20787
|
+
}
|
|
20788
|
+
return parseMcpServerRecords(servers, platform);
|
|
20789
|
+
}
|
|
20790
|
+
function parseMcpServerRecords(servers, platform) {
|
|
20791
|
+
const parsed = {};
|
|
20792
|
+
const warnings = [];
|
|
20793
|
+
let runtimeAuthMode = "inline";
|
|
20794
|
+
const nativePlatformOverrides = buildNativeMcpPlatformOverrides(platform, servers);
|
|
20795
|
+
for (const [name, config] of Object.entries(servers)) {
|
|
20796
|
+
const cfg = asRecord4(config);
|
|
20797
|
+
if (!cfg) continue;
|
|
20798
|
+
const entryWarnings = [];
|
|
20799
|
+
const entry = parseMcpServerEntry(cfg, entryWarnings);
|
|
20800
|
+
if (entry.auth?.type === "platform" && entry.transport !== "stdio") {
|
|
20801
|
+
runtimeAuthMode = "platform";
|
|
20802
|
+
}
|
|
20803
|
+
if (entryWarnings.length > 0) {
|
|
20804
|
+
entry.warnings = entryWarnings;
|
|
20805
|
+
warnings.push(...entryWarnings.map((warning) => `${name}: ${warning}`));
|
|
20806
|
+
}
|
|
20807
|
+
parsed[name] = entry;
|
|
20808
|
+
}
|
|
20809
|
+
return {
|
|
20810
|
+
servers: parsed,
|
|
20811
|
+
runtimeAuthMode,
|
|
20812
|
+
warnings,
|
|
20813
|
+
...nativePlatformOverrides ? { platformOverrides: nativePlatformOverrides } : {}
|
|
20814
|
+
};
|
|
20815
|
+
}
|
|
20816
|
+
function parseMcpServerEntry(cfg, warnings) {
|
|
20817
|
+
const entry = {};
|
|
20818
|
+
if (typeof cfg.url === "string") entry.url = cfg.url;
|
|
20819
|
+
if (cfg.type === "stdio" || cfg.command) {
|
|
20820
|
+
entry.transport = "stdio";
|
|
20821
|
+
entry.command = normalizeCommandValue(cfg.command);
|
|
20822
|
+
entry.args = normalizeStringArray(cfg.args);
|
|
20823
|
+
entry.env = normalizeEnvRecord(cfg.env);
|
|
20824
|
+
} else if (cfg.type === "sse") {
|
|
20825
|
+
entry.transport = "sse";
|
|
20826
|
+
} else {
|
|
20827
|
+
entry.transport = "http";
|
|
20828
|
+
}
|
|
20829
|
+
const auth = inferMigrateAuth(cfg, warnings);
|
|
20830
|
+
if (auth) entry.auth = auth;
|
|
20831
|
+
return entry;
|
|
20832
|
+
}
|
|
20833
|
+
function inferMigrateAuth(cfg, warnings) {
|
|
20834
|
+
const explicitAuth = asRecord4(cfg.auth);
|
|
20835
|
+
const explicitType = typeof explicitAuth?.type === "string" ? explicitAuth.type : void 0;
|
|
20836
|
+
if (explicitType === "platform") {
|
|
20837
|
+
return {
|
|
20838
|
+
type: "platform",
|
|
20839
|
+
mode: explicitAuth?.mode === "oauth" ? "oauth" : "oauth"
|
|
20840
|
+
};
|
|
20841
|
+
}
|
|
20842
|
+
if (explicitType === "none") {
|
|
20843
|
+
return { type: "none" };
|
|
20844
|
+
}
|
|
20845
|
+
if (explicitType === "bearer") {
|
|
20846
|
+
const envVar = firstString2(explicitAuth?.envVar, explicitAuth?.env_var);
|
|
20847
|
+
if (envVar) {
|
|
20848
|
+
return {
|
|
20849
|
+
type: "bearer",
|
|
20850
|
+
envVar,
|
|
20851
|
+
headerName: firstString2(explicitAuth?.headerName, explicitAuth?.header_name) ?? "Authorization",
|
|
20852
|
+
headerTemplate: firstString2(explicitAuth?.headerTemplate, explicitAuth?.header_template) ?? "Bearer ${value}"
|
|
20853
|
+
};
|
|
20854
|
+
}
|
|
20855
|
+
}
|
|
20856
|
+
if (explicitType === "header") {
|
|
20857
|
+
const envVar = firstString2(explicitAuth?.envVar, explicitAuth?.env_var);
|
|
20858
|
+
const headerName = firstString2(explicitAuth?.headerName, explicitAuth?.header_name);
|
|
20859
|
+
if (envVar && headerName) {
|
|
20860
|
+
return {
|
|
20861
|
+
type: "header",
|
|
20862
|
+
envVar,
|
|
20863
|
+
headerName,
|
|
20864
|
+
headerTemplate: firstString2(explicitAuth?.headerTemplate, explicitAuth?.header_template) ?? "${value}"
|
|
20865
|
+
};
|
|
20866
|
+
}
|
|
20867
|
+
}
|
|
20868
|
+
const bearerTokenEnv = firstString2(cfg.bearer_token_env_var, cfg.bearerTokenEnvVar);
|
|
20869
|
+
if (bearerTokenEnv) {
|
|
20870
|
+
return {
|
|
20871
|
+
type: "bearer",
|
|
20872
|
+
envVar: bearerTokenEnv,
|
|
20873
|
+
headerName: "Authorization",
|
|
20874
|
+
headerTemplate: "Bearer ${value}"
|
|
20875
|
+
};
|
|
20876
|
+
}
|
|
20877
|
+
const envHttpHeaders = asRecord4(cfg.env_http_headers ?? cfg.envHttpHeaders);
|
|
20878
|
+
if (envHttpHeaders) {
|
|
20879
|
+
const stringHeaders = Object.entries(envHttpHeaders).filter(([, value]) => typeof value === "string").map(([key, value]) => [key, String(value)]);
|
|
20880
|
+
if (stringHeaders.length > 0) {
|
|
20881
|
+
const preferred = stringHeaders.find(([key]) => key.toLowerCase() === "authorization") ?? stringHeaders[0];
|
|
20882
|
+
const [headerName, envVar] = preferred;
|
|
20883
|
+
const extras = stringHeaders.filter(([key]) => key !== headerName);
|
|
20884
|
+
if (extras.length > 0) {
|
|
20885
|
+
warnings.push(`Preserved ${headerName} as canonical auth but native env_http_headers declared additional header auth keys (${extras.map(([key]) => key).join(", ")}). Review the migrated MCP config and platform overrides.`);
|
|
20886
|
+
}
|
|
20887
|
+
return {
|
|
20888
|
+
type: headerName.toLowerCase() === "authorization" ? "bearer" : "header",
|
|
20889
|
+
envVar,
|
|
20890
|
+
headerName,
|
|
20891
|
+
headerTemplate: headerName.toLowerCase() === "authorization" ? "Bearer ${value}" : "${value}"
|
|
20892
|
+
};
|
|
19390
20893
|
}
|
|
19391
20894
|
}
|
|
19392
|
-
|
|
19393
|
-
|
|
19394
|
-
|
|
19395
|
-
|
|
19396
|
-
|
|
19397
|
-
|
|
19398
|
-
|
|
19399
|
-
|
|
19400
|
-
|
|
19401
|
-
|
|
19402
|
-
|
|
19403
|
-
|
|
19404
|
-
BeforeReadFile: "beforeReadFile",
|
|
19405
|
-
BeforeSubmitPrompt: "beforeSubmitPrompt",
|
|
19406
|
-
Stop: "stop",
|
|
19407
|
-
// Also handle already-normalized names
|
|
19408
|
-
sessionStart: "sessionStart",
|
|
19409
|
-
sessionEnd: "sessionEnd",
|
|
19410
|
-
preToolUse: "preToolUse",
|
|
19411
|
-
postToolUse: "postToolUse"
|
|
19412
|
-
};
|
|
19413
|
-
function parseHooks(pluginDir, detection) {
|
|
19414
|
-
const hooksPaths = [
|
|
19415
|
-
resolve21(pluginDir, ".codex", "hooks.json"),
|
|
19416
|
-
resolve21(pluginDir, "hooks.json"),
|
|
19417
|
-
resolve21(pluginDir, "hooks", "hooks.json")
|
|
19418
|
-
];
|
|
19419
|
-
try {
|
|
19420
|
-
const manifest = JSON.parse(readFileSync14(detection.manifestPath, "utf-8"));
|
|
19421
|
-
if (manifest.hooks && typeof manifest.hooks === "string") {
|
|
19422
|
-
hooksPaths.unshift(resolve21(pluginDir, manifest.hooks));
|
|
20895
|
+
const headers = asRecord4(cfg.headers ?? cfg.http_headers ?? cfg.httpHeaders);
|
|
20896
|
+
if (!headers) return void 0;
|
|
20897
|
+
const envBackedHeaders = Object.entries(headers).filter(([, value]) => typeof value === "string").map(([headerName, rawValue]) => ({
|
|
20898
|
+
headerName,
|
|
20899
|
+
rawValue,
|
|
20900
|
+
envVar: extractEnvVar2(rawValue)
|
|
20901
|
+
})).filter((entry) => Boolean(entry.envVar));
|
|
20902
|
+
if (envBackedHeaders.length > 0) {
|
|
20903
|
+
const preferred = envBackedHeaders.find((entry) => entry.headerName.toLowerCase() === "authorization") ?? envBackedHeaders[0];
|
|
20904
|
+
const extras = envBackedHeaders.filter((entry) => entry.headerName !== preferred.headerName);
|
|
20905
|
+
if (extras.length > 0) {
|
|
20906
|
+
warnings.push(`Preserved ${preferred.headerName} as canonical auth but native headers declared additional env-backed auth keys (${extras.map((entry) => entry.headerName).join(", ")}). Review the migrated MCP config and platform overrides.`);
|
|
19423
20907
|
}
|
|
19424
|
-
|
|
20908
|
+
return {
|
|
20909
|
+
type: preferred.headerName.toLowerCase() === "authorization" ? "bearer" : "header",
|
|
20910
|
+
envVar: preferred.envVar,
|
|
20911
|
+
headerName: preferred.headerName,
|
|
20912
|
+
headerTemplate: preferred.rawValue.replace(envReferencePattern(preferred.envVar), "${value}")
|
|
20913
|
+
};
|
|
19425
20914
|
}
|
|
19426
|
-
|
|
19427
|
-
|
|
20915
|
+
return void 0;
|
|
20916
|
+
}
|
|
20917
|
+
function normalizeCommandValue(value) {
|
|
20918
|
+
if (typeof value === "string") return value;
|
|
20919
|
+
if (Array.isArray(value) && typeof value[0] === "string") return value[0];
|
|
20920
|
+
return void 0;
|
|
20921
|
+
}
|
|
20922
|
+
function normalizeStringArray(value) {
|
|
20923
|
+
if (!Array.isArray(value)) return void 0;
|
|
20924
|
+
return value.map(String);
|
|
20925
|
+
}
|
|
20926
|
+
function normalizeEnvRecord(value) {
|
|
20927
|
+
const record = asRecord4(value);
|
|
20928
|
+
if (!record) return void 0;
|
|
20929
|
+
const env = Object.fromEntries(
|
|
20930
|
+
Object.entries(record).filter(([, rawValue]) => typeof rawValue === "string").map(([key, rawValue]) => [key, rawValue])
|
|
20931
|
+
);
|
|
20932
|
+
return Object.keys(env).length > 0 ? env : void 0;
|
|
20933
|
+
}
|
|
20934
|
+
function asRecord4(value) {
|
|
20935
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
20936
|
+
return value;
|
|
20937
|
+
}
|
|
20938
|
+
function firstString2(...values) {
|
|
20939
|
+
for (const value of values) {
|
|
20940
|
+
if (typeof value === "string" && value.trim()) return value.trim();
|
|
20941
|
+
}
|
|
20942
|
+
return void 0;
|
|
20943
|
+
}
|
|
20944
|
+
function extractEnvVar2(value) {
|
|
20945
|
+
const match = value.match(/\$\{(?:env:)?([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*)/);
|
|
20946
|
+
return match?.[1] ?? match?.[2];
|
|
20947
|
+
}
|
|
20948
|
+
function envReferencePattern(envVar) {
|
|
20949
|
+
return new RegExp(`\\$\\{(?:env:)?${escapeRegExp2(envVar)}\\}|\\$${escapeRegExp2(envVar)}`);
|
|
20950
|
+
}
|
|
20951
|
+
function escapeRegExp2(value) {
|
|
20952
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
20953
|
+
}
|
|
20954
|
+
function parseTopLevelStringFrontmatter(frontmatterLines, key) {
|
|
20955
|
+
const match = frontmatterLines.find((line) => new RegExp(`^${escapeRegExp2(key)}\\s*:`).test(line.trim()));
|
|
20956
|
+
if (!match) return void 0;
|
|
20957
|
+
const rawValue = match.replace(/^[^:]+:\s*/, "");
|
|
20958
|
+
return unquoteFrontmatterValue(rawValue);
|
|
20959
|
+
}
|
|
20960
|
+
function parseTopLevelBooleanFrontmatter(frontmatterLines, key) {
|
|
20961
|
+
const value = parseTopLevelStringFrontmatter(frontmatterLines, key)?.toLowerCase();
|
|
20962
|
+
if (value === "true") return true;
|
|
20963
|
+
if (value === "false") return false;
|
|
20964
|
+
return void 0;
|
|
20965
|
+
}
|
|
20966
|
+
function parseTopLevelStringOrStringArrayFrontmatter(frontmatterLines, key) {
|
|
20967
|
+
const match = frontmatterLines.find((line) => new RegExp(`^${escapeRegExp2(key)}\\s*:`).test(line.trim()));
|
|
20968
|
+
if (!match) return void 0;
|
|
20969
|
+
const rawValue = match.replace(/^[^:]+:\s*/, "").trim();
|
|
20970
|
+
if (rawValue.startsWith("[") && rawValue.endsWith("]")) {
|
|
19428
20971
|
try {
|
|
19429
|
-
const
|
|
19430
|
-
|
|
19431
|
-
|
|
19432
|
-
const result = {};
|
|
19433
|
-
for (const [event, entries] of Object.entries(hooksObj)) {
|
|
19434
|
-
const normalizedEvent = HOOK_EVENT_MAP[event] ?? event;
|
|
19435
|
-
const hookEntries = [];
|
|
19436
|
-
if (!Array.isArray(entries)) continue;
|
|
19437
|
-
for (const entry of entries) {
|
|
19438
|
-
if (entry.hooks && Array.isArray(entry.hooks)) {
|
|
19439
|
-
for (const hook of entry.hooks) {
|
|
19440
|
-
if (hook.command) {
|
|
19441
|
-
hookEntries.push({
|
|
19442
|
-
command: hook.command,
|
|
19443
|
-
...hook.timeout && { timeout: hook.timeout }
|
|
19444
|
-
});
|
|
19445
|
-
}
|
|
19446
|
-
}
|
|
19447
|
-
} else if (entry.command) {
|
|
19448
|
-
hookEntries.push({
|
|
19449
|
-
command: entry.command,
|
|
19450
|
-
...entry.timeout && { timeout: entry.timeout },
|
|
19451
|
-
...entry.matcher && { matcher: entry.matcher }
|
|
19452
|
-
});
|
|
19453
|
-
}
|
|
19454
|
-
}
|
|
19455
|
-
if (hookEntries.length > 0) {
|
|
19456
|
-
result[normalizedEvent] = hookEntries;
|
|
19457
|
-
}
|
|
20972
|
+
const parsed = JSON.parse(rawValue);
|
|
20973
|
+
if (Array.isArray(parsed) && parsed.every((value) => typeof value === "string")) {
|
|
20974
|
+
return parsed;
|
|
19458
20975
|
}
|
|
19459
|
-
return result;
|
|
19460
20976
|
} catch {
|
|
19461
|
-
|
|
20977
|
+
return void 0;
|
|
19462
20978
|
}
|
|
19463
20979
|
}
|
|
19464
|
-
return
|
|
20980
|
+
return unquoteFrontmatterValue(rawValue);
|
|
19465
20981
|
}
|
|
19466
|
-
function
|
|
19467
|
-
const
|
|
19468
|
-
|
|
19469
|
-
|
|
19470
|
-
"instructions.md",
|
|
19471
|
-
"INSTRUCTIONS.md",
|
|
19472
|
-
"README.md"
|
|
19473
|
-
];
|
|
19474
|
-
for (const candidate of candidates) {
|
|
19475
|
-
const filePath = resolve21(pluginDir, candidate);
|
|
19476
|
-
if (existsSync24(filePath)) {
|
|
19477
|
-
return `./${candidate}`;
|
|
19478
|
-
}
|
|
20982
|
+
function unquoteFrontmatterValue(value) {
|
|
20983
|
+
const trimmed = value.trim();
|
|
20984
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
20985
|
+
return trimmed.slice(1, -1);
|
|
19479
20986
|
}
|
|
19480
|
-
return
|
|
20987
|
+
return trimmed;
|
|
19481
20988
|
}
|
|
19482
20989
|
var CANONICAL_SOURCE_CANDIDATES = {
|
|
19483
20990
|
skills: ["skills", ".claude/skills", ".cursor/skills", ".agents/skills", ".opencode/skills"],
|
|
@@ -19559,7 +21066,12 @@ function inferPermissionsFromMigratedSkills(pluginDir, sourcePaths) {
|
|
|
19559
21066
|
if (!entry.isDirectory()) continue;
|
|
19560
21067
|
const skillPath = resolve21(skillsDir, entry.name, "SKILL.md");
|
|
19561
21068
|
if (!existsSync24(skillPath)) continue;
|
|
19562
|
-
const skill =
|
|
21069
|
+
const skill = getCanonicalSkillMetadata({
|
|
21070
|
+
filePath: skillPath,
|
|
21071
|
+
relativeDir: entry.name,
|
|
21072
|
+
dirName: entry.name,
|
|
21073
|
+
...parseSkillMarkdown(readFileSync14(skillPath, "utf-8"))
|
|
21074
|
+
});
|
|
19563
21075
|
const inferredRules = skill.allowedTools.map(normalizeMigratedAllowedTool).filter((rule) => Boolean(rule));
|
|
19564
21076
|
if (inferredRules.length === 0) continue;
|
|
19565
21077
|
sawAllowedTools = true;
|
|
@@ -19581,7 +21093,7 @@ function inferPermissionsFromMigratedSkills(pluginDir, sourcePaths) {
|
|
|
19581
21093
|
if (skill.shell) sourceFrontmatter.shell = skill.shell;
|
|
19582
21094
|
skillPolicies.push({
|
|
19583
21095
|
skillDir: entry.name,
|
|
19584
|
-
title: skill.
|
|
21096
|
+
title: skill.title,
|
|
19585
21097
|
description: skill.description,
|
|
19586
21098
|
...Object.keys(sourceFrontmatter).length > 0 ? { sourceFrontmatter } : {},
|
|
19587
21099
|
source: {
|
|
@@ -19612,10 +21124,11 @@ function readMigratedSkills(pluginDir, sourcePaths) {
|
|
|
19612
21124
|
if (sourcePaths.skills) {
|
|
19613
21125
|
const skillsDir = resolve21(pluginDir, stripRelativePrefix(sourcePaths.skills));
|
|
19614
21126
|
for (const skill of readCanonicalSkillFiles(skillsDir)) {
|
|
21127
|
+
const metadata = getCanonicalSkillMetadata(skill);
|
|
19615
21128
|
skills.push({
|
|
19616
21129
|
dirName: skill.dirName,
|
|
19617
|
-
title:
|
|
19618
|
-
description:
|
|
21130
|
+
title: metadata.title,
|
|
21131
|
+
description: metadata.description,
|
|
19619
21132
|
toolNames: []
|
|
19620
21133
|
});
|
|
19621
21134
|
}
|
|
@@ -19674,9 +21187,84 @@ function parseCodexAgentToml(content) {
|
|
|
19674
21187
|
description: readTomlScalarValue(content, "description"),
|
|
19675
21188
|
model: readTomlScalarValue(content, "model"),
|
|
19676
21189
|
effort: readTomlScalarValue(content, "model_reasoning_effort"),
|
|
21190
|
+
sandboxMode: readTomlScalarValue(content, "sandbox_mode"),
|
|
19677
21191
|
developerInstructions: readTomlMultilineValue(content, "developer_instructions")
|
|
19678
21192
|
};
|
|
19679
21193
|
}
|
|
21194
|
+
function stripTomlComment(line) {
|
|
21195
|
+
let inString = false;
|
|
21196
|
+
let quote2 = "";
|
|
21197
|
+
for (let i = 0; i < line.length; i += 1) {
|
|
21198
|
+
const char = line[i];
|
|
21199
|
+
if ((char === '"' || char === "'") && line[i - 1] !== "\\") {
|
|
21200
|
+
if (!inString) {
|
|
21201
|
+
inString = true;
|
|
21202
|
+
quote2 = char;
|
|
21203
|
+
} else if (quote2 === char) {
|
|
21204
|
+
inString = false;
|
|
21205
|
+
quote2 = "";
|
|
21206
|
+
}
|
|
21207
|
+
continue;
|
|
21208
|
+
}
|
|
21209
|
+
if (char === "#" && !inString) return line.slice(0, i);
|
|
21210
|
+
}
|
|
21211
|
+
return line;
|
|
21212
|
+
}
|
|
21213
|
+
function parseTomlValue(value) {
|
|
21214
|
+
if (value.startsWith('"') && value.endsWith('"')) return unquoteTomlString(value);
|
|
21215
|
+
if (value.startsWith("'") && value.endsWith("'")) return value.slice(1, -1);
|
|
21216
|
+
if (value.startsWith("[") && value.endsWith("]")) {
|
|
21217
|
+
const inner = value.slice(1, -1).trim();
|
|
21218
|
+
if (!inner) return [];
|
|
21219
|
+
return splitTomlList(inner).map((part) => parseTomlValue(part.trim()));
|
|
21220
|
+
}
|
|
21221
|
+
if (value.startsWith("{") && value.endsWith("}")) {
|
|
21222
|
+
const inner = value.slice(1, -1).trim();
|
|
21223
|
+
const result = {};
|
|
21224
|
+
if (!inner) return result;
|
|
21225
|
+
for (const part of splitTomlList(inner)) {
|
|
21226
|
+
const assignment = part.trim().match(/^([A-Za-z0-9_-]+)\s*=\s*(.+)$/);
|
|
21227
|
+
if (!assignment) continue;
|
|
21228
|
+
result[assignment[1]] = parseTomlValue(assignment[2].trim());
|
|
21229
|
+
}
|
|
21230
|
+
return result;
|
|
21231
|
+
}
|
|
21232
|
+
if (value === "true") return true;
|
|
21233
|
+
if (value === "false") return false;
|
|
21234
|
+
return value;
|
|
21235
|
+
}
|
|
21236
|
+
function splitTomlList(value) {
|
|
21237
|
+
const parts = [];
|
|
21238
|
+
let current = "";
|
|
21239
|
+
let inString = false;
|
|
21240
|
+
let quote2 = "";
|
|
21241
|
+
let braceDepth = 0;
|
|
21242
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
21243
|
+
const char = value[i];
|
|
21244
|
+
if ((char === '"' || char === "'") && value[i - 1] !== "\\") {
|
|
21245
|
+
if (!inString) {
|
|
21246
|
+
inString = true;
|
|
21247
|
+
quote2 = char;
|
|
21248
|
+
} else if (quote2 === char) {
|
|
21249
|
+
inString = false;
|
|
21250
|
+
quote2 = "";
|
|
21251
|
+
}
|
|
21252
|
+
}
|
|
21253
|
+
if (!inString && char === "{") braceDepth += 1;
|
|
21254
|
+
if (!inString && char === "}") braceDepth -= 1;
|
|
21255
|
+
if (!inString && braceDepth === 0 && char === ",") {
|
|
21256
|
+
parts.push(current);
|
|
21257
|
+
current = "";
|
|
21258
|
+
continue;
|
|
21259
|
+
}
|
|
21260
|
+
current += char;
|
|
21261
|
+
}
|
|
21262
|
+
if (current.trim()) parts.push(current);
|
|
21263
|
+
return parts;
|
|
21264
|
+
}
|
|
21265
|
+
function unquoteTomlString(value) {
|
|
21266
|
+
return value.slice(1, -1).replace(/\\"/g, '"').replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\\\/g, "\\");
|
|
21267
|
+
}
|
|
19680
21268
|
function renderMigratedAgentMarkdown(fileStem, parsed) {
|
|
19681
21269
|
const agentName = toKebabCase2(parsed.name ?? fileStem) || "agent";
|
|
19682
21270
|
const title = parsed.name ?? titleCaseFromDirName(agentName);
|
|
@@ -19692,6 +21280,7 @@ function renderMigratedAgentMarkdown(fileStem, parsed) {
|
|
|
19692
21280
|
...parsed.description ? [`description: ${JSON.stringify(parsed.description)}`] : [],
|
|
19693
21281
|
...parsed.model ? [`model: ${JSON.stringify(parsed.model)}`] : [],
|
|
19694
21282
|
...parsed.effort ? [`model_reasoning_effort: ${JSON.stringify(parsed.effort)}`] : [],
|
|
21283
|
+
...parsed.sandboxMode ? [`sandbox_mode: ${JSON.stringify(parsed.sandboxMode)}`] : [],
|
|
19695
21284
|
"---",
|
|
19696
21285
|
"",
|
|
19697
21286
|
`# ${title}`,
|
|
@@ -19855,6 +21444,12 @@ function normalizeMigrateAuth(auth) {
|
|
|
19855
21444
|
if (auth.type === "none") {
|
|
19856
21445
|
return { type: "none" };
|
|
19857
21446
|
}
|
|
21447
|
+
if (auth.type === "platform") {
|
|
21448
|
+
return {
|
|
21449
|
+
type: "platform",
|
|
21450
|
+
mode: auth.mode ?? "oauth"
|
|
21451
|
+
};
|
|
21452
|
+
}
|
|
19858
21453
|
if (auth.type === "bearer" && auth.envVar) {
|
|
19859
21454
|
return {
|
|
19860
21455
|
type: "bearer",
|
|
@@ -19880,6 +21475,7 @@ function buildMigratedScaffoldMetadata(result, outputDir) {
|
|
|
19880
21475
|
const generatedHookEvents = Object.keys(result.hooks);
|
|
19881
21476
|
const managedFiles = [
|
|
19882
21477
|
...result.instructions ? [result.instructions.replace(/^\.\//, "")] : [],
|
|
21478
|
+
...result.extraCopyPaths.map((path) => path.replace(/^\.\//, "")),
|
|
19883
21479
|
...["skills", "commands", "agents", "scripts", "assets"].flatMap((dir) => {
|
|
19884
21480
|
if (!result.sourcePaths[dir]) return [];
|
|
19885
21481
|
const baseDir = dir;
|
|
@@ -19926,7 +21522,7 @@ function buildMigratedScaffoldMetadata(result, outputDir) {
|
|
|
19926
21522
|
requestedHookMode: generatedHookEvents.length > 0 ? "safe" : "none",
|
|
19927
21523
|
generatedHookMode: generatedHookEvents.length > 0 ? "safe" : "none",
|
|
19928
21524
|
generatedHookEvents,
|
|
19929
|
-
runtimeAuthMode:
|
|
21525
|
+
runtimeAuthMode: result.runtimeAuthMode
|
|
19930
21526
|
},
|
|
19931
21527
|
userConfig: [],
|
|
19932
21528
|
tools: [],
|
|
@@ -20007,6 +21603,9 @@ function generateConfigTs(result) {
|
|
|
20007
21603
|
if (result.manifest.keywords && result.manifest.keywords.length > 0) {
|
|
20008
21604
|
lines.push(` keywords: [${result.manifest.keywords.map((k) => quote(k)).join(", ")}],`);
|
|
20009
21605
|
}
|
|
21606
|
+
if (result.manifest.brand && Object.keys(result.manifest.brand).length > 0) {
|
|
21607
|
+
lines.push(" brand: " + renderIndentedTsValue(result.manifest.brand, 2) + ",");
|
|
21608
|
+
}
|
|
20010
21609
|
lines.push("");
|
|
20011
21610
|
if (result.sourcePaths.skills) {
|
|
20012
21611
|
lines.push(` skills: './skills/',`);
|
|
@@ -20053,6 +21652,9 @@ function generateConfigTs(result) {
|
|
|
20053
21652
|
for (const name2 of mcpNames) {
|
|
20054
21653
|
const server = result.mcp[name2];
|
|
20055
21654
|
lines.push(` ${quote(name2)}: {`);
|
|
21655
|
+
for (const warning of server.warnings ?? []) {
|
|
21656
|
+
lines.push(` // ${warning}`);
|
|
21657
|
+
}
|
|
20056
21658
|
if (server.url) lines.push(` url: ${quote(server.url)},`);
|
|
20057
21659
|
if (server.transport && server.transport !== "http") {
|
|
20058
21660
|
lines.push(` transport: ${quote(server.transport)},`);
|
|
@@ -20072,6 +21674,9 @@ function generateConfigTs(result) {
|
|
|
20072
21674
|
lines.push(" auth: {");
|
|
20073
21675
|
lines.push(` type: ${quote(server.auth.type)},`);
|
|
20074
21676
|
if (server.auth.envVar) lines.push(` envVar: ${quote(server.auth.envVar)},`);
|
|
21677
|
+
if (server.auth.mode && server.auth.type === "platform") {
|
|
21678
|
+
lines.push(` mode: ${quote(server.auth.mode)},`);
|
|
21679
|
+
}
|
|
20075
21680
|
if (server.auth.headerName && server.auth.headerName !== "Authorization") {
|
|
20076
21681
|
lines.push(` headerName: ${quote(server.auth.headerName)},`);
|
|
20077
21682
|
}
|
|
@@ -20084,6 +21689,11 @@ function generateConfigTs(result) {
|
|
|
20084
21689
|
}
|
|
20085
21690
|
lines.push(" },");
|
|
20086
21691
|
}
|
|
21692
|
+
const platforms = buildMigratedPlatformOverrides(result);
|
|
21693
|
+
if (platforms && Object.keys(platforms).length > 0) {
|
|
21694
|
+
lines.push("");
|
|
21695
|
+
lines.push(" platforms: " + renderIndentedTsValue(platforms, 2) + ",");
|
|
21696
|
+
}
|
|
20087
21697
|
const hookEvents = Object.keys(result.hooks);
|
|
20088
21698
|
if (hookEvents.length > 0) {
|
|
20089
21699
|
lines.push("");
|
|
@@ -20092,9 +21702,25 @@ function generateConfigTs(result) {
|
|
|
20092
21702
|
const entries = result.hooks[event];
|
|
20093
21703
|
lines.push(` ${event}: [`);
|
|
20094
21704
|
for (const entry of entries) {
|
|
20095
|
-
const parts = [
|
|
20096
|
-
if (entry.
|
|
20097
|
-
if (entry.
|
|
21705
|
+
const parts = [];
|
|
21706
|
+
if (entry.type && entry.type !== "command") parts.push(`type: ${renderTsValue(entry.type)}`);
|
|
21707
|
+
if (entry.command) parts.push(`command: ${renderTsValue(entry.command)}`);
|
|
21708
|
+
if (entry.prompt) parts.push(`prompt: ${renderTsValue(entry.prompt)}`);
|
|
21709
|
+
if (entry.model) parts.push(`model: ${renderTsValue(entry.model)}`);
|
|
21710
|
+
if (entry.url) parts.push(`url: ${renderTsValue(entry.url)}`);
|
|
21711
|
+
if (entry.headers) parts.push(`headers: ${renderTsValue(entry.headers)}`);
|
|
21712
|
+
if (entry.allowedEnvVars) parts.push(`allowedEnvVars: ${renderTsValue(entry.allowedEnvVars)}`);
|
|
21713
|
+
if (entry.server) parts.push(`server: ${renderTsValue(entry.server)}`);
|
|
21714
|
+
if (entry.tool) parts.push(`tool: ${renderTsValue(entry.tool)}`);
|
|
21715
|
+
if (entry.input) parts.push(`input: ${renderTsValue(entry.input)}`);
|
|
21716
|
+
if (entry.if) parts.push(`if: ${renderTsValue(entry.if)}`);
|
|
21717
|
+
if (entry.async !== void 0) parts.push(`async: ${renderTsValue(entry.async)}`);
|
|
21718
|
+
if (entry.asyncRewake !== void 0) parts.push(`asyncRewake: ${renderTsValue(entry.asyncRewake)}`);
|
|
21719
|
+
if (entry.shell) parts.push(`shell: ${renderTsValue(entry.shell)}`);
|
|
21720
|
+
if (entry.timeout !== void 0) parts.push(`timeout: ${renderTsValue(entry.timeout)}`);
|
|
21721
|
+
if (entry.matcher !== void 0) parts.push(`matcher: ${renderTsValue(entry.matcher)}`);
|
|
21722
|
+
if (entry.failClosed !== void 0) parts.push(`failClosed: ${renderTsValue(entry.failClosed)}`);
|
|
21723
|
+
if (entry.loop_limit !== void 0) parts.push(`loop_limit: ${renderTsValue(entry.loop_limit)}`);
|
|
20098
21724
|
lines.push(` { ${parts.join(", ")} },`);
|
|
20099
21725
|
}
|
|
20100
21726
|
lines.push(" ],");
|
|
@@ -20111,6 +21737,65 @@ function generateConfigTs(result) {
|
|
|
20111
21737
|
function quote(s) {
|
|
20112
21738
|
return `'${s.replace(/'/g, "\\'")}'`;
|
|
20113
21739
|
}
|
|
21740
|
+
function renderTsValue(value) {
|
|
21741
|
+
if (typeof value === "string") return quote(value);
|
|
21742
|
+
return JSON.stringify(value);
|
|
21743
|
+
}
|
|
21744
|
+
function renderIndentedTsValue(value, indentLevel) {
|
|
21745
|
+
const rendered = JSON.stringify(value, null, 2);
|
|
21746
|
+
if (!rendered.includes("\n")) return rendered;
|
|
21747
|
+
const indent = " ".repeat(indentLevel);
|
|
21748
|
+
return rendered.split("\n").map((line, index) => index === 0 ? line : indent + line).join("\n");
|
|
21749
|
+
}
|
|
21750
|
+
function buildMigratedPlatformOverrides(result) {
|
|
21751
|
+
const platforms = cloneRecordMap(result.manifest.platforms);
|
|
21752
|
+
if (result.runtimeAuthMode === "platform") {
|
|
21753
|
+
platforms["claude-code"] = {
|
|
21754
|
+
...platforms["claude-code"] ?? {},
|
|
21755
|
+
mcpAuth: "platform"
|
|
21756
|
+
};
|
|
21757
|
+
platforms.cursor = {
|
|
21758
|
+
...platforms.cursor ?? {},
|
|
21759
|
+
mcpAuth: "platform"
|
|
21760
|
+
};
|
|
21761
|
+
}
|
|
21762
|
+
return mergePlatformOverrideMaps(platforms);
|
|
21763
|
+
}
|
|
21764
|
+
function cloneRecordMap(input) {
|
|
21765
|
+
if (!input) return {};
|
|
21766
|
+
return JSON.parse(JSON.stringify(input));
|
|
21767
|
+
}
|
|
21768
|
+
function mergePlatformOverrideMaps(...maps) {
|
|
21769
|
+
const merged = {};
|
|
21770
|
+
for (const map of maps) {
|
|
21771
|
+
if (!map) continue;
|
|
21772
|
+
for (const [platform, override] of Object.entries(map)) {
|
|
21773
|
+
const current = asRecord4(merged[platform]) ?? {};
|
|
21774
|
+
const next = asRecord4(override) ?? {};
|
|
21775
|
+
const mergedPlatform = {
|
|
21776
|
+
...current,
|
|
21777
|
+
...next
|
|
21778
|
+
};
|
|
21779
|
+
const currentMcpServers = asRecord4(current.mcpServers);
|
|
21780
|
+
const nextMcpServers = asRecord4(next.mcpServers);
|
|
21781
|
+
if (currentMcpServers || nextMcpServers) {
|
|
21782
|
+
const mergedMcpServers = {};
|
|
21783
|
+
for (const [serverName, serverConfig] of Object.entries(currentMcpServers ?? {})) {
|
|
21784
|
+
const currentServerConfig = asRecord4(serverConfig);
|
|
21785
|
+
mergedMcpServers[serverName] = currentServerConfig ? { ...currentServerConfig } : serverConfig;
|
|
21786
|
+
}
|
|
21787
|
+
for (const [serverName, serverConfig] of Object.entries(nextMcpServers ?? {})) {
|
|
21788
|
+
const previousServerConfig = asRecord4(mergedMcpServers[serverName]);
|
|
21789
|
+
const nextServerConfig = asRecord4(serverConfig);
|
|
21790
|
+
mergedMcpServers[serverName] = previousServerConfig && nextServerConfig ? { ...previousServerConfig, ...nextServerConfig } : serverConfig;
|
|
21791
|
+
}
|
|
21792
|
+
mergedPlatform.mcpServers = mergedMcpServers;
|
|
21793
|
+
}
|
|
21794
|
+
merged[platform] = mergedPlatform;
|
|
21795
|
+
}
|
|
21796
|
+
}
|
|
21797
|
+
return Object.keys(merged).length > 0 ? merged : void 0;
|
|
21798
|
+
}
|
|
20114
21799
|
async function migrate(inputPath) {
|
|
20115
21800
|
const pluginDir = resolve21(inputPath);
|
|
20116
21801
|
const outputDir = process.cwd();
|
|
@@ -20124,6 +21809,7 @@ async function migrate(inputPath) {
|
|
|
20124
21809
|
console.error("Error: Could not detect plugin platform.");
|
|
20125
21810
|
console.error("Expected one of:");
|
|
20126
21811
|
console.error(" .claude-plugin/plugin.json");
|
|
21812
|
+
console.error(" or a manifest-less Claude plugin with CLAUDE.md");
|
|
20127
21813
|
console.error(" .cursor-plugin/plugin.json");
|
|
20128
21814
|
console.error(" .codex-plugin/plugin.json");
|
|
20129
21815
|
console.error(" package.json with @opencode-ai/plugin dependency");
|
|
@@ -20133,7 +21819,8 @@ async function migrate(inputPath) {
|
|
|
20133
21819
|
const manifest = parseManifest(detection);
|
|
20134
21820
|
console.log(` name: ${manifest.name ?? "(none)"}`);
|
|
20135
21821
|
console.log(` version: ${manifest.version ?? "(none)"}`);
|
|
20136
|
-
const
|
|
21822
|
+
const parsedMcp = parseMcp(pluginDir, detection);
|
|
21823
|
+
const mcp = parsedMcp.servers;
|
|
20137
21824
|
const mcpCount = Object.keys(mcp).length;
|
|
20138
21825
|
if (mcpCount > 0) {
|
|
20139
21826
|
console.log(` mcp servers: ${Object.keys(mcp).join(", ")}`);
|
|
@@ -20143,10 +21830,32 @@ async function migrate(inputPath) {
|
|
|
20143
21830
|
if (hookCount > 0) {
|
|
20144
21831
|
console.log(` hooks: ${Object.keys(hooks).join(", ")}`);
|
|
20145
21832
|
}
|
|
20146
|
-
const
|
|
21833
|
+
const instructionSources = findInstructionSources(pluginDir, detection);
|
|
21834
|
+
const instructions = instructionSources.primary;
|
|
20147
21835
|
if (instructions) {
|
|
20148
21836
|
console.log(` instructions: ${instructions}`);
|
|
20149
21837
|
}
|
|
21838
|
+
if (instructionSources.platformOverrides) {
|
|
21839
|
+
manifest.platforms = mergePlatformOverrideMaps(manifest.platforms, instructionSources.platformOverrides);
|
|
21840
|
+
}
|
|
21841
|
+
const cursorRules = parseCursorRules(pluginDir, detection);
|
|
21842
|
+
const openCodeDistributionSources = findOpenCodeDistributionSources(pluginDir, detection);
|
|
21843
|
+
if (cursorRules.length > 0) {
|
|
21844
|
+
manifest.platforms = {
|
|
21845
|
+
...manifest.platforms ?? {},
|
|
21846
|
+
cursor: {
|
|
21847
|
+
...manifest.platforms?.cursor ?? {},
|
|
21848
|
+
rules: cursorRules
|
|
21849
|
+
}
|
|
21850
|
+
};
|
|
21851
|
+
console.log(` cursor rules: ${cursorRules.length}`);
|
|
21852
|
+
}
|
|
21853
|
+
if (openCodeDistributionSources.platformOverrides) {
|
|
21854
|
+
manifest.platforms = mergePlatformOverrideMaps(manifest.platforms, openCodeDistributionSources.platformOverrides);
|
|
21855
|
+
}
|
|
21856
|
+
if (parsedMcp.platformOverrides) {
|
|
21857
|
+
manifest.platforms = mergePlatformOverrideMaps(manifest.platforms, parsedMcp.platformOverrides);
|
|
21858
|
+
}
|
|
20150
21859
|
const sourcePaths = detectCanonicalSourcePaths(pluginDir);
|
|
20151
21860
|
const passthrough = detectPassthroughDirs(pluginDir, mcp);
|
|
20152
21861
|
const persistedSkills = readMigratedSkills(pluginDir, sourcePaths);
|
|
@@ -20165,6 +21874,7 @@ async function migrate(inputPath) {
|
|
|
20165
21874
|
platform: detection.platform,
|
|
20166
21875
|
manifest,
|
|
20167
21876
|
mcp,
|
|
21877
|
+
runtimeAuthMode: parsedMcp.runtimeAuthMode,
|
|
20168
21878
|
hooks,
|
|
20169
21879
|
permissions: inferredPermissions.permissions,
|
|
20170
21880
|
permissionNotes: inferredPermissions.notes,
|
|
@@ -20172,6 +21882,11 @@ async function migrate(inputPath) {
|
|
|
20172
21882
|
instructions,
|
|
20173
21883
|
sourcePaths,
|
|
20174
21884
|
persistedSkills,
|
|
21885
|
+
warnings: parsedMcp.warnings,
|
|
21886
|
+
extraCopyPaths: [.../* @__PURE__ */ new Set([
|
|
21887
|
+
...instructionSources.extraPaths,
|
|
21888
|
+
...openCodeDistributionSources.extraPaths
|
|
21889
|
+
])].sort(),
|
|
20175
21890
|
...inferredPermissions.skillPolicies.length > 0 ? {
|
|
20176
21891
|
compilerIntent: {
|
|
20177
21892
|
version: 1,
|
|
@@ -20204,6 +21919,14 @@ Generated pluxx.config.ts`);
|
|
|
20204
21919
|
console.log(`Copied: ${instructions}`);
|
|
20205
21920
|
}
|
|
20206
21921
|
}
|
|
21922
|
+
for (const copiedPath of result.extraCopyPaths) {
|
|
21923
|
+
const srcPath = resolve21(pluginDir, copiedPath);
|
|
21924
|
+
const destPath = resolve21(outputDir, copiedPath);
|
|
21925
|
+
if (existsSync24(destPath)) continue;
|
|
21926
|
+
const content = readFileSync14(srcPath, "utf-8");
|
|
21927
|
+
await writeTextFile(destPath, content);
|
|
21928
|
+
console.log(`Copied: ${copiedPath}`);
|
|
21929
|
+
}
|
|
20207
21930
|
const taxonomyPath = resolve21(outputDir, MCP_TAXONOMY_PATH);
|
|
20208
21931
|
const metadataPath = resolve21(outputDir, MCP_SCAFFOLD_METADATA_PATH);
|
|
20209
21932
|
mkdirSync5(resolve21(outputDir, ".pluxx"), { recursive: true });
|
|
@@ -20234,7 +21957,7 @@ Generated pluxx.config.ts`);
|
|
|
20234
21957
|
|
|
20235
21958
|
// src/cli/mcp-proxy.ts
|
|
20236
21959
|
import { mkdirSync as mkdirSync6, readFileSync as readFileSync15 } from "fs";
|
|
20237
|
-
import { dirname as
|
|
21960
|
+
import { dirname as dirname9, resolve as resolve22 } from "path";
|
|
20238
21961
|
import * as readline3 from "readline";
|
|
20239
21962
|
function usage() {
|
|
20240
21963
|
return [
|
|
@@ -20311,7 +22034,7 @@ async function loadReplayTape(filepath) {
|
|
|
20311
22034
|
}
|
|
20312
22035
|
async function writeTape(filepath, tape) {
|
|
20313
22036
|
const absolutePath = resolve22(process.cwd(), filepath);
|
|
20314
|
-
mkdirSync6(
|
|
22037
|
+
mkdirSync6(dirname9(absolutePath), { recursive: true });
|
|
20315
22038
|
await writeTextFile(absolutePath, `${JSON.stringify(tape, null, 2)}
|
|
20316
22039
|
`);
|
|
20317
22040
|
}
|
|
@@ -21488,17 +23211,17 @@ ${c2}
|
|
|
21488
23211
|
import { basename as basename8, resolve as resolve27 } from "path";
|
|
21489
23212
|
import { mkdir as mkdir4, mkdtemp as mkdtemp3, rm as rm4 } from "fs/promises";
|
|
21490
23213
|
import { tmpdir as tmpdir5 } from "os";
|
|
21491
|
-
import { spawn as spawn5, spawnSync as
|
|
23214
|
+
import { spawn as spawn5, spawnSync as spawnSync4 } from "child_process";
|
|
21492
23215
|
|
|
21493
23216
|
// src/cli/publish.ts
|
|
21494
23217
|
import { chmodSync, existsSync as existsSync25, mkdtempSync as mkdtempSync2, readFileSync as readFileSync16, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "fs";
|
|
21495
23218
|
import { createHash } from "crypto";
|
|
21496
23219
|
import { resolve as resolve23 } from "path";
|
|
21497
|
-
import { spawnSync as
|
|
23220
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
21498
23221
|
import { tmpdir as tmpdir3 } from "os";
|
|
21499
23222
|
var INSTALLER_TARGETS = ["claude-code", "cursor", "codex", "opencode"];
|
|
21500
23223
|
function runCommandDefault2(command2, args2, options) {
|
|
21501
|
-
const result =
|
|
23224
|
+
const result = spawnSync3(command2, args2, {
|
|
21502
23225
|
cwd: options?.cwd,
|
|
21503
23226
|
encoding: "utf-8"
|
|
21504
23227
|
});
|
|
@@ -22873,7 +24596,14 @@ async function runBehavioralSuite(rootDir, config, targets, options = {}) {
|
|
|
22873
24596
|
for (const platform of selectedPlatforms) {
|
|
22874
24597
|
const targetConfig = behavioralCase.targets[platform];
|
|
22875
24598
|
if (!targetConfig) continue;
|
|
22876
|
-
checks.push(await runBehavioralCheck(
|
|
24599
|
+
checks.push(await runBehavioralCheck(
|
|
24600
|
+
rootDir,
|
|
24601
|
+
config,
|
|
24602
|
+
behavioralCase.name,
|
|
24603
|
+
behavioralCase.commandId,
|
|
24604
|
+
platform,
|
|
24605
|
+
targetConfig
|
|
24606
|
+
));
|
|
22877
24607
|
}
|
|
22878
24608
|
}
|
|
22879
24609
|
return {
|
|
@@ -22903,17 +24633,31 @@ function loadBehavioralCases(rootDir, targets, promptOverride) {
|
|
|
22903
24633
|
}
|
|
22904
24634
|
return parsed.cases;
|
|
22905
24635
|
}
|
|
22906
|
-
async function runBehavioralCheck(rootDir, config, caseName, platform, targetConfig) {
|
|
24636
|
+
async function runBehavioralCheck(rootDir, config, caseName, caseCommandId, platform, targetConfig) {
|
|
22907
24637
|
const prompt = targetConfig.prompt.trim();
|
|
22908
24638
|
if (!prompt) {
|
|
22909
24639
|
throw new Error(`Behavioral smoke case "${caseName}" for ${platform} is missing a prompt.`);
|
|
22910
24640
|
}
|
|
22911
|
-
const
|
|
24641
|
+
const commandId = targetConfig.commandId ?? caseCommandId;
|
|
24642
|
+
if (commandId) {
|
|
24643
|
+
if (!behavioralPromptReferencesCommand(prompt, commandId)) {
|
|
24644
|
+
throw new Error(
|
|
24645
|
+
`Behavioral smoke case "${caseName}" for ${platform} declares commandId "${commandId}" but the prompt does not reference that command explicitly.`
|
|
24646
|
+
);
|
|
24647
|
+
}
|
|
24648
|
+
if (!targetConfig.require?.length) {
|
|
24649
|
+
throw new Error(
|
|
24650
|
+
`Behavioral smoke case "${caseName}" for ${platform} declares commandId "${commandId}" but does not define any required output markers.`
|
|
24651
|
+
);
|
|
24652
|
+
}
|
|
24653
|
+
}
|
|
24654
|
+
const expectedExitCodes = targetConfig.expectedExitCodes?.length ? [...new Set(targetConfig.expectedExitCodes)] : targetConfig.expectFailure ? [1] : [0];
|
|
24655
|
+
const command2 = await buildBehavioralCommand(platform, prompt, rootDir, targetConfig);
|
|
22912
24656
|
const execution = await executeBehavioralCommand(platform, command2, rootDir);
|
|
22913
24657
|
const responseText = execution.response.trim();
|
|
22914
24658
|
const failures = [];
|
|
22915
|
-
if (execution.exitCode
|
|
22916
|
-
failures.push(`runner exited with code ${execution.exitCode}`);
|
|
24659
|
+
if (!expectedExitCodes.includes(execution.exitCode)) {
|
|
24660
|
+
failures.push(`runner exited with code ${execution.exitCode}; expected one of ${expectedExitCodes.join(", ")}`);
|
|
22917
24661
|
}
|
|
22918
24662
|
if (!responseText) {
|
|
22919
24663
|
failures.push("runner returned no response text");
|
|
@@ -22932,6 +24676,7 @@ async function runBehavioralCheck(rootDir, config, caseName, platform, targetCon
|
|
|
22932
24676
|
caseName,
|
|
22933
24677
|
platform,
|
|
22934
24678
|
prompt,
|
|
24679
|
+
commandId,
|
|
22935
24680
|
command: command2,
|
|
22936
24681
|
ok: failures.length === 0,
|
|
22937
24682
|
exitCode: execution.exitCode,
|
|
@@ -22939,10 +24684,12 @@ async function runBehavioralCheck(rootDir, config, caseName, platform, targetCon
|
|
|
22939
24684
|
responsePreview: truncate2(responseText, 220),
|
|
22940
24685
|
require: targetConfig.require,
|
|
22941
24686
|
forbid: targetConfig.forbid,
|
|
24687
|
+
expectedExitCodes,
|
|
22942
24688
|
failures
|
|
22943
24689
|
};
|
|
22944
24690
|
}
|
|
22945
|
-
async function buildBehavioralCommand(platform, prompt, workspace) {
|
|
24691
|
+
async function buildBehavioralCommand(platform, prompt, workspace, targetConfig) {
|
|
24692
|
+
const runnerArgs = targetConfig.runnerArgs ?? [];
|
|
22946
24693
|
if (platform === "claude-code") {
|
|
22947
24694
|
return [
|
|
22948
24695
|
"claude",
|
|
@@ -22951,6 +24698,7 @@ async function buildBehavioralCommand(platform, prompt, workspace) {
|
|
|
22951
24698
|
"text",
|
|
22952
24699
|
"--permission-mode",
|
|
22953
24700
|
"acceptEdits",
|
|
24701
|
+
...runnerArgs,
|
|
22954
24702
|
"-p",
|
|
22955
24703
|
prompt
|
|
22956
24704
|
];
|
|
@@ -22968,6 +24716,7 @@ async function buildBehavioralCommand(platform, prompt, workspace) {
|
|
|
22968
24716
|
"--workspace",
|
|
22969
24717
|
workspace,
|
|
22970
24718
|
"--force",
|
|
24719
|
+
...runnerArgs,
|
|
22971
24720
|
prompt
|
|
22972
24721
|
];
|
|
22973
24722
|
}
|
|
@@ -22978,10 +24727,11 @@ async function buildBehavioralCommand(platform, prompt, workspace) {
|
|
|
22978
24727
|
"--ephemeral",
|
|
22979
24728
|
"--skip-git-repo-check",
|
|
22980
24729
|
"--full-auto",
|
|
24730
|
+
...runnerArgs,
|
|
22981
24731
|
prompt
|
|
22982
24732
|
];
|
|
22983
24733
|
}
|
|
22984
|
-
return ["opencode", "run", prompt];
|
|
24734
|
+
return ["opencode", "run", ...runnerArgs, prompt];
|
|
22985
24735
|
}
|
|
22986
24736
|
async function executeBehavioralCommand(platform, command2, cwd) {
|
|
22987
24737
|
let codexOutputDir = null;
|
|
@@ -23064,6 +24814,12 @@ function truncate2(value, length) {
|
|
|
23064
24814
|
function includesNeedle(haystack, needle) {
|
|
23065
24815
|
return haystack.toLowerCase().includes(needle.trim().toLowerCase());
|
|
23066
24816
|
}
|
|
24817
|
+
function behavioralPromptReferencesCommand(prompt, commandId) {
|
|
24818
|
+
const normalizedPrompt = prompt.toLowerCase();
|
|
24819
|
+
const normalizedCommandId = commandId.trim().toLowerCase();
|
|
24820
|
+
if (!normalizedCommandId) return false;
|
|
24821
|
+
return normalizedPrompt.includes(`/${normalizedCommandId}`) || normalizedPrompt.includes(`:${normalizedCommandId}`) || normalizedPrompt.includes(`command ${normalizedCommandId}`) || normalizedPrompt.includes(`command \`${normalizedCommandId}\``) || normalizedPrompt.includes(normalizedCommandId);
|
|
24822
|
+
}
|
|
23067
24823
|
function shellQuote2(value) {
|
|
23068
24824
|
if (/^[A-Za-z0-9_./:-]+$/.test(value)) return value;
|
|
23069
24825
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
@@ -23082,7 +24838,7 @@ function discoverInstalledMcpServers(options = {}) {
|
|
|
23082
24838
|
const seen = /* @__PURE__ */ new Set();
|
|
23083
24839
|
for (const candidate of installedMcpFileCandidates(rootDir, homeDir)) {
|
|
23084
24840
|
if (!hostSet.has(candidate.host) || !existsSync28(candidate.path)) continue;
|
|
23085
|
-
const parsed = candidate.parser === "json" ?
|
|
24841
|
+
const parsed = candidate.parser === "json" ? parseJsonMcpFile2(candidate.path, candidate.host) : parseCodexTomlMcpFile(candidate.path);
|
|
23086
24842
|
for (const discovered of parsed) {
|
|
23087
24843
|
const key = `${discovered.host}:${discovered.serverName}:${discovered.sourcePath}`;
|
|
23088
24844
|
if (seen.has(key)) continue;
|
|
@@ -23162,20 +24918,21 @@ function installedMcpFileCandidates(rootDir, homeDir) {
|
|
|
23162
24918
|
{ host: "opencode", path: resolve26(homeDir, ".config/opencode/opencode.json"), parser: "json" }
|
|
23163
24919
|
];
|
|
23164
24920
|
}
|
|
23165
|
-
function
|
|
24921
|
+
function parseJsonMcpFile2(path, host) {
|
|
23166
24922
|
try {
|
|
23167
24923
|
const raw = JSON.parse(readFileSync19(path, "utf-8"));
|
|
23168
|
-
const servers =
|
|
24924
|
+
const servers = extractJsonMcpServers2(raw, path, host);
|
|
23169
24925
|
return Object.entries(servers).flatMap(([serverName, config]) => {
|
|
23170
24926
|
const normalized = normalizeCommonMcpServer(config, host);
|
|
23171
24927
|
if (!normalized) return [];
|
|
23172
|
-
|
|
24928
|
+
const platformOverrides = buildNativeMcpPlatformOverrides(host, { [serverName]: config });
|
|
24929
|
+
return [toDiscovered(host, serverName, path, normalized.server, normalized.warnings, platformOverrides)];
|
|
23173
24930
|
});
|
|
23174
24931
|
} catch {
|
|
23175
24932
|
return [];
|
|
23176
24933
|
}
|
|
23177
24934
|
}
|
|
23178
|
-
function
|
|
24935
|
+
function extractJsonMcpServers2(raw, path, host) {
|
|
23179
24936
|
if (host === "opencode" && raw.mcp && typeof raw.mcp === "object") {
|
|
23180
24937
|
return raw.mcp;
|
|
23181
24938
|
}
|
|
@@ -23204,7 +24961,7 @@ function parseCodexTomlMcpFile(path) {
|
|
|
23204
24961
|
let currentServer;
|
|
23205
24962
|
let currentSubtable;
|
|
23206
24963
|
for (const rawLine of text.split(/\r?\n/)) {
|
|
23207
|
-
const line =
|
|
24964
|
+
const line = stripTomlComment2(rawLine).trim();
|
|
23208
24965
|
if (!line) continue;
|
|
23209
24966
|
const section = line.match(/^\[mcp_servers\.([A-Za-z0-9_.-]+)(?:\.([A-Za-z0-9_.-]+))?\]$/);
|
|
23210
24967
|
if (section) {
|
|
@@ -23223,7 +24980,7 @@ function parseCodexTomlMcpFile(path) {
|
|
|
23223
24980
|
const assignment = line.match(/^([A-Za-z0-9_-]+)\s*=\s*(.+)$/);
|
|
23224
24981
|
if (!assignment) continue;
|
|
23225
24982
|
const key = assignment[1];
|
|
23226
|
-
const value =
|
|
24983
|
+
const value = parseTomlValue2(assignment[2].trim());
|
|
23227
24984
|
if (currentSubtable) {
|
|
23228
24985
|
const table = servers[currentServer][currentSubtable];
|
|
23229
24986
|
table[key] = value;
|
|
@@ -23234,7 +24991,8 @@ function parseCodexTomlMcpFile(path) {
|
|
|
23234
24991
|
return Object.entries(servers).flatMap(([serverName, config]) => {
|
|
23235
24992
|
const normalized = normalizeCommonMcpServer(config, "codex");
|
|
23236
24993
|
if (!normalized) return [];
|
|
23237
|
-
|
|
24994
|
+
const platformOverrides = buildNativeMcpPlatformOverrides("codex", { [serverName]: config });
|
|
24995
|
+
return [toDiscovered("codex", serverName, path, normalized.server, normalized.warnings, platformOverrides)];
|
|
23238
24996
|
});
|
|
23239
24997
|
}
|
|
23240
24998
|
function normalizeCommonMcpServer(config, host) {
|
|
@@ -23242,7 +25000,7 @@ function normalizeCommonMcpServer(config, host) {
|
|
|
23242
25000
|
const cfg = config;
|
|
23243
25001
|
const warnings = [];
|
|
23244
25002
|
const auth = inferAuth(cfg, warnings);
|
|
23245
|
-
const remoteUrl =
|
|
25003
|
+
const remoteUrl = firstString3(cfg.url, cfg.endpoint);
|
|
23246
25004
|
if (remoteUrl) {
|
|
23247
25005
|
const server = cfg.type === "sse" || cfg.transport === "sse" ? {
|
|
23248
25006
|
transport: "sse",
|
|
@@ -23281,9 +25039,9 @@ function normalizeArgs(command2, args2, host) {
|
|
|
23281
25039
|
if (host === "opencode" && Array.isArray(command2)) {
|
|
23282
25040
|
return command2.slice(1).map(String);
|
|
23283
25041
|
}
|
|
23284
|
-
return
|
|
25042
|
+
return normalizeStringArray2(args2);
|
|
23285
25043
|
}
|
|
23286
|
-
function
|
|
25044
|
+
function normalizeStringArray2(value) {
|
|
23287
25045
|
if (!Array.isArray(value)) return [];
|
|
23288
25046
|
return value.map(String);
|
|
23289
25047
|
}
|
|
@@ -23307,7 +25065,7 @@ function normalizeEnv(value, warnings) {
|
|
|
23307
25065
|
return env;
|
|
23308
25066
|
}
|
|
23309
25067
|
function inferAuth(cfg, warnings) {
|
|
23310
|
-
const bearerTokenEnv =
|
|
25068
|
+
const bearerTokenEnv = firstString3(cfg.bearer_token_env_var, cfg.bearerTokenEnvVar);
|
|
23311
25069
|
if (bearerTokenEnv) {
|
|
23312
25070
|
return {
|
|
23313
25071
|
type: "bearer",
|
|
@@ -23333,13 +25091,13 @@ function inferAuth(cfg, warnings) {
|
|
|
23333
25091
|
const headerEntries = Object.entries(headers);
|
|
23334
25092
|
for (const [headerName, rawValue] of headerEntries) {
|
|
23335
25093
|
if (typeof rawValue !== "string") continue;
|
|
23336
|
-
const envVar =
|
|
25094
|
+
const envVar = extractEnvVar3(rawValue);
|
|
23337
25095
|
if (envVar) {
|
|
23338
25096
|
return {
|
|
23339
25097
|
type: headerName.toLowerCase() === "authorization" ? "bearer" : "header",
|
|
23340
25098
|
envVar,
|
|
23341
25099
|
headerName,
|
|
23342
|
-
headerTemplate: rawValue.replace(
|
|
25100
|
+
headerTemplate: rawValue.replace(envReferencePattern2(envVar), "${value}")
|
|
23343
25101
|
};
|
|
23344
25102
|
}
|
|
23345
25103
|
if (looksSecretValue(rawValue)) {
|
|
@@ -23348,43 +25106,44 @@ function inferAuth(cfg, warnings) {
|
|
|
23348
25106
|
}
|
|
23349
25107
|
return void 0;
|
|
23350
25108
|
}
|
|
23351
|
-
function toDiscovered(host, serverName, sourcePath, server, warnings) {
|
|
25109
|
+
function toDiscovered(host, serverName, sourcePath, server, warnings, platformOverrides) {
|
|
23352
25110
|
return {
|
|
23353
25111
|
id: `${host}:${serverName}`,
|
|
23354
25112
|
host,
|
|
23355
25113
|
serverName,
|
|
23356
25114
|
sourcePath,
|
|
23357
25115
|
server,
|
|
23358
|
-
warnings
|
|
25116
|
+
warnings,
|
|
25117
|
+
...platformOverrides ? { platformOverrides } : {}
|
|
23359
25118
|
};
|
|
23360
25119
|
}
|
|
23361
|
-
function
|
|
25120
|
+
function firstString3(...values) {
|
|
23362
25121
|
for (const value of values) {
|
|
23363
25122
|
if (typeof value === "string" && value.trim()) return value.trim();
|
|
23364
25123
|
}
|
|
23365
25124
|
return void 0;
|
|
23366
25125
|
}
|
|
23367
25126
|
function envPlaceholder(value) {
|
|
23368
|
-
const envVar =
|
|
25127
|
+
const envVar = extractEnvVar3(value);
|
|
23369
25128
|
return envVar ? `\${${envVar}}` : void 0;
|
|
23370
25129
|
}
|
|
23371
|
-
function
|
|
25130
|
+
function extractEnvVar3(value) {
|
|
23372
25131
|
const match = value.match(/\$\{(?:env:)?([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*)/);
|
|
23373
25132
|
return match?.[1] ?? match?.[2];
|
|
23374
25133
|
}
|
|
23375
|
-
function
|
|
23376
|
-
return new RegExp(`\\$\\{(?:env:)?${
|
|
25134
|
+
function envReferencePattern2(envVar) {
|
|
25135
|
+
return new RegExp(`\\$\\{(?:env:)?${escapeRegExp3(envVar)}\\}|\\$${escapeRegExp3(envVar)}`);
|
|
23377
25136
|
}
|
|
23378
|
-
function
|
|
25137
|
+
function escapeRegExp3(value) {
|
|
23379
25138
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
23380
25139
|
}
|
|
23381
25140
|
function looksSecretKey(value) {
|
|
23382
25141
|
return /(api|auth|secret|token|key|password|credential)/i.test(value);
|
|
23383
25142
|
}
|
|
23384
25143
|
function looksSecretValue(value) {
|
|
23385
|
-
return value.length >= 16 && !
|
|
25144
|
+
return value.length >= 16 && !extractEnvVar3(value);
|
|
23386
25145
|
}
|
|
23387
|
-
function
|
|
25146
|
+
function stripTomlComment2(line) {
|
|
23388
25147
|
let inString = false;
|
|
23389
25148
|
let quote2 = "";
|
|
23390
25149
|
for (let i = 0; i < line.length; i += 1) {
|
|
@@ -23403,22 +25162,22 @@ function stripTomlComment(line) {
|
|
|
23403
25162
|
}
|
|
23404
25163
|
return line;
|
|
23405
25164
|
}
|
|
23406
|
-
function
|
|
23407
|
-
if (value.startsWith('"') && value.endsWith('"')) return
|
|
25165
|
+
function parseTomlValue2(value) {
|
|
25166
|
+
if (value.startsWith('"') && value.endsWith('"')) return unquoteTomlString2(value);
|
|
23408
25167
|
if (value.startsWith("'") && value.endsWith("'")) return value.slice(1, -1);
|
|
23409
25168
|
if (value.startsWith("[") && value.endsWith("]")) {
|
|
23410
25169
|
const inner = value.slice(1, -1).trim();
|
|
23411
25170
|
if (!inner) return [];
|
|
23412
|
-
return
|
|
25171
|
+
return splitTomlList2(inner).map((part) => parseTomlValue2(part.trim()));
|
|
23413
25172
|
}
|
|
23414
25173
|
if (value.startsWith("{") && value.endsWith("}")) {
|
|
23415
25174
|
const inner = value.slice(1, -1).trim();
|
|
23416
25175
|
const result = {};
|
|
23417
25176
|
if (!inner) return result;
|
|
23418
|
-
for (const part of
|
|
25177
|
+
for (const part of splitTomlList2(inner)) {
|
|
23419
25178
|
const assignment = part.match(/^([A-Za-z0-9_-]+)\s*=\s*(.+)$/);
|
|
23420
25179
|
if (!assignment) continue;
|
|
23421
|
-
result[assignment[1]] =
|
|
25180
|
+
result[assignment[1]] = parseTomlValue2(assignment[2].trim());
|
|
23422
25181
|
}
|
|
23423
25182
|
return result;
|
|
23424
25183
|
}
|
|
@@ -23426,7 +25185,7 @@ function parseTomlValue(value) {
|
|
|
23426
25185
|
if (value === "false") return false;
|
|
23427
25186
|
return value;
|
|
23428
25187
|
}
|
|
23429
|
-
function
|
|
25188
|
+
function splitTomlList2(value) {
|
|
23430
25189
|
const parts = [];
|
|
23431
25190
|
let current = "";
|
|
23432
25191
|
let inString = false;
|
|
@@ -23455,7 +25214,7 @@ function splitTomlList(value) {
|
|
|
23455
25214
|
if (current.trim()) parts.push(current);
|
|
23456
25215
|
return parts;
|
|
23457
25216
|
}
|
|
23458
|
-
function
|
|
25217
|
+
function unquoteTomlString2(value) {
|
|
23459
25218
|
return value.slice(1, -1).replace(/\\"/g, '"').replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\\\/g, "\\");
|
|
23460
25219
|
}
|
|
23461
25220
|
|
|
@@ -23616,7 +25375,7 @@ async function runUpgradeCommand() {
|
|
|
23616
25375
|
}
|
|
23617
25376
|
return;
|
|
23618
25377
|
}
|
|
23619
|
-
const install =
|
|
25378
|
+
const install = spawnSync4(summary.command[0], summary.command.slice(1), runtime.jsonOutput ? {
|
|
23620
25379
|
env: process.env,
|
|
23621
25380
|
encoding: "utf-8",
|
|
23622
25381
|
stdio: "pipe"
|
|
@@ -24686,7 +26445,8 @@ ${formatAuthRequiredMessage("init", retryError, source)}`);
|
|
|
24686
26445
|
setupHints: sourcedContextPack.docsContext.setupHints,
|
|
24687
26446
|
authHints: sourcedContextPack.docsContext.authHints,
|
|
24688
26447
|
warnings: sourcedContextPack.docsContext.warnings
|
|
24689
|
-
} : void 0
|
|
26448
|
+
} : void 0,
|
|
26449
|
+
platformOverrides: installedMcpSource?.platformOverrides
|
|
24690
26450
|
});
|
|
24691
26451
|
const createdFiles = plan.files.filter((file) => file.action === "create").map((file) => file.relativePath);
|
|
24692
26452
|
const updatedFiles = plan.files.filter((file) => file.action === "update").map((file) => file.relativePath);
|