@orchid-labs/pluxx 0.1.20 → 0.1.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/index.js +439 -133
- package/dist/cli/install.d.ts +1 -0
- package/dist/cli/install.d.ts.map +1 -1
- package/dist/cli/primitive-summary.d.ts.map +1 -1
- package/dist/cli/publish.d.ts.map +1 -1
- package/dist/codex-hooks-feature.d.ts +1 -1
- package/dist/codex-hooks-feature.d.ts.map +1 -1
- package/dist/compatibility/core-four-primitives.d.ts +5 -0
- package/dist/compatibility/core-four-primitives.d.ts.map +1 -0
- package/dist/compatibility/matrix.d.ts +1 -0
- package/dist/compatibility/matrix.d.ts.map +1 -1
- package/dist/generators/opencode/index.d.ts.map +1 -1
- package/dist/hook-command-env.d.ts.map +1 -1
- package/dist/hook-events.d.ts +1 -0
- package/dist/hook-events.d.ts.map +1 -1
- package/dist/index.js +346 -60
- package/dist/readiness.d.ts.map +1 -1
- package/dist/user-config.d.ts +11 -0
- package/dist/user-config.d.ts.map +1 -1
- package/dist/validation/platform-rules.d.ts +1 -0
- package/dist/validation/platform-rules.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -5170,12 +5170,39 @@ function buildUserConfigEnvMap(entries) {
|
|
|
5170
5170
|
}
|
|
5171
5171
|
return env;
|
|
5172
5172
|
}
|
|
5173
|
-
function
|
|
5173
|
+
function buildInstalledUserConfigPayload(entries, options = {}) {
|
|
5174
|
+
const preserveSecretReferences = options.preserveSecretReferences === true;
|
|
5174
5175
|
const values = {};
|
|
5176
|
+
const env = {};
|
|
5177
|
+
const envRefs = {};
|
|
5178
|
+
const secretKeys = /* @__PURE__ */ new Set();
|
|
5179
|
+
const secretEnv = /* @__PURE__ */ new Set();
|
|
5180
|
+
let hasSecret = false;
|
|
5175
5181
|
for (const entry of entries) {
|
|
5182
|
+
const envVar = entry.envVar;
|
|
5183
|
+
const isSecret = entry.field.type === "secret";
|
|
5184
|
+
if (isSecret) {
|
|
5185
|
+
hasSecret = true;
|
|
5186
|
+
if (preserveSecretReferences) {
|
|
5187
|
+
secretKeys.add(entry.field.key);
|
|
5188
|
+
if (envVar) secretEnv.add(envVar);
|
|
5189
|
+
}
|
|
5190
|
+
}
|
|
5191
|
+
if (preserveSecretReferences && isSecret) {
|
|
5192
|
+
if (envVar) envRefs[envVar] = envVar;
|
|
5193
|
+
continue;
|
|
5194
|
+
}
|
|
5176
5195
|
values[entry.field.key] = entry.value;
|
|
5196
|
+
if (envVar) env[envVar] = String(entry.value);
|
|
5177
5197
|
}
|
|
5178
|
-
return
|
|
5198
|
+
return {
|
|
5199
|
+
...Object.keys(values).length > 0 ? { values } : {},
|
|
5200
|
+
...Object.keys(env).length > 0 ? { env } : {},
|
|
5201
|
+
...Object.keys(envRefs).length > 0 ? { envRefs } : {},
|
|
5202
|
+
...hasSecret ? { secretStorage: preserveSecretReferences ? "env-ref" : "materialized" } : {},
|
|
5203
|
+
...preserveSecretReferences && secretKeys.size > 0 ? { secretKeys: [...secretKeys].sort() } : {},
|
|
5204
|
+
...preserveSecretReferences && secretEnv.size > 0 ? { secretEnv: [...secretEnv].sort() } : {}
|
|
5205
|
+
};
|
|
5179
5206
|
}
|
|
5180
5207
|
|
|
5181
5208
|
// src/mcp-native-overrides.ts
|
|
@@ -5386,9 +5413,9 @@ var Generator = class {
|
|
|
5386
5413
|
for (const configPath of this.config.passthrough ?? []) {
|
|
5387
5414
|
const src = this.resolveConfigPath(configPath, "passthrough");
|
|
5388
5415
|
if (!existsSync4(src)) continue;
|
|
5389
|
-
const
|
|
5390
|
-
if (!
|
|
5391
|
-
this.copyDir(configPath, `${
|
|
5416
|
+
const basename11 = src.split("/").filter(Boolean).pop();
|
|
5417
|
+
if (!basename11) continue;
|
|
5418
|
+
this.copyDir(configPath, `${basename11}/`, "passthrough");
|
|
5392
5419
|
}
|
|
5393
5420
|
}
|
|
5394
5421
|
/** Build canonical MCP server configs for target-specific output shaping. */
|
|
@@ -5494,6 +5521,18 @@ var CURSOR_SUPPORTED_HOOK_EVENTS = [
|
|
|
5494
5521
|
"beforeTabFileRead",
|
|
5495
5522
|
"afterTabFileEdit"
|
|
5496
5523
|
];
|
|
5524
|
+
var CODEX_SUPPORTED_HOOK_EVENTS = [
|
|
5525
|
+
"SessionStart",
|
|
5526
|
+
"SubagentStart",
|
|
5527
|
+
"PreToolUse",
|
|
5528
|
+
"PermissionRequest",
|
|
5529
|
+
"PostToolUse",
|
|
5530
|
+
"PreCompact",
|
|
5531
|
+
"PostCompact",
|
|
5532
|
+
"UserPromptSubmit",
|
|
5533
|
+
"SubagentStop",
|
|
5534
|
+
"Stop"
|
|
5535
|
+
];
|
|
5497
5536
|
var PASCAL_CASE_HOOK_ALIASES = {
|
|
5498
5537
|
beforeSubmitPrompt: "UserPromptSubmit"
|
|
5499
5538
|
};
|
|
@@ -5540,8 +5579,8 @@ var HOOK_PLATFORM_REGISTRY = {
|
|
|
5540
5579
|
},
|
|
5541
5580
|
codex: {
|
|
5542
5581
|
supportedTypes: ["command"],
|
|
5543
|
-
supportedEvents:
|
|
5544
|
-
unsupportedEventReason:
|
|
5582
|
+
supportedEvents: CODEX_SUPPORTED_HOOK_EVENTS,
|
|
5583
|
+
unsupportedEventReason: `Codex currently documents only ${CODEX_SUPPORTED_HOOK_EVENTS.join(", ")} for hook configuration.`,
|
|
5545
5584
|
fields: {
|
|
5546
5585
|
prompt: { mode: "drop" },
|
|
5547
5586
|
matcher: {
|
|
@@ -5838,8 +5877,16 @@ function loadUserEnv(pluginRoot) {
|
|
|
5838
5877
|
const env = parsed && typeof parsed === "object" && parsed.env && typeof parsed.env === "object"
|
|
5839
5878
|
? parsed.env
|
|
5840
5879
|
: {}
|
|
5880
|
+
const envRefs = parsed && typeof parsed === "object" && parsed.envRefs && typeof parsed.envRefs === "object"
|
|
5881
|
+
? parsed.envRefs
|
|
5882
|
+
: {}
|
|
5841
5883
|
return Object.fromEntries(
|
|
5842
|
-
|
|
5884
|
+
[
|
|
5885
|
+
...Object.entries(env),
|
|
5886
|
+
...Object.entries(envRefs)
|
|
5887
|
+
.filter(([, value]) => typeof value === "string" && value in process.env)
|
|
5888
|
+
.map(([key, value]) => [key, process.env[value]]),
|
|
5889
|
+
].filter(([key, value]) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(key) && typeof value === "string"),
|
|
5843
5890
|
)
|
|
5844
5891
|
} catch {
|
|
5845
5892
|
return {}
|
|
@@ -6077,7 +6124,7 @@ function getEnabledRuntimeReadinessBindings(capability, plan) {
|
|
|
6077
6124
|
});
|
|
6078
6125
|
}
|
|
6079
6126
|
var NAMED_PROMPT_TARGET_NOTE = "Named `skills` / `commands` readiness targets currently translate through prompt-entry gating with best-effort matching because the core four do not share one exact per-skill or per-command runtime interception surface.";
|
|
6080
|
-
var CODEX_EXTERNAL_NOTE = "Codex readiness now bundles translated hooks in the plugin, and Pluxx still emits `.codex/readiness.generated.json` plus `.codex/hooks.generated.json` as debugging companions because
|
|
6127
|
+
var CODEX_EXTERNAL_NOTE = "Codex readiness now bundles translated hooks in the plugin, and Pluxx still emits `.codex/readiness.generated.json` plus `.codex/hooks.generated.json` as debugging companions because hook activation still depends on `[features].hooks = true`, enabled plugin state, review, trust, and runtime support. `codex_hooks` is deprecated and should not be treated as the current hook feature key.";
|
|
6081
6128
|
function getRuntimeReadinessNamedPromptTargetNote() {
|
|
6082
6129
|
return NAMED_PROMPT_TARGET_NOTE;
|
|
6083
6130
|
}
|
|
@@ -6151,18 +6198,18 @@ function getRuntimeReadinessCapability(platform, pluginRootVar = "PLUGIN_ROOT")
|
|
|
6151
6198
|
{
|
|
6152
6199
|
gate: "session-start",
|
|
6153
6200
|
event: "SessionStart",
|
|
6154
|
-
command:
|
|
6201
|
+
command: `node "\${${pluginRootVar}}/.codex/pluxx-readiness.mjs" session-start`
|
|
6155
6202
|
},
|
|
6156
6203
|
{
|
|
6157
6204
|
gate: "mcp-gate",
|
|
6158
6205
|
event: "PreToolUse",
|
|
6159
6206
|
matcher: "MCP",
|
|
6160
|
-
command:
|
|
6207
|
+
command: `node "\${${pluginRootVar}}/.codex/pluxx-readiness.mjs" mcp-gate`
|
|
6161
6208
|
},
|
|
6162
6209
|
{
|
|
6163
6210
|
gate: "prompt-gate",
|
|
6164
6211
|
event: "UserPromptSubmit",
|
|
6165
|
-
command:
|
|
6212
|
+
command: `node "\${${pluginRootVar}}/.codex/pluxx-readiness.mjs" prompt-gate`
|
|
6166
6213
|
}
|
|
6167
6214
|
],
|
|
6168
6215
|
notes: CODEX_EXTERNAL_NOTE
|
|
@@ -6378,6 +6425,9 @@ function shellSingleQuote(value) {
|
|
|
6378
6425
|
return `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
6379
6426
|
}
|
|
6380
6427
|
function buildHookCommandWrapperScript(command2, pluginRootVar, envFileVar) {
|
|
6428
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(pluginRootVar)) {
|
|
6429
|
+
throw new Error(`Invalid plugin root environment variable name: ${pluginRootVar}`);
|
|
6430
|
+
}
|
|
6381
6431
|
const serializedCommand = shellSingleQuote(command2);
|
|
6382
6432
|
const exportLoader = [
|
|
6383
6433
|
'import { readFileSync } from "node:fs"',
|
|
@@ -6390,10 +6440,19 @@ function buildHookCommandWrapperScript(command2, pluginRootVar, envFileVar) {
|
|
|
6390
6440
|
'const env = payload && typeof payload === "object" && payload.env && typeof payload.env === "object"',
|
|
6391
6441
|
" ? payload.env",
|
|
6392
6442
|
" : {}",
|
|
6443
|
+
'const envRefs = payload && typeof payload === "object" && payload.envRefs && typeof payload.envRefs === "object"',
|
|
6444
|
+
" ? payload.envRefs",
|
|
6445
|
+
" : {}",
|
|
6393
6446
|
"",
|
|
6394
6447
|
"for (const [key, value] of Object.entries(env)) {",
|
|
6395
6448
|
" if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) continue",
|
|
6396
6449
|
" process.stdout.write(`export ${key}=${shellSingleQuote(value)}\\0`)",
|
|
6450
|
+
"}",
|
|
6451
|
+
"for (const [key, envVar] of Object.entries(envRefs)) {",
|
|
6452
|
+
" if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) continue",
|
|
6453
|
+
' if (typeof envVar !== "string" || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(envVar)) continue',
|
|
6454
|
+
" if (!(envVar in process.env)) continue",
|
|
6455
|
+
" process.stdout.write(`export ${key}=${shellSingleQuote(process.env[envVar])}\\0`)",
|
|
6397
6456
|
"}"
|
|
6398
6457
|
].join("\n");
|
|
6399
6458
|
const maybeAppendEnvFile = envFileVar ? [
|
|
@@ -6406,6 +6465,8 @@ function buildHookCommandWrapperScript(command2, pluginRootVar, envFileVar) {
|
|
|
6406
6465
|
"set -euo pipefail",
|
|
6407
6466
|
"",
|
|
6408
6467
|
`PLUXX_PLUGIN_ROOT="\${${pluginRootVar}:-$(cd "$(dirname "$0")/.." && pwd)}"`,
|
|
6468
|
+
`export ${pluginRootVar}="$PLUXX_PLUGIN_ROOT"`,
|
|
6469
|
+
'export PLUGIN_ROOT="$PLUXX_PLUGIN_ROOT"',
|
|
6409
6470
|
'PLUXX_USER_CONFIG_PATH="$PLUXX_PLUGIN_ROOT/.pluxx-user.json"',
|
|
6410
6471
|
"",
|
|
6411
6472
|
'if [ -f "$PLUXX_USER_CONFIG_PATH" ]; then',
|
|
@@ -9870,7 +9931,7 @@ function toQuotedTomlKey(value) {
|
|
|
9870
9931
|
// src/codex-hooks-feature.ts
|
|
9871
9932
|
var RECOMMENDED_CODEX_HOOKS_FEATURE_FLAG = "hooks";
|
|
9872
9933
|
var ALTERNATE_CODEX_HOOKS_FEATURE_FLAG = "codex_hooks";
|
|
9873
|
-
var PLUGIN_BUNDLED_CODEX_HOOKS_FEATURE_FLAG = "
|
|
9934
|
+
var PLUGIN_BUNDLED_CODEX_HOOKS_FEATURE_FLAG = "hooks";
|
|
9874
9935
|
function getCodexHooksFeatureState(features) {
|
|
9875
9936
|
if (!features) {
|
|
9876
9937
|
return {
|
|
@@ -10145,7 +10206,7 @@ var CodexGenerator = class extends Generator {
|
|
|
10145
10206
|
}
|
|
10146
10207
|
async generateHooksCompanion() {
|
|
10147
10208
|
const readinessPlan = getRuntimeReadinessPlan(this.config.readiness);
|
|
10148
|
-
const readinessCapability = getRuntimeReadinessCapability("codex");
|
|
10209
|
+
const readinessCapability = getRuntimeReadinessCapability("codex", "CODEX_PLUGIN_ROOT");
|
|
10149
10210
|
if (!this.config.hooks && !readinessPlan.hasReadiness) return;
|
|
10150
10211
|
const hooks = {};
|
|
10151
10212
|
const unsupported = [];
|
|
@@ -10193,12 +10254,12 @@ var CodexGenerator = class extends Generator {
|
|
|
10193
10254
|
const relativePath = `hooks/pluxx-hook-command-${nextWrapperIndex}.sh`;
|
|
10194
10255
|
await this.writeFile(
|
|
10195
10256
|
relativePath,
|
|
10196
|
-
buildHookCommandWrapperScript(entry.command.replace("${PLUGIN_ROOT}", "
|
|
10257
|
+
buildHookCommandWrapperScript(entry.command.replace("${PLUGIN_ROOT}", "${PLUXX_PLUGIN_ROOT}"), "CODEX_PLUGIN_ROOT")
|
|
10197
10258
|
);
|
|
10198
10259
|
const matcher = typeof entry.matcher === "string" && isHookFieldPreserved("codex", "matcher", codexEvent) ? entry.matcher : void 0;
|
|
10199
10260
|
mappedEntries.push(this.buildCodexCommandHookGroup(
|
|
10200
10261
|
codexEvent,
|
|
10201
|
-
`bash
|
|
10262
|
+
`bash "\${CODEX_PLUGIN_ROOT}/${relativePath}"`,
|
|
10202
10263
|
matcher,
|
|
10203
10264
|
entry.timeout
|
|
10204
10265
|
));
|
|
@@ -10220,14 +10281,14 @@ var CodexGenerator = class extends Generator {
|
|
|
10220
10281
|
pluginBundleFeatureFlag: PLUGIN_BUNDLED_CODEX_HOOKS_FEATURE_FLAG,
|
|
10221
10282
|
generalFeatureFlag: RECOMMENDED_CODEX_HOOKS_FEATURE_FLAG,
|
|
10222
10283
|
deprecatedGeneralFeatureFlag: ALTERNATE_CODEX_HOOKS_FEATURE_FLAG,
|
|
10223
|
-
note: "Codex plugin-bundled hook configuration is bundled at hooks/hooks.json in the plugin and currently requires `[features].
|
|
10284
|
+
note: "Codex plugin-bundled hook configuration is bundled at hooks/hooks.json in the plugin and currently requires `[features].hooks = true`, enabled plugin state, review, trust, and runtime support. `codex_hooks` is deprecated and should not be treated as the current hook feature key. This companion mirror preserves the translated native event names, matcher groups, and command handlers while highlighting any dropped events or fields.",
|
|
10224
10285
|
hooks,
|
|
10225
10286
|
...unsupported.length > 0 ? { unsupported } : {}
|
|
10226
10287
|
});
|
|
10227
10288
|
}
|
|
10228
10289
|
async generateReadinessCompanion() {
|
|
10229
10290
|
const readinessPlan = getRuntimeReadinessPlan(this.config.readiness);
|
|
10230
|
-
const readinessCapability = getRuntimeReadinessCapability("codex");
|
|
10291
|
+
const readinessCapability = getRuntimeReadinessCapability("codex", "CODEX_PLUGIN_ROOT");
|
|
10231
10292
|
if (!readinessPlan.hasReadiness || !this.config.readiness) return;
|
|
10232
10293
|
const translatedHooks = Object.fromEntries(
|
|
10233
10294
|
getEnabledRuntimeReadinessBindings(readinessCapability, readinessPlan).map((binding) => [
|
|
@@ -10429,7 +10490,7 @@ var OpenCodeGenerator = class extends Generator {
|
|
|
10429
10490
|
`const isMcpTool = (tool: string): boolean =>`,
|
|
10430
10491
|
` tool === "mcp" || tool.startsWith("mcp.") || tool.startsWith("mcp_")`,
|
|
10431
10492
|
"",
|
|
10432
|
-
`const loadUserConfig = (directory: string): { values?: Record<string, string | number | boolean>; env?: Record<string, string> } => {`,
|
|
10493
|
+
`const loadUserConfig = (directory: string): { values?: Record<string, string | number | boolean>; env?: Record<string, string>; envRefs?: Record<string, string> } => {`,
|
|
10433
10494
|
` const filepath = resolve(directory, ".pluxx-user.json")`,
|
|
10434
10495
|
` if (!existsSync(filepath)) return {}`,
|
|
10435
10496
|
` try {`,
|
|
@@ -10439,28 +10500,30 @@ var OpenCodeGenerator = class extends Generator {
|
|
|
10439
10500
|
` }`,
|
|
10440
10501
|
`}`,
|
|
10441
10502
|
"",
|
|
10442
|
-
`const resolveRuntimeValue = (name: string, userEnv: Record<string, string>): string | undefined =>`,
|
|
10443
|
-
` userEnv[name] ?? process.env[name]`,
|
|
10503
|
+
`const resolveRuntimeValue = (name: string, userEnv: Record<string, string>, userEnvRefs: Record<string, string>): string | undefined =>`,
|
|
10504
|
+
` userEnv[name] ?? (userEnvRefs[name] ? process.env[userEnvRefs[name]] : undefined) ?? process.env[name]`,
|
|
10444
10505
|
"",
|
|
10445
|
-
`const materializeEnv = (input: Record<string, string> | undefined, userEnv: Record<string, string>): Record<string, string> | undefined => {`,
|
|
10506
|
+
`const materializeEnv = (input: Record<string, string> | undefined, userEnv: Record<string, string>, userEnvRefs: Record<string, string>): Record<string, string> | undefined => {`,
|
|
10446
10507
|
` if (!input) return undefined`,
|
|
10447
10508
|
` const output: Record<string, string> = {}`,
|
|
10448
10509
|
` for (const [key, value] of Object.entries(input)) {`,
|
|
10449
|
-
` output[key] = value.replace(/\\$\\{([A-Za-z_][A-Za-z0-9_]*)\\}/g, (_match, name) => resolveRuntimeValue(name, userEnv) ?? \`\\\${\${name}}\`)`,
|
|
10510
|
+
` output[key] = value.replace(/\\$\\{([A-Za-z_][A-Za-z0-9_]*)\\}/g, (_match, name) => resolveRuntimeValue(name, userEnv, userEnvRefs) ?? \`\\\${\${name}}\`)`,
|
|
10450
10511
|
` }`,
|
|
10451
10512
|
` return output`,
|
|
10452
10513
|
`}`,
|
|
10453
10514
|
"",
|
|
10454
10515
|
`const buildMcpConfig = (directory: string): NonNullable<Config["mcp"]> => {`,
|
|
10455
10516
|
` const config: NonNullable<Config["mcp"]> = {}`,
|
|
10456
|
-
` const
|
|
10517
|
+
` const userConfig = loadUserConfig(directory)`,
|
|
10518
|
+
` const userEnv = userConfig.env ?? {}`,
|
|
10519
|
+
` const userEnvRefs = userConfig.envRefs ?? {}`,
|
|
10457
10520
|
"",
|
|
10458
10521
|
` for (const [name, definition] of Object.entries(MCP_DEFINITIONS)) {`,
|
|
10459
10522
|
` if (definition.transport === "stdio" && definition.command) {`,
|
|
10460
10523
|
` config[name] = {`,
|
|
10461
10524
|
` type: "local",`,
|
|
10462
10525
|
` command: [definition.command, ...(definition.args ?? [])],`,
|
|
10463
|
-
` ...(definition.env ? { environment: materializeEnv(definition.env, userEnv) } : {}),`,
|
|
10526
|
+
` ...(definition.env ? { environment: materializeEnv(definition.env, userEnv, userEnvRefs) } : {}),`,
|
|
10464
10527
|
` }`,
|
|
10465
10528
|
` continue`,
|
|
10466
10529
|
` }`,
|
|
@@ -10477,12 +10540,12 @@ var OpenCodeGenerator = class extends Generator {
|
|
|
10477
10540
|
` }`,
|
|
10478
10541
|
"",
|
|
10479
10542
|
` if (definition.auth?.type === "bearer" && definition.auth.envVar) {`,
|
|
10480
|
-
` const token = resolveRuntimeValue(definition.auth.envVar, userEnv)`,
|
|
10543
|
+
` const token = resolveRuntimeValue(definition.auth.envVar, userEnv, userEnvRefs)`,
|
|
10481
10544
|
` if (token) remote.headers = { Authorization: \`Bearer \${token}\` }`,
|
|
10482
10545
|
` }`,
|
|
10483
10546
|
"",
|
|
10484
10547
|
` if (definition.auth?.type === "header" && definition.auth.envVar && definition.auth.headerName && definition.auth.headerTemplate) {`,
|
|
10485
|
-
` const value = resolveRuntimeValue(definition.auth.envVar, userEnv)`,
|
|
10548
|
+
` const value = resolveRuntimeValue(definition.auth.envVar, userEnv, userEnvRefs)`,
|
|
10486
10549
|
` if (value) {`,
|
|
10487
10550
|
` remote.headers = {`,
|
|
10488
10551
|
` ...(remote.headers ?? {}),`,
|
|
@@ -10512,10 +10575,15 @@ var OpenCodeGenerator = class extends Generator {
|
|
|
10512
10575
|
` const shellSingleQuote = (input: string): string => \`'\${String(input ?? "").replace(/'/g, \`'"'"'\`)}'\``,
|
|
10513
10576
|
"",
|
|
10514
10577
|
` const buildHookShellCommand = (rawCommand: string): string => {`,
|
|
10515
|
-
` const
|
|
10578
|
+
` const userConfig = loadUserConfig(directory)`,
|
|
10579
|
+
` const userEnv = userConfig.env ?? {}`,
|
|
10580
|
+
` const userEnvRefs = userConfig.envRefs ?? {}`,
|
|
10516
10581
|
` const exports = Object.entries(userEnv)`,
|
|
10517
10582
|
` .filter(([key]) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(key))`,
|
|
10518
10583
|
` .map(([key, value]) => \`export \${key}=\${shellSingleQuote(String(value))}\`)`,
|
|
10584
|
+
` .concat(Object.entries(userEnvRefs)`,
|
|
10585
|
+
` .filter(([key, envVar]) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(key) && typeof envVar === "string" && envVar in process.env)`,
|
|
10586
|
+
` .map(([key, envVar]) => \`export \${key}=\${shellSingleQuote(String(process.env[envVar]))}\`))`,
|
|
10519
10587
|
` .join("; ")`,
|
|
10520
10588
|
` const command = rawCommand.replaceAll("\${PLUGIN_ROOT}", directory)`,
|
|
10521
10589
|
` return exports ? \`\${exports}; \${command}\` : command`,
|
|
@@ -11313,6 +11381,7 @@ import { existsSync as existsSync18, lstatSync as lstatSync2, readdirSync as rea
|
|
|
11313
11381
|
import { resolve as resolve14, relative as relative8, basename as basename5, dirname as dirname4 } from "path";
|
|
11314
11382
|
|
|
11315
11383
|
// src/validation/platform-rules.ts
|
|
11384
|
+
var CORE_FOUR_PLATFORMS = ["claude-code", "cursor", "codex", "opencode"];
|
|
11316
11385
|
var STANDARD_SKILL_FRONTMATTER = [
|
|
11317
11386
|
"name",
|
|
11318
11387
|
"description",
|
|
@@ -11441,7 +11510,7 @@ var PLATFORM_LIMIT_POLICIES = {
|
|
|
11441
11510
|
},
|
|
11442
11511
|
hooksFeatureFlag: {
|
|
11443
11512
|
kind: "hard",
|
|
11444
|
-
notes: "
|
|
11513
|
+
notes: "Codex hook support depends on the canonical hooks feature flag plus enabled-plugin, trust/review, and runtime support."
|
|
11445
11514
|
}
|
|
11446
11515
|
},
|
|
11447
11516
|
"cursor": {
|
|
@@ -11641,8 +11710,8 @@ var PLATFORM_VALIDATION_RULES = {
|
|
|
11641
11710
|
hooks: {
|
|
11642
11711
|
supported: true,
|
|
11643
11712
|
files: ["hooks/hooks.json", ".codex/hooks.json", "~/.codex/hooks.json"],
|
|
11644
|
-
eventNames: [
|
|
11645
|
-
notes: "Codex documents both project
|
|
11713
|
+
eventNames: [...CODEX_SUPPORTED_HOOK_EVENTS],
|
|
11714
|
+
notes: "Codex documents both project/user hook config and plugin-bundled hooks. Plugin-bundled hooks live at `hooks/hooks.json` in the plugin and require the canonical `[features].hooks = true` flag, enabled plugin state, user review, trust, and runtime support. `codex_hooks` is deprecated and should not be treated as the current hook feature key."
|
|
11646
11715
|
},
|
|
11647
11716
|
instructions: {
|
|
11648
11717
|
files: ["AGENTS.md", "AGENTS.override.md"],
|
|
@@ -12081,7 +12150,7 @@ var CORE_FOUR_PRIMITIVE_CAPABILITIES = {
|
|
|
12081
12150
|
hooks: {
|
|
12082
12151
|
mode: "translate",
|
|
12083
12152
|
nativeSurfaces: ["hooks/hooks.json", ".codex/hooks.json", "~/.codex/hooks.json"],
|
|
12084
|
-
notes: "Hooks are native. Pluxx bundles translated Codex hooks in the plugin with the documented `[features].
|
|
12153
|
+
notes: "Hooks are native. Pluxx bundles translated Codex hooks in the plugin with the documented `[features].hooks = true` feature key, and still tracks broader project/user hook config paths where `codex_hooks` is deprecated. Plugin-bundled execution still also depends on enabled plugin state, review, trust, and runtime support."
|
|
12085
12154
|
},
|
|
12086
12155
|
permissions: {
|
|
12087
12156
|
mode: "translate",
|
|
@@ -12152,7 +12221,6 @@ function getCoreFourPrimitiveCapabilities(platform) {
|
|
|
12152
12221
|
}
|
|
12153
12222
|
|
|
12154
12223
|
// src/cli/primitive-summary.ts
|
|
12155
|
-
var CORE_FOUR_ORDER = ["claude-code", "cursor", "codex", "opencode"];
|
|
12156
12224
|
var TARGET_LABELS = {
|
|
12157
12225
|
"claude-code": "claude",
|
|
12158
12226
|
cursor: "cursor",
|
|
@@ -12166,7 +12234,7 @@ var MODE_LABELS = {
|
|
|
12166
12234
|
drop: "drop"
|
|
12167
12235
|
};
|
|
12168
12236
|
function buildPrimitiveTranslationSummary(config, targets = config.targets) {
|
|
12169
|
-
const selectedTargets =
|
|
12237
|
+
const selectedTargets = CORE_FOUR_PLATFORMS.filter((target) => targets.includes(target));
|
|
12170
12238
|
if (selectedTargets.length === 0) return void 0;
|
|
12171
12239
|
const configuredBuckets = getConfiguredCompilerBuckets({
|
|
12172
12240
|
...config,
|
|
@@ -13356,7 +13424,7 @@ function lintCodexHooksExternalConfig(config, issues) {
|
|
|
13356
13424
|
pushIssue(issues, {
|
|
13357
13425
|
level: "warning",
|
|
13358
13426
|
code: "codex-hooks-external-config",
|
|
13359
|
-
message: `Pluxx now bundles Codex hooks at \`hooks/hooks.json\`, and Codex
|
|
13427
|
+
message: `Pluxx now bundles Codex hooks at \`hooks/hooks.json\`, and current Codex docs use \`${RECOMMENDED_CODEX_HOOKS_FEATURE_FLAG} = true\` under \`[features]\` as the canonical hook feature key. \`${ALTERNATE_CODEX_HOOKS_FEATURE_FLAG}\` is deprecated and should not be treated as the current hook feature key. If bundled hooks do not activate, enable \`${RECOMMENDED_CODEX_HOOKS_FEATURE_FLAG} = true\`, reload Codex, and retest in a trusted interactive session.`,
|
|
13360
13428
|
file: "pluxx.config.ts",
|
|
13361
13429
|
platform: "Codex"
|
|
13362
13430
|
});
|
|
@@ -13365,7 +13433,7 @@ function lintCodexHooksExternalConfig(config, issues) {
|
|
|
13365
13433
|
pushIssue(issues, {
|
|
13366
13434
|
level: "warning",
|
|
13367
13435
|
code: "codex-hooks-legacy-feature-flag",
|
|
13368
|
-
message: `\`${ALTERNATE_CODEX_HOOKS_FEATURE_FLAG}\` is deprecated for
|
|
13436
|
+
message: `\`${ALTERNATE_CODEX_HOOKS_FEATURE_FLAG}\` is deprecated for Codex hook config. Prefer \`${RECOMMENDED_CODEX_HOOKS_FEATURE_FLAG} = true\` under \`[features]\`.`,
|
|
13369
13437
|
file: "pluxx.config.ts",
|
|
13370
13438
|
platform: "Codex"
|
|
13371
13439
|
});
|
|
@@ -13374,7 +13442,7 @@ function lintCodexHooksExternalConfig(config, issues) {
|
|
|
13374
13442
|
pushIssue(issues, {
|
|
13375
13443
|
level: "warning",
|
|
13376
13444
|
code: "codex-hooks-general-feature-flag-only",
|
|
13377
|
-
message: `This Codex config enables only
|
|
13445
|
+
message: `This Codex config enables only deprecated hook flags. Plugin-bundled hooks at \`hooks/hooks.json\` should use \`${RECOMMENDED_CODEX_HOOKS_FEATURE_FLAG} = true\` under \`[features]\`.`,
|
|
13378
13446
|
file: "pluxx.config.ts",
|
|
13379
13447
|
platform: "Codex"
|
|
13380
13448
|
});
|
|
@@ -17987,10 +18055,10 @@ ${additions.map((block) => `- ${block.replace(/\n/g, "\n ")}`).join("\n")}
|
|
|
17987
18055
|
import { spawn as spawn3, spawnSync as spawnSync2 } from "child_process";
|
|
17988
18056
|
import { accessSync, constants, existsSync as existsSync24, lstatSync as lstatSync3, readFileSync as readFileSync14, readdirSync as readdirSync10, realpathSync } from "fs";
|
|
17989
18057
|
import { homedir as homedir2 } from "os";
|
|
17990
|
-
import { basename as
|
|
18058
|
+
import { basename as basename7, dirname as dirname7, relative as relative12, resolve as resolve20 } from "path";
|
|
17991
18059
|
|
|
17992
18060
|
// src/cli/install.ts
|
|
17993
|
-
import { resolve as resolve19, dirname as dirname6 } from "path";
|
|
18061
|
+
import { resolve as resolve19, dirname as dirname6, basename as basename6, relative as relative11 } from "path";
|
|
17994
18062
|
import { existsSync as existsSync23, symlinkSync, mkdirSync as mkdirSync4, rmSync as rmSync3, readFileSync as readFileSync13, writeFileSync as writeFileSync4, cpSync as cpSync3, readdirSync as readdirSync9 } from "fs";
|
|
17995
18063
|
import { spawnSync } from "child_process";
|
|
17996
18064
|
import * as readline2 from "readline";
|
|
@@ -18474,19 +18542,25 @@ function createOpenCodeCopiedInstall(target, pluginName) {
|
|
|
18474
18542
|
writeOpenCodeEntryFile(target.pluginDir, pluginName);
|
|
18475
18543
|
syncOpenCodeSkills(target.pluginDir, pluginName);
|
|
18476
18544
|
}
|
|
18477
|
-
function materializeTemplateValue(value, env) {
|
|
18478
|
-
return value.replace(
|
|
18545
|
+
function materializeTemplateValue(value, env, secretEnvVars = /* @__PURE__ */ new Set()) {
|
|
18546
|
+
return value.replace(
|
|
18547
|
+
/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g,
|
|
18548
|
+
(_match, name) => secretEnvVars.has(name) ? `\${${name}}` : env[name] ?? `\${${name}}`
|
|
18549
|
+
);
|
|
18479
18550
|
}
|
|
18480
|
-
function materializeEnvRecord(input, env) {
|
|
18551
|
+
function materializeEnvRecord(input, env, secretEnvVars = /* @__PURE__ */ new Set()) {
|
|
18481
18552
|
const output = {};
|
|
18482
18553
|
for (const [key, value] of Object.entries(input ?? {})) {
|
|
18483
|
-
output[key] = materializeTemplateValue(value, env);
|
|
18554
|
+
output[key] = materializeTemplateValue(value, env, secretEnvVars);
|
|
18484
18555
|
}
|
|
18485
18556
|
return output;
|
|
18486
18557
|
}
|
|
18487
18558
|
function patchInstalledMcpConfig(pluginDir, platform, config, entries) {
|
|
18488
18559
|
if (!config.mcp) return;
|
|
18489
18560
|
const env = buildUserConfigEnvMap(entries);
|
|
18561
|
+
const secretEnvVars = new Set(
|
|
18562
|
+
entries.filter((entry) => entry.field.type === "secret" && entry.envVar).map((entry) => entry.envVar)
|
|
18563
|
+
);
|
|
18490
18564
|
if (platform === "claude-code" || platform === "cursor") {
|
|
18491
18565
|
const filepath = resolve19(pluginDir, platform === "claude-code" ? ".mcp.json" : "mcp.json");
|
|
18492
18566
|
if (!existsSync23(filepath)) return;
|
|
@@ -18531,7 +18605,7 @@ function patchInstalledMcpConfig(pluginDir, platform, config, entries) {
|
|
|
18531
18605
|
mcpServers[name] = {
|
|
18532
18606
|
command: materializeInstalledPluginOwnedStdioPathForPlatform(server.command, platform, pluginDir),
|
|
18533
18607
|
args: (server.args ?? []).map((value) => materializeInstalledPluginOwnedStdioPathForPlatform(value, platform, pluginDir)),
|
|
18534
|
-
env: materializeEnvRecord(server.env, env)
|
|
18608
|
+
env: materializeEnvRecord(server.env, env, secretEnvVars)
|
|
18535
18609
|
};
|
|
18536
18610
|
continue;
|
|
18537
18611
|
}
|
|
@@ -18541,49 +18615,46 @@ function patchInstalledMcpConfig(pluginDir, platform, config, entries) {
|
|
|
18541
18615
|
const nativeOverride = getNativeCodexMcpEntryOverride(config, name);
|
|
18542
18616
|
if (nativeOverride) {
|
|
18543
18617
|
if (typeof nativeOverride.bearer_token_env_var === "string") {
|
|
18544
|
-
|
|
18545
|
-
if (env[envVar]) {
|
|
18546
|
-
entry.bearer_token_env_var = envVar;
|
|
18547
|
-
}
|
|
18618
|
+
entry.bearer_token_env_var = nativeOverride.bearer_token_env_var;
|
|
18548
18619
|
}
|
|
18549
18620
|
if (nativeOverride.auth) {
|
|
18550
18621
|
entry.auth = nativeOverride.auth;
|
|
18551
18622
|
}
|
|
18552
18623
|
if (nativeOverride.env_http_headers && typeof nativeOverride.env_http_headers === "object") {
|
|
18553
|
-
entry.
|
|
18554
|
-
Object.entries(nativeOverride.env_http_headers).filter(([, value]) => typeof value === "string").map(([headerName, envVar]) => [
|
|
18555
|
-
headerName,
|
|
18556
|
-
env[envVar] ?? `\${${envVar}}`
|
|
18557
|
-
])
|
|
18624
|
+
entry.env_http_headers = Object.fromEntries(
|
|
18625
|
+
Object.entries(nativeOverride.env_http_headers).filter(([, value]) => typeof value === "string").map(([headerName, envVar]) => [headerName, envVar])
|
|
18558
18626
|
);
|
|
18559
18627
|
}
|
|
18560
18628
|
if (nativeOverride.http_headers && typeof nativeOverride.http_headers === "object") {
|
|
18561
18629
|
entry.http_headers = materializeEnvRecord(
|
|
18562
18630
|
nativeOverride.http_headers,
|
|
18563
|
-
env
|
|
18631
|
+
env,
|
|
18632
|
+
secretEnvVars
|
|
18564
18633
|
);
|
|
18565
18634
|
}
|
|
18566
|
-
} else if (server.auth?.type === "bearer" && server.auth.envVar
|
|
18567
|
-
entry.
|
|
18568
|
-
|
|
18569
|
-
};
|
|
18570
|
-
|
|
18571
|
-
|
|
18572
|
-
|
|
18573
|
-
|
|
18635
|
+
} else if (server.auth?.type === "bearer" && server.auth.envVar) {
|
|
18636
|
+
entry.bearer_token_env_var = server.auth.envVar;
|
|
18637
|
+
} else if (server.auth?.type === "header" && server.auth.envVar) {
|
|
18638
|
+
const isBearerAuthorizationHeader = server.auth.headerName === "Authorization" && server.auth.headerTemplate === "Bearer ${value}";
|
|
18639
|
+
if (isBearerAuthorizationHeader) {
|
|
18640
|
+
entry.bearer_token_env_var = server.auth.envVar;
|
|
18641
|
+
} else if (server.auth.headerTemplate === "${value}") {
|
|
18642
|
+
entry.env_http_headers = {
|
|
18643
|
+
[server.auth.headerName]: server.auth.envVar
|
|
18644
|
+
};
|
|
18645
|
+
}
|
|
18574
18646
|
}
|
|
18575
18647
|
mcpServers[name] = entry;
|
|
18576
18648
|
}
|
|
18577
18649
|
writeFileSync4(filepath, JSON.stringify({ mcpServers }, null, 2) + "\n");
|
|
18578
18650
|
}
|
|
18579
18651
|
}
|
|
18580
|
-
function writeInstalledUserConfig(pluginDir, entries) {
|
|
18652
|
+
function writeInstalledUserConfig(pluginDir, entries, platform) {
|
|
18581
18653
|
if (entries.length === 0) return;
|
|
18582
18654
|
const filepath = resolve19(pluginDir, ".pluxx-user.json");
|
|
18583
|
-
const payload = {
|
|
18584
|
-
|
|
18585
|
-
|
|
18586
|
-
};
|
|
18655
|
+
const payload = buildInstalledUserConfigPayload(entries, {
|
|
18656
|
+
preserveSecretReferences: platform === "codex"
|
|
18657
|
+
});
|
|
18587
18658
|
writeFileSync4(filepath, JSON.stringify(payload, null, 2) + "\n");
|
|
18588
18659
|
}
|
|
18589
18660
|
function disableInstalledEnvValidation(pluginDir, entries) {
|
|
@@ -18597,7 +18668,7 @@ function disableInstalledEnvValidation(pluginDir, entries) {
|
|
|
18597
18668
|
}
|
|
18598
18669
|
function materializeInstalledPlugin(pluginDir, platform, config, entries) {
|
|
18599
18670
|
if (entries.length > 0) {
|
|
18600
|
-
writeInstalledUserConfig(pluginDir, entries);
|
|
18671
|
+
writeInstalledUserConfig(pluginDir, entries, platform);
|
|
18601
18672
|
disableInstalledEnvValidation(pluginDir, entries);
|
|
18602
18673
|
}
|
|
18603
18674
|
patchInstalledMcpConfig(pluginDir, platform, config, entries);
|
|
@@ -18676,7 +18747,7 @@ function resolveBundleReference(rootDir, value) {
|
|
|
18676
18747
|
if (isRelativeBundlePath(value)) {
|
|
18677
18748
|
return resolve19(rootDir, value);
|
|
18678
18749
|
}
|
|
18679
|
-
const pluginRootMatch = value.match(/^\$\{(?:CLAUDE_PLUGIN_ROOT|CURSOR_PLUGIN_ROOT|PLUGIN_ROOT)\}[\\/](.+)$/);
|
|
18750
|
+
const pluginRootMatch = value.match(/^\$\{(?:CLAUDE_PLUGIN_ROOT|CURSOR_PLUGIN_ROOT|CODEX_PLUGIN_ROOT|OPENCODE_PLUGIN_ROOT|PLUGIN_ROOT|PLUXX_PLUGIN_ROOT)\}[\\/](.+)$/);
|
|
18680
18751
|
if (pluginRootMatch) {
|
|
18681
18752
|
return resolve19(rootDir, pluginRootMatch[1]);
|
|
18682
18753
|
}
|
|
@@ -18719,9 +18790,43 @@ function collectHookCommandStrings(value, commands) {
|
|
|
18719
18790
|
}
|
|
18720
18791
|
}
|
|
18721
18792
|
function extractBundleCommandTargets(command2) {
|
|
18722
|
-
const matches = command2.match(/\$\{(?:CLAUDE_PLUGIN_ROOT|CURSOR_PLUGIN_ROOT|PLUGIN_ROOT)\}[\\/][^\s"'`;$|&<>]+|\.\.?[\\/][^\s"'`;$|&<>]+/g);
|
|
18793
|
+
const matches = command2.match(/\$\{(?:CLAUDE_PLUGIN_ROOT|CURSOR_PLUGIN_ROOT|CODEX_PLUGIN_ROOT|OPENCODE_PLUGIN_ROOT|PLUGIN_ROOT|PLUXX_PLUGIN_ROOT)\}[\\/][^\s"'`;$|&<>]+|\.\.?[\\/][^\s"'`;$|&<>]+/g);
|
|
18723
18794
|
return matches ?? [];
|
|
18724
18795
|
}
|
|
18796
|
+
function extractRelativeBundleCommandTargets(command2) {
|
|
18797
|
+
return extractBundleCommandTargets(command2).filter(isRelativeBundlePath);
|
|
18798
|
+
}
|
|
18799
|
+
function commandChangesToKnownPluginRoot(command2) {
|
|
18800
|
+
return /\bcd\s+["']?\$\{?(?:CLAUDE_PLUGIN_ROOT|CURSOR_PLUGIN_ROOT|CODEX_PLUGIN_ROOT|OPENCODE_PLUGIN_ROOT|PLUGIN_ROOT|PLUXX_PLUGIN_ROOT)\}?/.test(command2);
|
|
18801
|
+
}
|
|
18802
|
+
function formatBundleRelativePath(rootDir, filePath) {
|
|
18803
|
+
const relativePath = relative11(rootDir, filePath);
|
|
18804
|
+
return relativePath && !relativePath.startsWith("..") ? relativePath : filePath;
|
|
18805
|
+
}
|
|
18806
|
+
function findHookWrapperPaths(rootDir, command2) {
|
|
18807
|
+
return extractBundleCommandTargets(command2).map((target) => resolveBundleReference(rootDir, target)).filter((target) => {
|
|
18808
|
+
if (!target) return false;
|
|
18809
|
+
return existsSync23(target) && basename6(target).startsWith("pluxx-hook-command-") && target.endsWith(".sh");
|
|
18810
|
+
});
|
|
18811
|
+
}
|
|
18812
|
+
function findCodexCwdUnsafeHookCommands(rootDir, commands) {
|
|
18813
|
+
const issues = /* @__PURE__ */ new Set();
|
|
18814
|
+
for (const command2 of commands) {
|
|
18815
|
+
const relativeTargets = extractRelativeBundleCommandTargets(command2);
|
|
18816
|
+
if (relativeTargets.length > 0 && !commandChangesToKnownPluginRoot(command2)) {
|
|
18817
|
+
issues.add(`${command2} uses cwd-relative bundle target(s): ${relativeTargets.join(", ")}`);
|
|
18818
|
+
}
|
|
18819
|
+
for (const wrapperPath of findHookWrapperPaths(rootDir, command2)) {
|
|
18820
|
+
const wrapper = readFileSync13(wrapperPath, "utf-8");
|
|
18821
|
+
const hookCommand = wrapper.match(/^PLUXX_HOOK_COMMAND=(.*)$/m)?.[1] ?? "";
|
|
18822
|
+
const wrapperRelativeTargets = extractRelativeBundleCommandTargets(hookCommand);
|
|
18823
|
+
if (wrapperRelativeTargets.length > 0 && !commandChangesToKnownPluginRoot(wrapper)) {
|
|
18824
|
+
issues.add(`${formatBundleRelativePath(rootDir, wrapperPath)} evaluates cwd-relative bundle target(s): ${wrapperRelativeTargets.join(", ")}`);
|
|
18825
|
+
}
|
|
18826
|
+
}
|
|
18827
|
+
}
|
|
18828
|
+
return [...issues].sort();
|
|
18829
|
+
}
|
|
18725
18830
|
function resolveInstalledHooksReference(rootDir, platform, manifest) {
|
|
18726
18831
|
const manifestReference = typeof manifest.hooks === "string" ? manifest.hooks : void 0;
|
|
18727
18832
|
if (manifestReference) {
|
|
@@ -18759,6 +18864,7 @@ function findInstalledBundleIntegrityIssues(rootDir, platform) {
|
|
|
18759
18864
|
return {
|
|
18760
18865
|
missingManifestPaths: [],
|
|
18761
18866
|
missingHookTargets: [],
|
|
18867
|
+
cwdUnsafeHookCommands: [],
|
|
18762
18868
|
invalidRuntimeScripts: []
|
|
18763
18869
|
};
|
|
18764
18870
|
}
|
|
@@ -18768,6 +18874,7 @@ function findInstalledBundleIntegrityIssues(rootDir, platform) {
|
|
|
18768
18874
|
manifestIssue: `missing plugin manifest at ${manifestPath}`,
|
|
18769
18875
|
missingManifestPaths: [],
|
|
18770
18876
|
missingHookTargets: [],
|
|
18877
|
+
cwdUnsafeHookCommands: [],
|
|
18771
18878
|
invalidRuntimeScripts: []
|
|
18772
18879
|
};
|
|
18773
18880
|
}
|
|
@@ -18779,6 +18886,7 @@ function findInstalledBundleIntegrityIssues(rootDir, platform) {
|
|
|
18779
18886
|
manifestIssue: `plugin manifest at ${manifestPath} is not parseable: ${error instanceof Error ? error.message : String(error)}`,
|
|
18780
18887
|
missingManifestPaths: [],
|
|
18781
18888
|
missingHookTargets: [],
|
|
18889
|
+
cwdUnsafeHookCommands: [],
|
|
18782
18890
|
invalidRuntimeScripts: []
|
|
18783
18891
|
};
|
|
18784
18892
|
}
|
|
@@ -18793,6 +18901,7 @@ function findInstalledBundleIntegrityIssues(rootDir, platform) {
|
|
|
18793
18901
|
...manifestIssue ? { manifestIssue } : {},
|
|
18794
18902
|
missingManifestPaths,
|
|
18795
18903
|
missingHookTargets: [],
|
|
18904
|
+
cwdUnsafeHookCommands: [],
|
|
18796
18905
|
invalidRuntimeScripts: findInstalledRuntimeScriptIssues(rootDir, manifest)
|
|
18797
18906
|
};
|
|
18798
18907
|
}
|
|
@@ -18801,6 +18910,7 @@ function findInstalledBundleIntegrityIssues(rootDir, platform) {
|
|
|
18801
18910
|
...manifestIssue ? { manifestIssue } : {},
|
|
18802
18911
|
missingManifestPaths,
|
|
18803
18912
|
missingHookTargets: [],
|
|
18913
|
+
cwdUnsafeHookCommands: [],
|
|
18804
18914
|
invalidRuntimeScripts: findInstalledRuntimeScriptIssues(rootDir, manifest)
|
|
18805
18915
|
};
|
|
18806
18916
|
}
|
|
@@ -18814,10 +18924,12 @@ function findInstalledBundleIntegrityIssues(rootDir, platform) {
|
|
|
18814
18924
|
return resolved !== void 0 && !existsSync23(resolved);
|
|
18815
18925
|
})
|
|
18816
18926
|
)].sort();
|
|
18927
|
+
const cwdUnsafeHookCommands = platform === "codex" ? findCodexCwdUnsafeHookCommands(rootDir, commands) : [];
|
|
18817
18928
|
return {
|
|
18818
18929
|
...manifestIssue ? { manifestIssue } : {},
|
|
18819
18930
|
missingManifestPaths,
|
|
18820
18931
|
missingHookTargets,
|
|
18932
|
+
cwdUnsafeHookCommands,
|
|
18821
18933
|
invalidRuntimeScripts: findInstalledRuntimeScriptIssues(rootDir, manifest)
|
|
18822
18934
|
};
|
|
18823
18935
|
} catch (error) {
|
|
@@ -18826,6 +18938,7 @@ function findInstalledBundleIntegrityIssues(rootDir, platform) {
|
|
|
18826
18938
|
hookConfigIssue: `hooks config at ${hooksReference} is not parseable: ${error instanceof Error ? error.message : String(error)}`,
|
|
18827
18939
|
missingManifestPaths,
|
|
18828
18940
|
missingHookTargets: [],
|
|
18941
|
+
cwdUnsafeHookCommands: [],
|
|
18829
18942
|
invalidRuntimeScripts: findInstalledRuntimeScriptIssues(rootDir, manifest)
|
|
18830
18943
|
};
|
|
18831
18944
|
}
|
|
@@ -18875,6 +18988,9 @@ function assertInstalledBundleIntegrity(rootDir, platform, label) {
|
|
|
18875
18988
|
if (issues.missingHookTargets.length > 0) {
|
|
18876
18989
|
details.push(`hook targets missing: ${issues.missingHookTargets.join(", ")}`);
|
|
18877
18990
|
}
|
|
18991
|
+
if (issues.cwdUnsafeHookCommands.length > 0) {
|
|
18992
|
+
details.push(`hook commands require plugin-root cwd: ${issues.cwdUnsafeHookCommands.join("; ")}`);
|
|
18993
|
+
}
|
|
18878
18994
|
if (issues.invalidRuntimeScripts.length > 0) {
|
|
18879
18995
|
details.push(`runtime script issues: ${issues.invalidRuntimeScripts.join(", ")}`);
|
|
18880
18996
|
}
|
|
@@ -19119,6 +19235,13 @@ var LOW_INFO_DESCRIPTION_PATTERNS = [
|
|
|
19119
19235
|
var MATERIALIZED_ENV_MARKER = "materialized required config";
|
|
19120
19236
|
var MIN_NODE_MAJOR = 18;
|
|
19121
19237
|
var STDIO_LAUNCH_SMOKE_TIMEOUT_MS = 1200;
|
|
19238
|
+
var MAX_SECRET_SCAN_FILE_BYTES = 512 * 1024;
|
|
19239
|
+
var LEGACY_SECRET_IDENTIFIER_PATTERN = /(^|[-_])(api[-_]?key|secret|token|password|credential)([-_]|$)/i;
|
|
19240
|
+
var TEST_SECRET_SENTINELS = [
|
|
19241
|
+
{ pattern: /\bshh-secret\b/i, label: "test secret sentinel" },
|
|
19242
|
+
{ pattern: /\bsecret-key\b/i, label: "test secret sentinel" },
|
|
19243
|
+
{ pattern: /\bliteral-secret-value-that-should-not-copy\b/i, label: "test secret sentinel" }
|
|
19244
|
+
];
|
|
19122
19245
|
var PRIMITIVE_MODE_LEVEL = {
|
|
19123
19246
|
preserve: "success",
|
|
19124
19247
|
translate: "info",
|
|
@@ -19131,6 +19254,106 @@ function renderInstalledPluginRoot(value, rootDir) {
|
|
|
19131
19254
|
function addCheck2(checks, check) {
|
|
19132
19255
|
checks.push(check);
|
|
19133
19256
|
}
|
|
19257
|
+
function looksLegacySecretIdentifier(value) {
|
|
19258
|
+
return LEGACY_SECRET_IDENTIFIER_PATTERN.test(
|
|
19259
|
+
value.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[._/\s]+/g, "-")
|
|
19260
|
+
);
|
|
19261
|
+
}
|
|
19262
|
+
function walkInstalledBundleFiles(rootDir, currentDir = rootDir) {
|
|
19263
|
+
const files = [];
|
|
19264
|
+
for (const entry of readdirSync10(currentDir, { withFileTypes: true })) {
|
|
19265
|
+
const absolutePath = resolve20(currentDir, entry.name);
|
|
19266
|
+
if (entry.isDirectory()) {
|
|
19267
|
+
files.push(...walkInstalledBundleFiles(rootDir, absolutePath));
|
|
19268
|
+
continue;
|
|
19269
|
+
}
|
|
19270
|
+
if (!entry.isFile()) continue;
|
|
19271
|
+
files.push(relative12(rootDir, absolutePath).replace(/\\/g, "/"));
|
|
19272
|
+
}
|
|
19273
|
+
return files.sort();
|
|
19274
|
+
}
|
|
19275
|
+
function readInstalledTextFile(rootDir, relativePath) {
|
|
19276
|
+
const absolutePath = resolve20(rootDir, relativePath);
|
|
19277
|
+
const content = readFileSync14(absolutePath);
|
|
19278
|
+
if (content.length > MAX_SECRET_SCAN_FILE_BYTES) return null;
|
|
19279
|
+
if (content.includes(0)) return null;
|
|
19280
|
+
return content.toString("utf-8");
|
|
19281
|
+
}
|
|
19282
|
+
function collectInstalledPlaintextSecretCandidates(rootDir) {
|
|
19283
|
+
const userConfigPath = resolve20(rootDir, ".pluxx-user.json");
|
|
19284
|
+
if (!existsSync24(userConfigPath)) return [];
|
|
19285
|
+
try {
|
|
19286
|
+
const payload = JSON.parse(readFileSync14(userConfigPath, "utf-8"));
|
|
19287
|
+
if (payload.secretStorage === "materialized") return [];
|
|
19288
|
+
const secretKeys = new Set(Array.isArray(payload.secretKeys) ? payload.secretKeys.filter((value) => typeof value === "string") : []);
|
|
19289
|
+
const secretEnv = new Set(Array.isArray(payload.secretEnv) ? payload.secretEnv.filter((value) => typeof value === "string") : []);
|
|
19290
|
+
const hasSecretMetadata = secretKeys.size > 0 || secretEnv.size > 0;
|
|
19291
|
+
const candidateLabels = /* @__PURE__ */ new Map();
|
|
19292
|
+
const recordCandidate = (rawValue, label) => {
|
|
19293
|
+
if (typeof rawValue !== "string") return;
|
|
19294
|
+
const value = rawValue.trim();
|
|
19295
|
+
if (!value) return;
|
|
19296
|
+
if (extractEnvReference(value)) return;
|
|
19297
|
+
if (isPlaceholderSecretValue(value)) return;
|
|
19298
|
+
const labels = candidateLabels.get(value) ?? /* @__PURE__ */ new Set();
|
|
19299
|
+
labels.add(label);
|
|
19300
|
+
candidateLabels.set(value, labels);
|
|
19301
|
+
};
|
|
19302
|
+
for (const [key, value] of Object.entries(payload.values ?? {})) {
|
|
19303
|
+
if (hasSecretMetadata ? !secretKeys.has(key) : !looksLegacySecretIdentifier(key)) continue;
|
|
19304
|
+
recordCandidate(value, `userConfig "${key}"`);
|
|
19305
|
+
}
|
|
19306
|
+
for (const [key, value] of Object.entries(payload.env ?? {})) {
|
|
19307
|
+
if (hasSecretMetadata ? !secretEnv.has(key) : !looksLegacySecretIdentifier(key)) continue;
|
|
19308
|
+
recordCandidate(value, `env "${key}"`);
|
|
19309
|
+
}
|
|
19310
|
+
return [...candidateLabels.entries()].map(([value, labels]) => ({
|
|
19311
|
+
value,
|
|
19312
|
+
labels: [...labels].sort()
|
|
19313
|
+
}));
|
|
19314
|
+
} catch {
|
|
19315
|
+
return [];
|
|
19316
|
+
}
|
|
19317
|
+
}
|
|
19318
|
+
function checkInstalledPlaintextSecrets(checks, rootDir) {
|
|
19319
|
+
const literalCandidates = collectInstalledPlaintextSecretCandidates(rootDir);
|
|
19320
|
+
const findings = /* @__PURE__ */ new Map();
|
|
19321
|
+
for (const relativePath of walkInstalledBundleFiles(rootDir)) {
|
|
19322
|
+
const content = readInstalledTextFile(rootDir, relativePath);
|
|
19323
|
+
if (!content) continue;
|
|
19324
|
+
const labels = /* @__PURE__ */ new Set();
|
|
19325
|
+
for (const candidate of literalCandidates) {
|
|
19326
|
+
if (!content.includes(candidate.value)) continue;
|
|
19327
|
+
for (const label of candidate.labels) labels.add(label);
|
|
19328
|
+
}
|
|
19329
|
+
for (const sentinel of TEST_SECRET_SENTINELS) {
|
|
19330
|
+
if (sentinel.pattern.test(content)) {
|
|
19331
|
+
labels.add(sentinel.label);
|
|
19332
|
+
}
|
|
19333
|
+
}
|
|
19334
|
+
if (labels.size > 0) {
|
|
19335
|
+
findings.set(relativePath, labels);
|
|
19336
|
+
}
|
|
19337
|
+
}
|
|
19338
|
+
if (findings.size === 0) {
|
|
19339
|
+
addCheck2(checks, {
|
|
19340
|
+
level: "success",
|
|
19341
|
+
code: "consumer-plaintext-secret-absent",
|
|
19342
|
+
title: "Installed bundle does not expose known plaintext secret material",
|
|
19343
|
+
detail: "No known prompted secret values or maintained test-secret sentinels were detected in scanned installed text files.",
|
|
19344
|
+
fix: "No action needed."
|
|
19345
|
+
});
|
|
19346
|
+
return;
|
|
19347
|
+
}
|
|
19348
|
+
const detail = [...findings.entries()].map(([path, labels]) => `${path} (${[...labels].sort().join(", ")})`).join("; ");
|
|
19349
|
+
addCheck2(checks, {
|
|
19350
|
+
level: "error",
|
|
19351
|
+
code: "consumer-plaintext-secret-leak",
|
|
19352
|
+
title: "Installed bundle contains plaintext secret material",
|
|
19353
|
+
detail: `Detected plaintext secret material in installed files: ${detail}.`,
|
|
19354
|
+
fix: "Rotate the affected credential, remove the leaking install, reinstall after the Pluxx secret-reference fix, and rerun pluxx doctor --consumer."
|
|
19355
|
+
});
|
|
19356
|
+
}
|
|
19134
19357
|
function summarizeChecks2(checks) {
|
|
19135
19358
|
const visibleChecks = checks.filter((check) => !check.code.startsWith("primitive-"));
|
|
19136
19359
|
const errors = visibleChecks.filter((check) => check.level === "error").length;
|
|
@@ -19755,7 +19978,7 @@ function readCodexHooksFeatureFlag(filePath) {
|
|
|
19755
19978
|
inFeaturesTable = sectionMatch[1].trim() === "features";
|
|
19756
19979
|
continue;
|
|
19757
19980
|
}
|
|
19758
|
-
const dottedFeatureMatch = line.match(/^features\.(
|
|
19981
|
+
const dottedFeatureMatch = line.match(/^features\.(hooks|codex_hooks)\s*=\s*(.+)$/);
|
|
19759
19982
|
if (dottedFeatureMatch) {
|
|
19760
19983
|
assignFeatureFlag(dottedFeatureMatch[1], dottedFeatureMatch[2]);
|
|
19761
19984
|
continue;
|
|
@@ -19772,7 +19995,7 @@ function readCodexHooksFeatureFlag(filePath) {
|
|
|
19772
19995
|
continue;
|
|
19773
19996
|
}
|
|
19774
19997
|
if (!inFeaturesTable) continue;
|
|
19775
|
-
const featureMatch = line.match(/^(
|
|
19998
|
+
const featureMatch = line.match(/^(hooks|codex_hooks)\s*=\s*(.+)$/);
|
|
19776
19999
|
if (!featureMatch) continue;
|
|
19777
20000
|
assignFeatureFlag(featureMatch[1], featureMatch[2]);
|
|
19778
20001
|
}
|
|
@@ -19961,7 +20184,7 @@ function listClaudeSettingsCandidates(projectRoot) {
|
|
|
19961
20184
|
});
|
|
19962
20185
|
}
|
|
19963
20186
|
function readClaudeDisableAllHooks(filePath) {
|
|
19964
|
-
const parsed = readJsonFile2(dirname7(filePath),
|
|
20187
|
+
const parsed = readJsonFile2(dirname7(filePath), basename7(filePath));
|
|
19965
20188
|
return typeof parsed.disableAllHooks === "boolean" ? parsed.disableAllHooks : void 0;
|
|
19966
20189
|
}
|
|
19967
20190
|
function probeClaudeDisableAllHooks(projectRoot) {
|
|
@@ -20304,6 +20527,9 @@ function checkInstalledBundleIntegrity(checks, rootDir, layout) {
|
|
|
20304
20527
|
if (issues.missingHookTargets.length > 0) {
|
|
20305
20528
|
details.push(`hook commands reference missing bundle target${issues.missingHookTargets.length === 1 ? "" : "s"}: ${issues.missingHookTargets.join(", ")}`);
|
|
20306
20529
|
}
|
|
20530
|
+
if (issues.cwdUnsafeHookCommands.length > 0) {
|
|
20531
|
+
details.push(`Codex hook command${issues.cwdUnsafeHookCommands.length === 1 ? " depends" : "s depend"} on plugin-root cwd: ${issues.cwdUnsafeHookCommands.join("; ")}`);
|
|
20532
|
+
}
|
|
20307
20533
|
if (issues.invalidRuntimeScripts.length > 0) {
|
|
20308
20534
|
details.push(`runtime startup still depends on installer-owned validation: ${issues.invalidRuntimeScripts.join(", ")}`);
|
|
20309
20535
|
}
|
|
@@ -20323,7 +20549,7 @@ function checkInstalledBundleIntegrity(checks, rootDir, layout) {
|
|
|
20323
20549
|
code: "consumer-bundle-integrity-invalid",
|
|
20324
20550
|
title: "Installed bundle integrity is broken",
|
|
20325
20551
|
detail: details.join("; "),
|
|
20326
|
-
fix: "Reinstall the plugin or rebuild the bundle so every manifest path, hook config, and hook
|
|
20552
|
+
fix: "Reinstall the plugin or rebuild the bundle so every manifest path, hook config, and hook command resolves from the installed plugin root.",
|
|
20327
20553
|
path: layout.manifestPath
|
|
20328
20554
|
});
|
|
20329
20555
|
}
|
|
@@ -20388,31 +20614,31 @@ function checkInstalledCodexHooksFeatureFlag(checks, rootDir, layout, options) {
|
|
|
20388
20614
|
const enabledProbes = probes.filter((probe) => probe.enabled);
|
|
20389
20615
|
const checkedPaths = probes.map((probe) => `${probe.scope} ${probe.exists ? "config" : "path"}: ${probe.path}${probe.exists ? "" : " (missing)"}`).join("; ");
|
|
20390
20616
|
const describeEnabledFlags = (probe) => {
|
|
20391
|
-
const enabledFlags =
|
|
20392
|
-
if (probe.pluginBundledEnabled) enabledFlags.
|
|
20393
|
-
if (probe.recommendedEnabled) enabledFlags.
|
|
20394
|
-
if (probe.alternateEnabled) enabledFlags.
|
|
20395
|
-
return enabledFlags.join(" + ");
|
|
20617
|
+
const enabledFlags = /* @__PURE__ */ new Set();
|
|
20618
|
+
if (probe.pluginBundledEnabled) enabledFlags.add(PLUGIN_BUNDLED_CODEX_HOOKS_FEATURE_FLAG);
|
|
20619
|
+
if (probe.recommendedEnabled) enabledFlags.add(RECOMMENDED_CODEX_HOOKS_FEATURE_FLAG);
|
|
20620
|
+
if (probe.alternateEnabled) enabledFlags.add(ALTERNATE_CODEX_HOOKS_FEATURE_FLAG);
|
|
20621
|
+
return [...enabledFlags].join(" + ");
|
|
20396
20622
|
};
|
|
20397
20623
|
const generalOnlyProbes = probes.filter((probe) => !probe.pluginBundledEnabled && (probe.recommendedEnabled || probe.alternateEnabled));
|
|
20398
20624
|
if (generalOnlyProbes.length > 0) {
|
|
20399
20625
|
addCheck2(checks, {
|
|
20400
20626
|
level: "warning",
|
|
20401
20627
|
code: "consumer-codex-plugin-hooks-feature-flag-general-only",
|
|
20402
|
-
title: "Codex config enables only
|
|
20403
|
-
detail: `This installed Codex bundle declares plugin-bundled hooks at ${hooksReference}, but ${generalOnlyProbes.map((probe) => `${probe.scope} config ${probe.path}`).join(" and ")} enables only
|
|
20404
|
-
fix: `Enable \`${
|
|
20628
|
+
title: "Codex config enables only deprecated hook flags",
|
|
20629
|
+
detail: `This installed Codex bundle declares plugin-bundled hooks at ${hooksReference}, but ${generalOnlyProbes.map((probe) => `${probe.scope} config ${probe.path}`).join(" and ")} enables only deprecated hook flags (${generalOnlyProbes.map((probe) => describeEnabledFlags(probe)).join(" and ")}). Current Codex docs use \`${RECOMMENDED_CODEX_HOOKS_FEATURE_FLAG}\` as the canonical feature key.`,
|
|
20630
|
+
fix: `Enable \`${RECOMMENDED_CODEX_HOOKS_FEATURE_FLAG} = true\` under \`[features]\` in the active Codex config, reload Codex, and rerun pluxx verify-install.`,
|
|
20405
20631
|
path: generalOnlyProbes[0].path
|
|
20406
20632
|
});
|
|
20407
20633
|
}
|
|
20408
|
-
const legacyOnlyProbes = probes.filter((probe) => probe.alternateEnabled && !probe.recommendedEnabled);
|
|
20634
|
+
const legacyOnlyProbes = probes.filter((probe) => !probe.pluginBundledEnabled && probe.alternateEnabled && !probe.recommendedEnabled);
|
|
20409
20635
|
if (legacyOnlyProbes.length > 0) {
|
|
20410
20636
|
addCheck2(checks, {
|
|
20411
20637
|
level: "warning",
|
|
20412
20638
|
code: "consumer-codex-hooks-feature-flag-legacy-only",
|
|
20413
20639
|
title: "Codex config still uses the deprecated general hook compatibility flag",
|
|
20414
|
-
detail: `This installed Codex bundle declares plugin-bundled hooks at ${hooksReference}, and the checked Codex config enables only the deprecated
|
|
20415
|
-
fix: `
|
|
20640
|
+
detail: `This installed Codex bundle declares plugin-bundled hooks at ${hooksReference}, and the checked Codex config enables only the deprecated hook flag \`${ALTERNATE_CODEX_HOOKS_FEATURE_FLAG} = true\` in ${legacyOnlyProbes.map((probe) => `${probe.scope} config ${probe.path}`).join(" and ")}. Current Codex docs use \`${RECOMMENDED_CODEX_HOOKS_FEATURE_FLAG}\` as the canonical feature key.`,
|
|
20641
|
+
fix: `Use \`${RECOMMENDED_CODEX_HOOKS_FEATURE_FLAG} = true\` under \`[features]\`, reload Codex, and rerun pluxx verify-install after updating the active config.`,
|
|
20416
20642
|
path: legacyOnlyProbes[0].path
|
|
20417
20643
|
});
|
|
20418
20644
|
}
|
|
@@ -20420,8 +20646,8 @@ function checkInstalledCodexHooksFeatureFlag(checks, rootDir, layout, options) {
|
|
|
20420
20646
|
addCheck2(checks, {
|
|
20421
20647
|
level: "success",
|
|
20422
20648
|
code: "consumer-codex-hooks-feature-flag-enabled",
|
|
20423
|
-
title: "Codex
|
|
20424
|
-
detail: `This installed Codex bundle declares plugin-bundled hooks at ${hooksReference}, and the
|
|
20649
|
+
title: "Codex hook feature flag found for this install",
|
|
20650
|
+
detail: `This installed Codex bundle declares plugin-bundled hooks at ${hooksReference}, and the canonical hook feature key was found in ${enabledProbes.map((probe) => `${probe.scope} config ${probe.path} (${describeEnabledFlags(probe)})`).join(" and ")}. Treat that as a prerequisite, not proof of live hook execution.`,
|
|
20425
20651
|
fix: "No action needed.",
|
|
20426
20652
|
path: enabledProbes[0].path
|
|
20427
20653
|
});
|
|
@@ -20432,8 +20658,8 @@ function checkInstalledCodexHooksFeatureFlag(checks, rootDir, layout, options) {
|
|
|
20432
20658
|
level: "warning",
|
|
20433
20659
|
code: "consumer-codex-hooks-feature-flag-missing",
|
|
20434
20660
|
title: "Codex plugin-bundled hook activation is missing its known feature-gate prerequisite",
|
|
20435
|
-
detail: `This installed Codex bundle declares plugin-bundled hooks at ${hooksReference}, but \`${
|
|
20436
|
-
fix: `Enable \`${
|
|
20661
|
+
detail: `This installed Codex bundle declares plugin-bundled hooks at ${hooksReference}, but \`${RECOMMENDED_CODEX_HOOKS_FEATURE_FLAG} = true\` was not found in the checked Codex config layers. \`${ALTERNATE_CODEX_HOOKS_FEATURE_FLAG}\` is deprecated and should not be treated as the current hook feature key. Checked ${checkedPaths}.${parseErrors.length > 0 ? ` Unparseable config: ${parseErrors.join("; ")}.` : ""}`,
|
|
20662
|
+
fix: `Enable \`${RECOMMENDED_CODEX_HOOKS_FEATURE_FLAG} = true\` under \`[features]\` in the relevant project or user Codex config, reload Codex, and rerun pluxx verify-install.`,
|
|
20437
20663
|
path: probes.find((probe) => probe.exists)?.path ?? hooksReference
|
|
20438
20664
|
});
|
|
20439
20665
|
}
|
|
@@ -20796,7 +21022,7 @@ function isLikelyLocalRuntimePath3(value) {
|
|
|
20796
21022
|
function isLikelyOpenCodeInstallPath(rootDir) {
|
|
20797
21023
|
const parent = dirname7(rootDir);
|
|
20798
21024
|
const grandparent = dirname7(parent);
|
|
20799
|
-
return
|
|
21025
|
+
return basename7(parent) === "plugins" && basename7(grandparent) === "opencode";
|
|
20800
21026
|
}
|
|
20801
21027
|
function checkInstalledOpenCodeHostBridge(checks, rootDir) {
|
|
20802
21028
|
if (!isLikelyOpenCodeInstallPath(rootDir)) {
|
|
@@ -20810,7 +21036,7 @@ function checkInstalledOpenCodeHostBridge(checks, rootDir) {
|
|
|
20810
21036
|
});
|
|
20811
21037
|
return;
|
|
20812
21038
|
}
|
|
20813
|
-
const pluginName =
|
|
21039
|
+
const pluginName = basename7(rootDir);
|
|
20814
21040
|
const entryPath = `${rootDir}.ts`;
|
|
20815
21041
|
const entryRelativePath = `${pluginName}.ts`;
|
|
20816
21042
|
if (!existsSync24(entryPath)) {
|
|
@@ -20851,7 +21077,7 @@ function checkInstalledOpenCodeSkills(checks, rootDir) {
|
|
|
20851
21077
|
if (!isLikelyOpenCodeInstallPath(rootDir)) {
|
|
20852
21078
|
return;
|
|
20853
21079
|
}
|
|
20854
|
-
const pluginName =
|
|
21080
|
+
const pluginName = basename7(rootDir);
|
|
20855
21081
|
const sourceSkillsDir = resolve20(rootDir, "skills");
|
|
20856
21082
|
if (!existsSync24(sourceSkillsDir)) {
|
|
20857
21083
|
addCheck2(checks, {
|
|
@@ -20967,6 +21193,7 @@ async function doctorConsumer(rootDir = process.cwd(), options = {}) {
|
|
|
20967
21193
|
checkInstalledCodexPermissionCompanion(checks, rootDir, layout, options);
|
|
20968
21194
|
checkInstalledPermissionHook(checks, rootDir, layout);
|
|
20969
21195
|
checkInstalledUserConfig(checks, rootDir);
|
|
21196
|
+
checkInstalledPlaintextSecrets(checks, rootDir);
|
|
20970
21197
|
checkInstalledEnvValidation(checks, rootDir);
|
|
20971
21198
|
checkInstalledRuntimeScriptRoles(checks, rootDir);
|
|
20972
21199
|
await checkInstalledMcpConfig(checks, rootDir, layout);
|
|
@@ -21079,7 +21306,7 @@ function printDoctorReport(report) {
|
|
|
21079
21306
|
|
|
21080
21307
|
// src/cli/dev.ts
|
|
21081
21308
|
import { watch } from "fs";
|
|
21082
|
-
import { relative as
|
|
21309
|
+
import { relative as relative13, resolve as resolve21 } from "path";
|
|
21083
21310
|
var WATCH_PATTERNS = [
|
|
21084
21311
|
/^pluxx\.config\.(ts|js|json)$/,
|
|
21085
21312
|
/^skills\//,
|
|
@@ -21105,7 +21332,7 @@ async function runDev(args2) {
|
|
|
21105
21332
|
let pendingFile = null;
|
|
21106
21333
|
const watcher = watch(rootDir, { recursive: true }, (_event, filename) => {
|
|
21107
21334
|
if (!filename) return;
|
|
21108
|
-
const rel =
|
|
21335
|
+
const rel = relative13(rootDir, resolve21(rootDir, filename));
|
|
21109
21336
|
if (rel.startsWith("dist/") || rel.startsWith(".") || rel.includes("node_modules")) {
|
|
21110
21337
|
return;
|
|
21111
21338
|
}
|
|
@@ -21160,7 +21387,7 @@ async function runBuild(rootDir, targets) {
|
|
|
21160
21387
|
}
|
|
21161
21388
|
|
|
21162
21389
|
// src/cli/migrate.ts
|
|
21163
|
-
import { basename as
|
|
21390
|
+
import { basename as basename8, dirname as dirname8, relative as relative14, resolve as resolve22 } from "path";
|
|
21164
21391
|
import { existsSync as existsSync25, readdirSync as readdirSync11, mkdirSync as mkdirSync5, cpSync as cpSync4, readFileSync as readFileSync15, writeFileSync as writeFileSync5 } from "fs";
|
|
21165
21392
|
function detectPlatform(pluginDir) {
|
|
21166
21393
|
const checks = [
|
|
@@ -21525,7 +21752,7 @@ function parseCursorRuleFile(pluginDir, filePath) {
|
|
|
21525
21752
|
const content = readFileSync15(filePath, "utf-8");
|
|
21526
21753
|
const { hasFrontmatter, frontmatterLines, body } = splitMarkdownFrontmatter5(content);
|
|
21527
21754
|
if (!hasFrontmatter) return null;
|
|
21528
|
-
const description = parseTopLevelStringFrontmatter(frontmatterLines, "description") ?? titleCaseFromDirName(
|
|
21755
|
+
const description = parseTopLevelStringFrontmatter(frontmatterLines, "description") ?? titleCaseFromDirName(basename8(filePath, ".mdc"));
|
|
21529
21756
|
const globs = parseTopLevelStringOrStringArrayFrontmatter(frontmatterLines, "globs");
|
|
21530
21757
|
const alwaysApply = parseTopLevelBooleanFrontmatter(frontmatterLines, "alwaysApply");
|
|
21531
21758
|
return {
|
|
@@ -21533,7 +21760,7 @@ function parseCursorRuleFile(pluginDir, filePath) {
|
|
|
21533
21760
|
...globs !== void 0 ? { globs } : {},
|
|
21534
21761
|
...alwaysApply !== void 0 ? { alwaysApply } : {},
|
|
21535
21762
|
...body.trim() ? { content: body.trim() } : {},
|
|
21536
|
-
path: `./${
|
|
21763
|
+
path: `./${relative14(pluginDir, filePath).replace(/\\/g, "/")}`
|
|
21537
21764
|
};
|
|
21538
21765
|
}
|
|
21539
21766
|
function collectFiles(dir, predicate) {
|
|
@@ -21578,7 +21805,7 @@ function findInstructionSources(pluginDir, detection) {
|
|
|
21578
21805
|
const extraPaths = /* @__PURE__ */ new Set();
|
|
21579
21806
|
const platformOverrides = {};
|
|
21580
21807
|
if (detection.platform === "cursor") {
|
|
21581
|
-
const nestedAgents = collectFiles(pluginDir, (entry) => entry === "AGENTS.md").map((path) => `./${
|
|
21808
|
+
const nestedAgents = collectFiles(pluginDir, (entry) => entry === "AGENTS.md").map((path) => `./${relative14(pluginDir, path).replace(/\\/g, "/")}`).filter((path) => path !== primary);
|
|
21582
21809
|
if (nestedAgents.length > 0) {
|
|
21583
21810
|
platformOverrides.cursor = {
|
|
21584
21811
|
...platformOverrides.cursor ?? {},
|
|
@@ -22241,7 +22468,7 @@ function hasTopLevelFrontmatterKey(frontmatterLines, key) {
|
|
|
22241
22468
|
function normalizeMigratedOpenCodeAgentFile(agentPath) {
|
|
22242
22469
|
const original = readFileSync15(agentPath, "utf-8");
|
|
22243
22470
|
const parsed = splitMarkdownFrontmatter5(original);
|
|
22244
|
-
const fileStem = toKebabCase2(
|
|
22471
|
+
const fileStem = toKebabCase2(basename8(agentPath, ".md")) || "agent";
|
|
22245
22472
|
const fallbackDescription = buildFallbackAgentDescription(fileStem);
|
|
22246
22473
|
if (!parsed.hasFrontmatter) {
|
|
22247
22474
|
const rewritten2 = [
|
|
@@ -22297,7 +22524,7 @@ function normalizeMigratedOpenCodeAgents(destDir) {
|
|
|
22297
22524
|
const normalized = [];
|
|
22298
22525
|
for (const filePath of walkMarkdownFiles3(destDir)) {
|
|
22299
22526
|
if (normalizeMigratedOpenCodeAgentFile(filePath)) {
|
|
22300
|
-
normalized.push(
|
|
22527
|
+
normalized.push(relative14(destDir, filePath).replace(/\\/g, "/"));
|
|
22301
22528
|
}
|
|
22302
22529
|
}
|
|
22303
22530
|
return normalized.sort();
|
|
@@ -24135,7 +24362,7 @@ ${c2}
|
|
|
24135
24362
|
} }).prompt();
|
|
24136
24363
|
|
|
24137
24364
|
// src/cli/index.ts
|
|
24138
|
-
import { basename as
|
|
24365
|
+
import { basename as basename10, resolve as resolve29 } from "path";
|
|
24139
24366
|
import { mkdir as mkdir4, mkdtemp as mkdtemp3, rm as rm4 } from "fs/promises";
|
|
24140
24367
|
import { tmpdir as tmpdir5 } from "os";
|
|
24141
24368
|
import { spawn as spawn6, spawnSync as spawnSync4 } from "child_process";
|
|
@@ -24444,8 +24671,15 @@ echo
|
|
|
24444
24671
|
echo "Installed __DISPLAY_NAME__ across ${installerTargets.join(", ")}."
|
|
24445
24672
|
`.replaceAll("__REPO__", "REPO_PLACEHOLDER").replaceAll("__DISPLAY_NAME__", "DISPLAY_PLACEHOLDER");
|
|
24446
24673
|
}
|
|
24674
|
+
function collectInstallerUserConfigEntries(config, platforms) {
|
|
24675
|
+
const baseEntries = collectUserConfigEntries(config, platforms);
|
|
24676
|
+
return [
|
|
24677
|
+
...baseEntries,
|
|
24678
|
+
...collectNativeMcpAuthUserConfigEntries(config, platforms, baseEntries)
|
|
24679
|
+
];
|
|
24680
|
+
}
|
|
24447
24681
|
function renderInstallerUserConfigSnippet(config, platform, installDirVariable) {
|
|
24448
|
-
const entries =
|
|
24682
|
+
const entries = collectInstallerUserConfigEntries(config, [platform]).map((entry) => ({
|
|
24449
24683
|
key: entry.key,
|
|
24450
24684
|
title: entry.title,
|
|
24451
24685
|
type: entry.type ?? "string",
|
|
@@ -24453,6 +24687,7 @@ function renderInstallerUserConfigSnippet(config, platform, installDirVariable)
|
|
|
24453
24687
|
envVar: entry.envVar ?? defaultUserConfigEnvVar(entry.key)
|
|
24454
24688
|
}));
|
|
24455
24689
|
if (entries.length === 0) return "";
|
|
24690
|
+
const preserveSecretReferences = platform === "codex";
|
|
24456
24691
|
const promptLines = entries.map((entry) => {
|
|
24457
24692
|
const functionName = entry.type === "secret" ? "pluxx_prompt_secret_config" : "pluxx_prompt_text_config";
|
|
24458
24693
|
return `${functionName} ${JSON.stringify(entry.key)} ${JSON.stringify(entry.envVar)} ${JSON.stringify(entry.title)} ${entry.required ? "1" : "0"}`;
|
|
@@ -24463,6 +24698,8 @@ ${JSON.stringify(entries)}
|
|
|
24463
24698
|
PLUXX_USER_CONFIG_JSON
|
|
24464
24699
|
)"
|
|
24465
24700
|
PLUXX_REUSED_USER_CONFIG=0
|
|
24701
|
+
PLUXX_PRESERVE_SECRET_REFS="${preserveSecretReferences ? "1" : "0"}"
|
|
24702
|
+
export PLUXX_PRESERVE_SECRET_REFS
|
|
24466
24703
|
|
|
24467
24704
|
pluxx_is_placeholder_secret() {
|
|
24468
24705
|
case "$1" in
|
|
@@ -24489,12 +24726,14 @@ const fs = require('fs')
|
|
|
24489
24726
|
const filepath = process.env.PLUXX_SAVED_USER_CONFIG_PATH
|
|
24490
24727
|
const key = process.env.PLUXX_SAVED_CONFIG_KEY
|
|
24491
24728
|
const envVar = process.env.PLUXX_SAVED_CONFIG_ENV_VAR
|
|
24729
|
+
const preserveSecretRefs = process.env.PLUXX_PRESERVE_SECRET_REFS === '1'
|
|
24492
24730
|
|
|
24493
24731
|
try {
|
|
24494
24732
|
const payload = JSON.parse(fs.readFileSync(filepath, 'utf8'))
|
|
24495
24733
|
const candidates = [
|
|
24496
24734
|
payload && payload.env && envVar ? payload.env[envVar] : undefined,
|
|
24497
24735
|
payload && payload.values && key ? payload.values[key] : undefined,
|
|
24736
|
+
preserveSecretRefs && payload && payload.envRefs && envVar && payload.envRefs[envVar] === envVar ? envVar : undefined,
|
|
24498
24737
|
]
|
|
24499
24738
|
|
|
24500
24739
|
for (const candidate of candidates) {
|
|
@@ -24590,21 +24829,53 @@ const path = require('path')
|
|
|
24590
24829
|
|
|
24591
24830
|
const installDir = process.env.PLUXX_INSTALL_DIR
|
|
24592
24831
|
const spec = JSON.parse(process.env.PLUXX_USER_CONFIG_SPEC || '[]')
|
|
24832
|
+
const preserveSecretReferences = ${preserveSecretReferences ? "true" : "false"}
|
|
24593
24833
|
|
|
24594
24834
|
if (installDir && spec.length > 0) {
|
|
24595
24835
|
const env = {}
|
|
24596
24836
|
const values = {}
|
|
24837
|
+
const envRefs = {}
|
|
24838
|
+
const secretKeys = []
|
|
24839
|
+
const secretEnv = []
|
|
24840
|
+
let hasSecret = false
|
|
24841
|
+
const secretEnvVars = new Set(
|
|
24842
|
+
spec
|
|
24843
|
+
.filter((entry) => entry && entry.type === 'secret' && typeof entry.envVar === 'string' && entry.envVar !== '')
|
|
24844
|
+
.map((entry) => entry.envVar),
|
|
24845
|
+
)
|
|
24597
24846
|
|
|
24598
24847
|
for (const entry of spec) {
|
|
24848
|
+
if (entry.type === 'secret') {
|
|
24849
|
+
hasSecret = true
|
|
24850
|
+
if (preserveSecretReferences) {
|
|
24851
|
+
if (entry.key) secretKeys.push(entry.key)
|
|
24852
|
+
if (entry.envVar) secretEnv.push(entry.envVar)
|
|
24853
|
+
}
|
|
24854
|
+
}
|
|
24599
24855
|
const value = process.env[entry.envVar]
|
|
24600
24856
|
if (value === undefined || value === '') continue
|
|
24857
|
+
if (preserveSecretReferences && entry.type === 'secret') {
|
|
24858
|
+
envRefs[entry.envVar] = entry.envVar
|
|
24859
|
+
continue
|
|
24860
|
+
}
|
|
24601
24861
|
values[entry.key] = value
|
|
24602
24862
|
env[entry.envVar] = value
|
|
24603
24863
|
}
|
|
24604
24864
|
|
|
24605
24865
|
fs.writeFileSync(
|
|
24606
24866
|
path.join(installDir, '.pluxx-user.json'),
|
|
24607
|
-
JSON.stringify(
|
|
24867
|
+
JSON.stringify(
|
|
24868
|
+
{
|
|
24869
|
+
...(Object.keys(values).length > 0 ? { values } : {}),
|
|
24870
|
+
...(Object.keys(env).length > 0 ? { env } : {}),
|
|
24871
|
+
...(Object.keys(envRefs).length > 0 ? { envRefs } : {}),
|
|
24872
|
+
...(hasSecret ? { secretStorage: preserveSecretReferences ? 'env-ref' : 'materialized' } : {}),
|
|
24873
|
+
...(preserveSecretReferences && secretKeys.length > 0 ? { secretKeys: [...new Set(secretKeys)].sort() } : {}),
|
|
24874
|
+
...(preserveSecretReferences && secretEnv.length > 0 ? { secretEnv: [...new Set(secretEnv)].sort() } : {}),
|
|
24875
|
+
},
|
|
24876
|
+
null,
|
|
24877
|
+
2,
|
|
24878
|
+
) + '\\n',
|
|
24608
24879
|
)
|
|
24609
24880
|
|
|
24610
24881
|
const envScriptPath = path.join(installDir, 'scripts/check-env.sh')
|
|
@@ -24617,7 +24888,10 @@ if (installDir && spec.length > 0) {
|
|
|
24617
24888
|
|
|
24618
24889
|
const materialize = (value) =>
|
|
24619
24890
|
typeof value === 'string'
|
|
24620
|
-
? value.replace(
|
|
24891
|
+
? value.replace(
|
|
24892
|
+
/\\$\\{([A-Za-z_][A-Za-z0-9_]*)\\}/g,
|
|
24893
|
+
(_match, name) => (preserveSecretReferences && secretEnvVars.has(name) ? '${" + name + "}' : (env[name] || '${" + name + "}')),
|
|
24894
|
+
)
|
|
24621
24895
|
: value
|
|
24622
24896
|
|
|
24623
24897
|
const materializeRecord = (record) => {
|
|
@@ -24641,7 +24915,7 @@ if (installDir && spec.length > 0) {
|
|
|
24641
24915
|
server.env = materializeRecord(server.env)
|
|
24642
24916
|
}
|
|
24643
24917
|
|
|
24644
|
-
if (server.bearer_token_env_var && env[server.bearer_token_env_var]) {
|
|
24918
|
+
if (!preserveSecretReferences && server.bearer_token_env_var && env[server.bearer_token_env_var]) {
|
|
24645
24919
|
server.http_headers = {
|
|
24646
24920
|
...(server.http_headers || {}),
|
|
24647
24921
|
Authorization: 'Bearer ' + env[server.bearer_token_env_var],
|
|
@@ -24649,7 +24923,7 @@ if (installDir && spec.length > 0) {
|
|
|
24649
24923
|
delete server.bearer_token_env_var
|
|
24650
24924
|
}
|
|
24651
24925
|
|
|
24652
|
-
if (server.env_http_headers && typeof server.env_http_headers === 'object') {
|
|
24926
|
+
if (!preserveSecretReferences && server.env_http_headers && typeof server.env_http_headers === 'object') {
|
|
24653
24927
|
server.http_headers = {
|
|
24654
24928
|
...(server.http_headers || {}),
|
|
24655
24929
|
}
|
|
@@ -24674,7 +24948,7 @@ NODE
|
|
|
24674
24948
|
`;
|
|
24675
24949
|
}
|
|
24676
24950
|
function hasInstallerUserConfig(config, platform) {
|
|
24677
|
-
return
|
|
24951
|
+
return collectInstallerUserConfigEntries(config, [platform]).length > 0;
|
|
24678
24952
|
}
|
|
24679
24953
|
function renderInstallerSavedUserConfigCaptureSnippet(config, platform, installDirVariable) {
|
|
24680
24954
|
if (!hasInstallerUserConfig(config, platform)) return "";
|
|
@@ -24840,13 +25114,13 @@ for (const line of lines) {
|
|
|
24840
25114
|
continue
|
|
24841
25115
|
}
|
|
24842
25116
|
if (tableName === '') {
|
|
24843
|
-
const dottedMatch = trimmed.match(/^features\\.
|
|
25117
|
+
const dottedMatch = trimmed.match(/^features\\.hooks\\s*=\\s*(.+)$/)
|
|
24844
25118
|
if (dottedMatch && isTomlTrue(dottedMatch[1])) process.exit(0)
|
|
24845
25119
|
const inlineMatch = trimmed.match(/^features\\s*=\\s*(.+)$/)
|
|
24846
|
-
if (inlineMatch && /\\
|
|
25120
|
+
if (inlineMatch && /\\bhooks\\s*=\\s*true\\b/i.test(inlineMatch[1])) process.exit(0)
|
|
24847
25121
|
}
|
|
24848
25122
|
if (tableName !== 'features') continue
|
|
24849
|
-
const match = trimmed.match(/^
|
|
25123
|
+
const match = trimmed.match(/^hooks\\s*=\\s*(.+)$/)
|
|
24850
25124
|
if (match && isTomlTrue(match[1])) process.exit(0)
|
|
24851
25125
|
}
|
|
24852
25126
|
process.exit(1)
|
|
@@ -24865,7 +25139,7 @@ NODE
|
|
|
24865
25139
|
*)
|
|
24866
25140
|
if [[ -r /dev/tty ]]; then
|
|
24867
25141
|
echo "This Codex plugin bundle includes startup hooks." >/dev/tty
|
|
24868
|
-
echo "Codex requires [features].
|
|
25142
|
+
echo "Codex requires [features].hooks = true before plugin-bundled hooks can run." >/dev/tty
|
|
24869
25143
|
read -r -p "Enable Codex plugin-bundled hooks in $CODEX_CONFIG_PATH now? [Y/n] " PLUXX_CODEX_HOOKS_REPLY </dev/tty
|
|
24870
25144
|
case "$PLUXX_CODEX_HOOKS_REPLY" in
|
|
24871
25145
|
n|N|no|NO)
|
|
@@ -24937,7 +25211,7 @@ for (let index = 0; index < lines.length; index += 1) {
|
|
|
24937
25211
|
if (/^features\\.[A-Za-z0-9_-]+\\s*=/.test(trimmed) && firstTopLevelFeaturesDotted < 0) {
|
|
24938
25212
|
firstTopLevelFeaturesDotted = index
|
|
24939
25213
|
}
|
|
24940
|
-
if (/^features\\.
|
|
25214
|
+
if (/^features\\.hooks\\s*=/.test(trimmed)) {
|
|
24941
25215
|
topLevelPluginHooksDotted = index
|
|
24942
25216
|
}
|
|
24943
25217
|
if (/^features\\s*=\\s*\\{/.test(trimmed)) {
|
|
@@ -24956,28 +25230,28 @@ if (start >= 0) {
|
|
|
24956
25230
|
|
|
24957
25231
|
let updated = false
|
|
24958
25232
|
for (let index = start + 1; index < end; index += 1) {
|
|
24959
|
-
if (/^
|
|
24960
|
-
lines[index] = '
|
|
25233
|
+
if (/^hooks\\s*=/.test(stripTomlComment(lines[index]).trim())) {
|
|
25234
|
+
lines[index] = 'hooks = true'
|
|
24961
25235
|
updated = true
|
|
24962
25236
|
}
|
|
24963
25237
|
}
|
|
24964
|
-
if (!updated) lines.splice(start + 1, 0, '
|
|
25238
|
+
if (!updated) lines.splice(start + 1, 0, 'hooks = true')
|
|
24965
25239
|
} else if (topLevelPluginHooksDotted >= 0) {
|
|
24966
|
-
lines[topLevelPluginHooksDotted] = 'features.
|
|
25240
|
+
lines[topLevelPluginHooksDotted] = 'features.hooks = true'
|
|
24967
25241
|
} else if (firstTopLevelFeaturesDotted >= 0) {
|
|
24968
|
-
lines.splice(firstTopLevelFeaturesDotted + 1, 0, 'features.
|
|
25242
|
+
lines.splice(firstTopLevelFeaturesDotted + 1, 0, 'features.hooks = true')
|
|
24969
25243
|
} else if (topLevelInlineFeatures >= 0 && lines[topLevelInlineFeatures].includes('}')) {
|
|
24970
|
-
if (/\\
|
|
25244
|
+
if (/\\bhooks\\s*=/.test(lines[topLevelInlineFeatures])) {
|
|
24971
25245
|
lines[topLevelInlineFeatures] = lines[topLevelInlineFeatures].replace(
|
|
24972
|
-
/\\
|
|
24973
|
-
'
|
|
25246
|
+
/\\bhooks\\s*=\\s*(true|false)\\b/i,
|
|
25247
|
+
'hooks = true',
|
|
24974
25248
|
)
|
|
24975
25249
|
} else {
|
|
24976
|
-
lines[topLevelInlineFeatures] = lines[topLevelInlineFeatures].replace(/}/, ',
|
|
25250
|
+
lines[topLevelInlineFeatures] = lines[topLevelInlineFeatures].replace(/}/, ', hooks = true }')
|
|
24977
25251
|
}
|
|
24978
25252
|
} else {
|
|
24979
25253
|
if (lines.length > 0 && lines[lines.length - 1] !== '') lines.push('')
|
|
24980
|
-
lines.push('[features]', '
|
|
25254
|
+
lines.push('[features]', 'hooks = true')
|
|
24981
25255
|
}
|
|
24982
25256
|
|
|
24983
25257
|
fs.mkdirSync(path.dirname(filepath), { recursive: true })
|
|
@@ -24988,7 +25262,7 @@ NODE
|
|
|
24988
25262
|
else
|
|
24989
25263
|
echo "Codex plugin-bundled hooks are not enabled. Startup hooks from this plugin will not run until you add this to $CODEX_CONFIG_PATH:" >&2
|
|
24990
25264
|
echo "[features]" >&2
|
|
24991
|
-
echo "
|
|
25265
|
+
echo "hooks = true" >&2
|
|
24992
25266
|
echo "Then restart or refresh Codex before relying on plugin startup hooks." >&2
|
|
24993
25267
|
echo "Set PLUXX_CODEX_ENABLE_PLUGIN_HOOKS=1 before running this installer to enable it noninteractively." >&2
|
|
24994
25268
|
fi
|
|
@@ -25854,10 +26128,10 @@ import { resolve as resolve27 } from "path";
|
|
|
25854
26128
|
import { existsSync as existsSync28, readFileSync as readFileSync19 } from "fs";
|
|
25855
26129
|
import { mkdtemp as mkdtemp2, rm as rm3 } from "fs/promises";
|
|
25856
26130
|
import { tmpdir as tmpdir4 } from "os";
|
|
25857
|
-
import { basename as
|
|
26131
|
+
import { basename as basename9, resolve as resolve26 } from "path";
|
|
25858
26132
|
import { spawn as spawn4 } from "child_process";
|
|
25859
26133
|
async function executeCodexExecCommand(command2, options) {
|
|
25860
|
-
if (
|
|
26134
|
+
if (basename9(command2[0]) !== "codex" || command2[1] !== "exec") {
|
|
25861
26135
|
throw new Error("Codex exec runner requires a command beginning with `codex exec`.");
|
|
25862
26136
|
}
|
|
25863
26137
|
const outputDir = await mkdtemp2(resolve26(tmpdir4(), options.outputDirPrefix ?? "pluxx-codex-exec-"));
|
|
@@ -26182,6 +26456,7 @@ async function executeBehavioralCommand(platform, command2, cwd, timeoutMs) {
|
|
|
26182
26456
|
};
|
|
26183
26457
|
}
|
|
26184
26458
|
return await new Promise((resolvePromise, reject) => {
|
|
26459
|
+
const startedAt = Date.now();
|
|
26185
26460
|
const child = spawn5(command2[0], command2.slice(1), {
|
|
26186
26461
|
cwd,
|
|
26187
26462
|
detached: process.platform !== "win32",
|
|
@@ -26190,15 +26465,30 @@ async function executeBehavioralCommand(platform, command2, cwd, timeoutMs) {
|
|
|
26190
26465
|
});
|
|
26191
26466
|
const stdoutChunks = [];
|
|
26192
26467
|
const stderrChunks = [];
|
|
26468
|
+
let settled = false;
|
|
26469
|
+
let timedOut = false;
|
|
26470
|
+
const timeout = setTimeout(() => {
|
|
26471
|
+
timedOut = true;
|
|
26472
|
+
signalBehavioralProcess(child, "SIGKILL");
|
|
26473
|
+
}, timeoutMs);
|
|
26193
26474
|
child.stdout?.on("data", (chunk) => stdoutChunks.push(Buffer.from(chunk)));
|
|
26194
26475
|
child.stderr?.on("data", (chunk) => stderrChunks.push(Buffer.from(chunk)));
|
|
26195
|
-
child.on("error",
|
|
26476
|
+
child.on("error", (error) => {
|
|
26477
|
+
if (settled) return;
|
|
26478
|
+
settled = true;
|
|
26479
|
+
clearTimeout(timeout);
|
|
26480
|
+
reject(error);
|
|
26481
|
+
});
|
|
26196
26482
|
child.on("close", (code) => {
|
|
26483
|
+
if (settled) return;
|
|
26484
|
+
settled = true;
|
|
26485
|
+
clearTimeout(timeout);
|
|
26486
|
+
const exceededDeadline = timedOut || Date.now() - startedAt > timeoutMs;
|
|
26197
26487
|
const stdout = Buffer.concat(stdoutChunks).toString("utf-8");
|
|
26198
26488
|
const stderr = Buffer.concat(stderrChunks).toString("utf-8");
|
|
26199
26489
|
resolvePromise({
|
|
26200
|
-
exitCode: code ?? 1,
|
|
26201
|
-
response: stdout.trim() || stderr.trim()
|
|
26490
|
+
exitCode: exceededDeadline ? 124 : code ?? 1,
|
|
26491
|
+
response: exceededDeadline ? `behavioral runner timed out after ${timeoutMs}ms` : stdout.trim() || stderr.trim()
|
|
26202
26492
|
});
|
|
26203
26493
|
});
|
|
26204
26494
|
});
|
|
@@ -26240,6 +26530,22 @@ async function commandSucceeds2(command2) {
|
|
|
26240
26530
|
child.on("error", () => resolvePromise(false));
|
|
26241
26531
|
});
|
|
26242
26532
|
}
|
|
26533
|
+
function signalBehavioralProcess(child, signal) {
|
|
26534
|
+
if (child.exitCode != null || child.signalCode != null) {
|
|
26535
|
+
return;
|
|
26536
|
+
}
|
|
26537
|
+
if (process.platform !== "win32" && typeof child.pid === "number") {
|
|
26538
|
+
try {
|
|
26539
|
+
process.kill(-child.pid, signal);
|
|
26540
|
+
return;
|
|
26541
|
+
} catch {
|
|
26542
|
+
}
|
|
26543
|
+
}
|
|
26544
|
+
try {
|
|
26545
|
+
child.kill(signal);
|
|
26546
|
+
} catch {
|
|
26547
|
+
}
|
|
26548
|
+
}
|
|
26243
26549
|
function truncate2(value, length) {
|
|
26244
26550
|
if (value.length <= length) return value;
|
|
26245
26551
|
return `${value.slice(0, Math.max(0, length - 3))}...`;
|
|
@@ -26261,7 +26567,7 @@ function shellQuote2(value) {
|
|
|
26261
26567
|
// src/cli/discover-installed-mcp.ts
|
|
26262
26568
|
import { existsSync as existsSync30, readFileSync as readFileSync21, realpathSync as realpathSync3 } from "fs";
|
|
26263
26569
|
import { homedir as homedir3 } from "os";
|
|
26264
|
-
import { isAbsolute as isAbsolute2, relative as
|
|
26570
|
+
import { isAbsolute as isAbsolute2, relative as relative15, resolve as resolve28 } from "path";
|
|
26265
26571
|
var INSTALLED_MCP_HOSTS = ["claude-code", "cursor", "codex", "opencode"];
|
|
26266
26572
|
function discoverInstalledMcpServers(options = {}) {
|
|
26267
26573
|
const rootDir = options.rootDir ?? process.cwd();
|
|
@@ -26400,13 +26706,13 @@ function buildClaudeProjectSourceScope(projectPath, rootDir, homeDir) {
|
|
|
26400
26706
|
return `projects/absolute/${normalizeScopePath(projectPath)}`;
|
|
26401
26707
|
}
|
|
26402
26708
|
function buildRelativeScopePath(baseDir, targetPath) {
|
|
26403
|
-
const relativePath =
|
|
26709
|
+
const relativePath = relative15(baseDir, targetPath);
|
|
26404
26710
|
if (!relativePath.startsWith("..") && !isAbsolute2(relativePath)) {
|
|
26405
26711
|
return normalizeScopePath(relativePath);
|
|
26406
26712
|
}
|
|
26407
26713
|
if (!existsSync30(baseDir) || !existsSync30(targetPath)) return void 0;
|
|
26408
26714
|
try {
|
|
26409
|
-
const realRelativePath =
|
|
26715
|
+
const realRelativePath = relative15(realpathSync3(baseDir), realpathSync3(targetPath));
|
|
26410
26716
|
if (realRelativePath.startsWith("..") || isAbsolute2(realRelativePath)) return void 0;
|
|
26411
26717
|
return normalizeScopePath(realRelativePath);
|
|
26412
26718
|
} catch {
|
|
@@ -26606,7 +26912,7 @@ function buildInstalledMcpSourceLabel(discovered, rootDir, homeDir) {
|
|
|
26606
26912
|
return discovered.sourceScope ? `${base}:${discovered.sourceScope}` : base;
|
|
26607
26913
|
}
|
|
26608
26914
|
function buildRelativeSelectorLabel(prefix, baseDir, sourcePath) {
|
|
26609
|
-
const relativePath =
|
|
26915
|
+
const relativePath = relative15(baseDir, sourcePath);
|
|
26610
26916
|
if (relativePath === "") return `${prefix}:.`;
|
|
26611
26917
|
if (relativePath.startsWith("..") || isAbsolute2(relativePath)) return void 0;
|
|
26612
26918
|
return `${prefix}:${relativePath.replace(/\\/g, "/")}`;
|
|
@@ -27530,7 +27836,7 @@ async function runInit() {
|
|
|
27530
27836
|
if (!runtime.isInteractive) {
|
|
27531
27837
|
throw new Error("pluxx init requires an interactive terminal unless you use `pluxx init --from-mcp ... --yes`.");
|
|
27532
27838
|
}
|
|
27533
|
-
const dirName = positionalName ? toKebabCase3(positionalName) :
|
|
27839
|
+
const dirName = positionalName ? toKebabCase3(positionalName) : basename10(process.cwd()).toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
27534
27840
|
console.log("");
|
|
27535
27841
|
console.log(" pluxx init \u2014 Create a new plugin");
|
|
27536
27842
|
console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|