@orchid-labs/pluxx 0.1.21 → 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 +361 -108
- 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 +286 -45
- 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
|
|
@@ -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
|
}
|
|
@@ -6393,10 +6440,19 @@ function buildHookCommandWrapperScript(command2, pluginRootVar, envFileVar) {
|
|
|
6393
6440
|
'const env = payload && typeof payload === "object" && payload.env && typeof payload.env === "object"',
|
|
6394
6441
|
" ? payload.env",
|
|
6395
6442
|
" : {}",
|
|
6443
|
+
'const envRefs = payload && typeof payload === "object" && payload.envRefs && typeof payload.envRefs === "object"',
|
|
6444
|
+
" ? payload.envRefs",
|
|
6445
|
+
" : {}",
|
|
6396
6446
|
"",
|
|
6397
6447
|
"for (const [key, value] of Object.entries(env)) {",
|
|
6398
6448
|
" if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) continue",
|
|
6399
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`)",
|
|
6400
6456
|
"}"
|
|
6401
6457
|
].join("\n");
|
|
6402
6458
|
const maybeAppendEnvFile = envFileVar ? [
|
|
@@ -9875,7 +9931,7 @@ function toQuotedTomlKey(value) {
|
|
|
9875
9931
|
// src/codex-hooks-feature.ts
|
|
9876
9932
|
var RECOMMENDED_CODEX_HOOKS_FEATURE_FLAG = "hooks";
|
|
9877
9933
|
var ALTERNATE_CODEX_HOOKS_FEATURE_FLAG = "codex_hooks";
|
|
9878
|
-
var PLUGIN_BUNDLED_CODEX_HOOKS_FEATURE_FLAG = "
|
|
9934
|
+
var PLUGIN_BUNDLED_CODEX_HOOKS_FEATURE_FLAG = "hooks";
|
|
9879
9935
|
function getCodexHooksFeatureState(features) {
|
|
9880
9936
|
if (!features) {
|
|
9881
9937
|
return {
|
|
@@ -10225,7 +10281,7 @@ var CodexGenerator = class extends Generator {
|
|
|
10225
10281
|
pluginBundleFeatureFlag: PLUGIN_BUNDLED_CODEX_HOOKS_FEATURE_FLAG,
|
|
10226
10282
|
generalFeatureFlag: RECOMMENDED_CODEX_HOOKS_FEATURE_FLAG,
|
|
10227
10283
|
deprecatedGeneralFeatureFlag: ALTERNATE_CODEX_HOOKS_FEATURE_FLAG,
|
|
10228
|
-
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.",
|
|
10229
10285
|
hooks,
|
|
10230
10286
|
...unsupported.length > 0 ? { unsupported } : {}
|
|
10231
10287
|
});
|
|
@@ -10434,7 +10490,7 @@ var OpenCodeGenerator = class extends Generator {
|
|
|
10434
10490
|
`const isMcpTool = (tool: string): boolean =>`,
|
|
10435
10491
|
` tool === "mcp" || tool.startsWith("mcp.") || tool.startsWith("mcp_")`,
|
|
10436
10492
|
"",
|
|
10437
|
-
`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> } => {`,
|
|
10438
10494
|
` const filepath = resolve(directory, ".pluxx-user.json")`,
|
|
10439
10495
|
` if (!existsSync(filepath)) return {}`,
|
|
10440
10496
|
` try {`,
|
|
@@ -10444,28 +10500,30 @@ var OpenCodeGenerator = class extends Generator {
|
|
|
10444
10500
|
` }`,
|
|
10445
10501
|
`}`,
|
|
10446
10502
|
"",
|
|
10447
|
-
`const resolveRuntimeValue = (name: string, userEnv: Record<string, string>): string | undefined =>`,
|
|
10448
|
-
` 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]`,
|
|
10449
10505
|
"",
|
|
10450
|
-
`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 => {`,
|
|
10451
10507
|
` if (!input) return undefined`,
|
|
10452
10508
|
` const output: Record<string, string> = {}`,
|
|
10453
10509
|
` for (const [key, value] of Object.entries(input)) {`,
|
|
10454
|
-
` 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}}\`)`,
|
|
10455
10511
|
` }`,
|
|
10456
10512
|
` return output`,
|
|
10457
10513
|
`}`,
|
|
10458
10514
|
"",
|
|
10459
10515
|
`const buildMcpConfig = (directory: string): NonNullable<Config["mcp"]> => {`,
|
|
10460
10516
|
` const config: NonNullable<Config["mcp"]> = {}`,
|
|
10461
|
-
` const
|
|
10517
|
+
` const userConfig = loadUserConfig(directory)`,
|
|
10518
|
+
` const userEnv = userConfig.env ?? {}`,
|
|
10519
|
+
` const userEnvRefs = userConfig.envRefs ?? {}`,
|
|
10462
10520
|
"",
|
|
10463
10521
|
` for (const [name, definition] of Object.entries(MCP_DEFINITIONS)) {`,
|
|
10464
10522
|
` if (definition.transport === "stdio" && definition.command) {`,
|
|
10465
10523
|
` config[name] = {`,
|
|
10466
10524
|
` type: "local",`,
|
|
10467
10525
|
` command: [definition.command, ...(definition.args ?? [])],`,
|
|
10468
|
-
` ...(definition.env ? { environment: materializeEnv(definition.env, userEnv) } : {}),`,
|
|
10526
|
+
` ...(definition.env ? { environment: materializeEnv(definition.env, userEnv, userEnvRefs) } : {}),`,
|
|
10469
10527
|
` }`,
|
|
10470
10528
|
` continue`,
|
|
10471
10529
|
` }`,
|
|
@@ -10482,12 +10540,12 @@ var OpenCodeGenerator = class extends Generator {
|
|
|
10482
10540
|
` }`,
|
|
10483
10541
|
"",
|
|
10484
10542
|
` if (definition.auth?.type === "bearer" && definition.auth.envVar) {`,
|
|
10485
|
-
` const token = resolveRuntimeValue(definition.auth.envVar, userEnv)`,
|
|
10543
|
+
` const token = resolveRuntimeValue(definition.auth.envVar, userEnv, userEnvRefs)`,
|
|
10486
10544
|
` if (token) remote.headers = { Authorization: \`Bearer \${token}\` }`,
|
|
10487
10545
|
` }`,
|
|
10488
10546
|
"",
|
|
10489
10547
|
` if (definition.auth?.type === "header" && definition.auth.envVar && definition.auth.headerName && definition.auth.headerTemplate) {`,
|
|
10490
|
-
` const value = resolveRuntimeValue(definition.auth.envVar, userEnv)`,
|
|
10548
|
+
` const value = resolveRuntimeValue(definition.auth.envVar, userEnv, userEnvRefs)`,
|
|
10491
10549
|
` if (value) {`,
|
|
10492
10550
|
` remote.headers = {`,
|
|
10493
10551
|
` ...(remote.headers ?? {}),`,
|
|
@@ -10517,10 +10575,15 @@ var OpenCodeGenerator = class extends Generator {
|
|
|
10517
10575
|
` const shellSingleQuote = (input: string): string => \`'\${String(input ?? "").replace(/'/g, \`'"'"'\`)}'\``,
|
|
10518
10576
|
"",
|
|
10519
10577
|
` const buildHookShellCommand = (rawCommand: string): string => {`,
|
|
10520
|
-
` const
|
|
10578
|
+
` const userConfig = loadUserConfig(directory)`,
|
|
10579
|
+
` const userEnv = userConfig.env ?? {}`,
|
|
10580
|
+
` const userEnvRefs = userConfig.envRefs ?? {}`,
|
|
10521
10581
|
` const exports = Object.entries(userEnv)`,
|
|
10522
10582
|
` .filter(([key]) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(key))`,
|
|
10523
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]))}\`))`,
|
|
10524
10587
|
` .join("; ")`,
|
|
10525
10588
|
` const command = rawCommand.replaceAll("\${PLUGIN_ROOT}", directory)`,
|
|
10526
10589
|
` return exports ? \`\${exports}; \${command}\` : command`,
|
|
@@ -11318,6 +11381,7 @@ import { existsSync as existsSync18, lstatSync as lstatSync2, readdirSync as rea
|
|
|
11318
11381
|
import { resolve as resolve14, relative as relative8, basename as basename5, dirname as dirname4 } from "path";
|
|
11319
11382
|
|
|
11320
11383
|
// src/validation/platform-rules.ts
|
|
11384
|
+
var CORE_FOUR_PLATFORMS = ["claude-code", "cursor", "codex", "opencode"];
|
|
11321
11385
|
var STANDARD_SKILL_FRONTMATTER = [
|
|
11322
11386
|
"name",
|
|
11323
11387
|
"description",
|
|
@@ -11446,7 +11510,7 @@ var PLATFORM_LIMIT_POLICIES = {
|
|
|
11446
11510
|
},
|
|
11447
11511
|
hooksFeatureFlag: {
|
|
11448
11512
|
kind: "hard",
|
|
11449
|
-
notes: "
|
|
11513
|
+
notes: "Codex hook support depends on the canonical hooks feature flag plus enabled-plugin, trust/review, and runtime support."
|
|
11450
11514
|
}
|
|
11451
11515
|
},
|
|
11452
11516
|
"cursor": {
|
|
@@ -11646,8 +11710,8 @@ var PLATFORM_VALIDATION_RULES = {
|
|
|
11646
11710
|
hooks: {
|
|
11647
11711
|
supported: true,
|
|
11648
11712
|
files: ["hooks/hooks.json", ".codex/hooks.json", "~/.codex/hooks.json"],
|
|
11649
|
-
eventNames: [
|
|
11650
|
-
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."
|
|
11651
11715
|
},
|
|
11652
11716
|
instructions: {
|
|
11653
11717
|
files: ["AGENTS.md", "AGENTS.override.md"],
|
|
@@ -12086,7 +12150,7 @@ var CORE_FOUR_PRIMITIVE_CAPABILITIES = {
|
|
|
12086
12150
|
hooks: {
|
|
12087
12151
|
mode: "translate",
|
|
12088
12152
|
nativeSurfaces: ["hooks/hooks.json", ".codex/hooks.json", "~/.codex/hooks.json"],
|
|
12089
|
-
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."
|
|
12090
12154
|
},
|
|
12091
12155
|
permissions: {
|
|
12092
12156
|
mode: "translate",
|
|
@@ -12157,7 +12221,6 @@ function getCoreFourPrimitiveCapabilities(platform) {
|
|
|
12157
12221
|
}
|
|
12158
12222
|
|
|
12159
12223
|
// src/cli/primitive-summary.ts
|
|
12160
|
-
var CORE_FOUR_ORDER = ["claude-code", "cursor", "codex", "opencode"];
|
|
12161
12224
|
var TARGET_LABELS = {
|
|
12162
12225
|
"claude-code": "claude",
|
|
12163
12226
|
cursor: "cursor",
|
|
@@ -12171,7 +12234,7 @@ var MODE_LABELS = {
|
|
|
12171
12234
|
drop: "drop"
|
|
12172
12235
|
};
|
|
12173
12236
|
function buildPrimitiveTranslationSummary(config, targets = config.targets) {
|
|
12174
|
-
const selectedTargets =
|
|
12237
|
+
const selectedTargets = CORE_FOUR_PLATFORMS.filter((target) => targets.includes(target));
|
|
12175
12238
|
if (selectedTargets.length === 0) return void 0;
|
|
12176
12239
|
const configuredBuckets = getConfiguredCompilerBuckets({
|
|
12177
12240
|
...config,
|
|
@@ -13361,7 +13424,7 @@ function lintCodexHooksExternalConfig(config, issues) {
|
|
|
13361
13424
|
pushIssue(issues, {
|
|
13362
13425
|
level: "warning",
|
|
13363
13426
|
code: "codex-hooks-external-config",
|
|
13364
|
-
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.`,
|
|
13365
13428
|
file: "pluxx.config.ts",
|
|
13366
13429
|
platform: "Codex"
|
|
13367
13430
|
});
|
|
@@ -13370,7 +13433,7 @@ function lintCodexHooksExternalConfig(config, issues) {
|
|
|
13370
13433
|
pushIssue(issues, {
|
|
13371
13434
|
level: "warning",
|
|
13372
13435
|
code: "codex-hooks-legacy-feature-flag",
|
|
13373
|
-
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]\`.`,
|
|
13374
13437
|
file: "pluxx.config.ts",
|
|
13375
13438
|
platform: "Codex"
|
|
13376
13439
|
});
|
|
@@ -13379,7 +13442,7 @@ function lintCodexHooksExternalConfig(config, issues) {
|
|
|
13379
13442
|
pushIssue(issues, {
|
|
13380
13443
|
level: "warning",
|
|
13381
13444
|
code: "codex-hooks-general-feature-flag-only",
|
|
13382
|
-
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]\`.`,
|
|
13383
13446
|
file: "pluxx.config.ts",
|
|
13384
13447
|
platform: "Codex"
|
|
13385
13448
|
});
|
|
@@ -17992,7 +18055,7 @@ ${additions.map((block) => `- ${block.replace(/\n/g, "\n ")}`).join("\n")}
|
|
|
17992
18055
|
import { spawn as spawn3, spawnSync as spawnSync2 } from "child_process";
|
|
17993
18056
|
import { accessSync, constants, existsSync as existsSync24, lstatSync as lstatSync3, readFileSync as readFileSync14, readdirSync as readdirSync10, realpathSync } from "fs";
|
|
17994
18057
|
import { homedir as homedir2 } from "os";
|
|
17995
|
-
import { basename as basename7, dirname as dirname7, resolve as resolve20 } from "path";
|
|
18058
|
+
import { basename as basename7, dirname as dirname7, relative as relative12, resolve as resolve20 } from "path";
|
|
17996
18059
|
|
|
17997
18060
|
// src/cli/install.ts
|
|
17998
18061
|
import { resolve as resolve19, dirname as dirname6, basename as basename6, relative as relative11 } from "path";
|
|
@@ -18479,19 +18542,25 @@ function createOpenCodeCopiedInstall(target, pluginName) {
|
|
|
18479
18542
|
writeOpenCodeEntryFile(target.pluginDir, pluginName);
|
|
18480
18543
|
syncOpenCodeSkills(target.pluginDir, pluginName);
|
|
18481
18544
|
}
|
|
18482
|
-
function materializeTemplateValue(value, env) {
|
|
18483
|
-
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
|
+
);
|
|
18484
18550
|
}
|
|
18485
|
-
function materializeEnvRecord(input, env) {
|
|
18551
|
+
function materializeEnvRecord(input, env, secretEnvVars = /* @__PURE__ */ new Set()) {
|
|
18486
18552
|
const output = {};
|
|
18487
18553
|
for (const [key, value] of Object.entries(input ?? {})) {
|
|
18488
|
-
output[key] = materializeTemplateValue(value, env);
|
|
18554
|
+
output[key] = materializeTemplateValue(value, env, secretEnvVars);
|
|
18489
18555
|
}
|
|
18490
18556
|
return output;
|
|
18491
18557
|
}
|
|
18492
18558
|
function patchInstalledMcpConfig(pluginDir, platform, config, entries) {
|
|
18493
18559
|
if (!config.mcp) return;
|
|
18494
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
|
+
);
|
|
18495
18564
|
if (platform === "claude-code" || platform === "cursor") {
|
|
18496
18565
|
const filepath = resolve19(pluginDir, platform === "claude-code" ? ".mcp.json" : "mcp.json");
|
|
18497
18566
|
if (!existsSync23(filepath)) return;
|
|
@@ -18536,7 +18605,7 @@ function patchInstalledMcpConfig(pluginDir, platform, config, entries) {
|
|
|
18536
18605
|
mcpServers[name] = {
|
|
18537
18606
|
command: materializeInstalledPluginOwnedStdioPathForPlatform(server.command, platform, pluginDir),
|
|
18538
18607
|
args: (server.args ?? []).map((value) => materializeInstalledPluginOwnedStdioPathForPlatform(value, platform, pluginDir)),
|
|
18539
|
-
env: materializeEnvRecord(server.env, env)
|
|
18608
|
+
env: materializeEnvRecord(server.env, env, secretEnvVars)
|
|
18540
18609
|
};
|
|
18541
18610
|
continue;
|
|
18542
18611
|
}
|
|
@@ -18546,49 +18615,46 @@ function patchInstalledMcpConfig(pluginDir, platform, config, entries) {
|
|
|
18546
18615
|
const nativeOverride = getNativeCodexMcpEntryOverride(config, name);
|
|
18547
18616
|
if (nativeOverride) {
|
|
18548
18617
|
if (typeof nativeOverride.bearer_token_env_var === "string") {
|
|
18549
|
-
|
|
18550
|
-
if (env[envVar]) {
|
|
18551
|
-
entry.bearer_token_env_var = envVar;
|
|
18552
|
-
}
|
|
18618
|
+
entry.bearer_token_env_var = nativeOverride.bearer_token_env_var;
|
|
18553
18619
|
}
|
|
18554
18620
|
if (nativeOverride.auth) {
|
|
18555
18621
|
entry.auth = nativeOverride.auth;
|
|
18556
18622
|
}
|
|
18557
18623
|
if (nativeOverride.env_http_headers && typeof nativeOverride.env_http_headers === "object") {
|
|
18558
|
-
entry.
|
|
18559
|
-
Object.entries(nativeOverride.env_http_headers).filter(([, value]) => typeof value === "string").map(([headerName, envVar]) => [
|
|
18560
|
-
headerName,
|
|
18561
|
-
env[envVar] ?? `\${${envVar}}`
|
|
18562
|
-
])
|
|
18624
|
+
entry.env_http_headers = Object.fromEntries(
|
|
18625
|
+
Object.entries(nativeOverride.env_http_headers).filter(([, value]) => typeof value === "string").map(([headerName, envVar]) => [headerName, envVar])
|
|
18563
18626
|
);
|
|
18564
18627
|
}
|
|
18565
18628
|
if (nativeOverride.http_headers && typeof nativeOverride.http_headers === "object") {
|
|
18566
18629
|
entry.http_headers = materializeEnvRecord(
|
|
18567
18630
|
nativeOverride.http_headers,
|
|
18568
|
-
env
|
|
18631
|
+
env,
|
|
18632
|
+
secretEnvVars
|
|
18569
18633
|
);
|
|
18570
18634
|
}
|
|
18571
|
-
} else if (server.auth?.type === "bearer" && server.auth.envVar
|
|
18572
|
-
entry.
|
|
18573
|
-
|
|
18574
|
-
};
|
|
18575
|
-
|
|
18576
|
-
|
|
18577
|
-
|
|
18578
|
-
|
|
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
|
+
}
|
|
18579
18646
|
}
|
|
18580
18647
|
mcpServers[name] = entry;
|
|
18581
18648
|
}
|
|
18582
18649
|
writeFileSync4(filepath, JSON.stringify({ mcpServers }, null, 2) + "\n");
|
|
18583
18650
|
}
|
|
18584
18651
|
}
|
|
18585
|
-
function writeInstalledUserConfig(pluginDir, entries) {
|
|
18652
|
+
function writeInstalledUserConfig(pluginDir, entries, platform) {
|
|
18586
18653
|
if (entries.length === 0) return;
|
|
18587
18654
|
const filepath = resolve19(pluginDir, ".pluxx-user.json");
|
|
18588
|
-
const payload = {
|
|
18589
|
-
|
|
18590
|
-
|
|
18591
|
-
};
|
|
18655
|
+
const payload = buildInstalledUserConfigPayload(entries, {
|
|
18656
|
+
preserveSecretReferences: platform === "codex"
|
|
18657
|
+
});
|
|
18592
18658
|
writeFileSync4(filepath, JSON.stringify(payload, null, 2) + "\n");
|
|
18593
18659
|
}
|
|
18594
18660
|
function disableInstalledEnvValidation(pluginDir, entries) {
|
|
@@ -18602,7 +18668,7 @@ function disableInstalledEnvValidation(pluginDir, entries) {
|
|
|
18602
18668
|
}
|
|
18603
18669
|
function materializeInstalledPlugin(pluginDir, platform, config, entries) {
|
|
18604
18670
|
if (entries.length > 0) {
|
|
18605
|
-
writeInstalledUserConfig(pluginDir, entries);
|
|
18671
|
+
writeInstalledUserConfig(pluginDir, entries, platform);
|
|
18606
18672
|
disableInstalledEnvValidation(pluginDir, entries);
|
|
18607
18673
|
}
|
|
18608
18674
|
patchInstalledMcpConfig(pluginDir, platform, config, entries);
|
|
@@ -19169,6 +19235,13 @@ var LOW_INFO_DESCRIPTION_PATTERNS = [
|
|
|
19169
19235
|
var MATERIALIZED_ENV_MARKER = "materialized required config";
|
|
19170
19236
|
var MIN_NODE_MAJOR = 18;
|
|
19171
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
|
+
];
|
|
19172
19245
|
var PRIMITIVE_MODE_LEVEL = {
|
|
19173
19246
|
preserve: "success",
|
|
19174
19247
|
translate: "info",
|
|
@@ -19181,6 +19254,106 @@ function renderInstalledPluginRoot(value, rootDir) {
|
|
|
19181
19254
|
function addCheck2(checks, check) {
|
|
19182
19255
|
checks.push(check);
|
|
19183
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
|
+
}
|
|
19184
19357
|
function summarizeChecks2(checks) {
|
|
19185
19358
|
const visibleChecks = checks.filter((check) => !check.code.startsWith("primitive-"));
|
|
19186
19359
|
const errors = visibleChecks.filter((check) => check.level === "error").length;
|
|
@@ -19805,7 +19978,7 @@ function readCodexHooksFeatureFlag(filePath) {
|
|
|
19805
19978
|
inFeaturesTable = sectionMatch[1].trim() === "features";
|
|
19806
19979
|
continue;
|
|
19807
19980
|
}
|
|
19808
|
-
const dottedFeatureMatch = line.match(/^features\.(
|
|
19981
|
+
const dottedFeatureMatch = line.match(/^features\.(hooks|codex_hooks)\s*=\s*(.+)$/);
|
|
19809
19982
|
if (dottedFeatureMatch) {
|
|
19810
19983
|
assignFeatureFlag(dottedFeatureMatch[1], dottedFeatureMatch[2]);
|
|
19811
19984
|
continue;
|
|
@@ -19822,7 +19995,7 @@ function readCodexHooksFeatureFlag(filePath) {
|
|
|
19822
19995
|
continue;
|
|
19823
19996
|
}
|
|
19824
19997
|
if (!inFeaturesTable) continue;
|
|
19825
|
-
const featureMatch = line.match(/^(
|
|
19998
|
+
const featureMatch = line.match(/^(hooks|codex_hooks)\s*=\s*(.+)$/);
|
|
19826
19999
|
if (!featureMatch) continue;
|
|
19827
20000
|
assignFeatureFlag(featureMatch[1], featureMatch[2]);
|
|
19828
20001
|
}
|
|
@@ -20441,20 +20614,20 @@ function checkInstalledCodexHooksFeatureFlag(checks, rootDir, layout, options) {
|
|
|
20441
20614
|
const enabledProbes = probes.filter((probe) => probe.enabled);
|
|
20442
20615
|
const checkedPaths = probes.map((probe) => `${probe.scope} ${probe.exists ? "config" : "path"}: ${probe.path}${probe.exists ? "" : " (missing)"}`).join("; ");
|
|
20443
20616
|
const describeEnabledFlags = (probe) => {
|
|
20444
|
-
const enabledFlags =
|
|
20445
|
-
if (probe.pluginBundledEnabled) enabledFlags.
|
|
20446
|
-
if (probe.recommendedEnabled) enabledFlags.
|
|
20447
|
-
if (probe.alternateEnabled) enabledFlags.
|
|
20448
|
-
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(" + ");
|
|
20449
20622
|
};
|
|
20450
20623
|
const generalOnlyProbes = probes.filter((probe) => !probe.pluginBundledEnabled && (probe.recommendedEnabled || probe.alternateEnabled));
|
|
20451
20624
|
if (generalOnlyProbes.length > 0) {
|
|
20452
20625
|
addCheck2(checks, {
|
|
20453
20626
|
level: "warning",
|
|
20454
20627
|
code: "consumer-codex-plugin-hooks-feature-flag-general-only",
|
|
20455
|
-
title: "Codex config enables only
|
|
20456
|
-
detail: `This installed Codex bundle declares plugin-bundled hooks at ${hooksReference}, but ${generalOnlyProbes.map((probe) => `${probe.scope} config ${probe.path}`).join(" and ")} enables only
|
|
20457
|
-
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.`,
|
|
20458
20631
|
path: generalOnlyProbes[0].path
|
|
20459
20632
|
});
|
|
20460
20633
|
}
|
|
@@ -20464,8 +20637,8 @@ function checkInstalledCodexHooksFeatureFlag(checks, rootDir, layout, options) {
|
|
|
20464
20637
|
level: "warning",
|
|
20465
20638
|
code: "consumer-codex-hooks-feature-flag-legacy-only",
|
|
20466
20639
|
title: "Codex config still uses the deprecated general hook compatibility flag",
|
|
20467
|
-
detail: `This installed Codex bundle declares plugin-bundled hooks at ${hooksReference}, and the checked Codex config enables only the deprecated
|
|
20468
|
-
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.`,
|
|
20469
20642
|
path: legacyOnlyProbes[0].path
|
|
20470
20643
|
});
|
|
20471
20644
|
}
|
|
@@ -20473,8 +20646,8 @@ function checkInstalledCodexHooksFeatureFlag(checks, rootDir, layout, options) {
|
|
|
20473
20646
|
addCheck2(checks, {
|
|
20474
20647
|
level: "success",
|
|
20475
20648
|
code: "consumer-codex-hooks-feature-flag-enabled",
|
|
20476
|
-
title: "Codex
|
|
20477
|
-
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.`,
|
|
20478
20651
|
fix: "No action needed.",
|
|
20479
20652
|
path: enabledProbes[0].path
|
|
20480
20653
|
});
|
|
@@ -20485,8 +20658,8 @@ function checkInstalledCodexHooksFeatureFlag(checks, rootDir, layout, options) {
|
|
|
20485
20658
|
level: "warning",
|
|
20486
20659
|
code: "consumer-codex-hooks-feature-flag-missing",
|
|
20487
20660
|
title: "Codex plugin-bundled hook activation is missing its known feature-gate prerequisite",
|
|
20488
|
-
detail: `This installed Codex bundle declares plugin-bundled hooks at ${hooksReference}, but \`${
|
|
20489
|
-
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.`,
|
|
20490
20663
|
path: probes.find((probe) => probe.exists)?.path ?? hooksReference
|
|
20491
20664
|
});
|
|
20492
20665
|
}
|
|
@@ -21020,6 +21193,7 @@ async function doctorConsumer(rootDir = process.cwd(), options = {}) {
|
|
|
21020
21193
|
checkInstalledCodexPermissionCompanion(checks, rootDir, layout, options);
|
|
21021
21194
|
checkInstalledPermissionHook(checks, rootDir, layout);
|
|
21022
21195
|
checkInstalledUserConfig(checks, rootDir);
|
|
21196
|
+
checkInstalledPlaintextSecrets(checks, rootDir);
|
|
21023
21197
|
checkInstalledEnvValidation(checks, rootDir);
|
|
21024
21198
|
checkInstalledRuntimeScriptRoles(checks, rootDir);
|
|
21025
21199
|
await checkInstalledMcpConfig(checks, rootDir, layout);
|
|
@@ -21132,7 +21306,7 @@ function printDoctorReport(report) {
|
|
|
21132
21306
|
|
|
21133
21307
|
// src/cli/dev.ts
|
|
21134
21308
|
import { watch } from "fs";
|
|
21135
|
-
import { relative as
|
|
21309
|
+
import { relative as relative13, resolve as resolve21 } from "path";
|
|
21136
21310
|
var WATCH_PATTERNS = [
|
|
21137
21311
|
/^pluxx\.config\.(ts|js|json)$/,
|
|
21138
21312
|
/^skills\//,
|
|
@@ -21158,7 +21332,7 @@ async function runDev(args2) {
|
|
|
21158
21332
|
let pendingFile = null;
|
|
21159
21333
|
const watcher = watch(rootDir, { recursive: true }, (_event, filename) => {
|
|
21160
21334
|
if (!filename) return;
|
|
21161
|
-
const rel =
|
|
21335
|
+
const rel = relative13(rootDir, resolve21(rootDir, filename));
|
|
21162
21336
|
if (rel.startsWith("dist/") || rel.startsWith(".") || rel.includes("node_modules")) {
|
|
21163
21337
|
return;
|
|
21164
21338
|
}
|
|
@@ -21213,7 +21387,7 @@ async function runBuild(rootDir, targets) {
|
|
|
21213
21387
|
}
|
|
21214
21388
|
|
|
21215
21389
|
// src/cli/migrate.ts
|
|
21216
|
-
import { basename as basename8, dirname as dirname8, relative as
|
|
21390
|
+
import { basename as basename8, dirname as dirname8, relative as relative14, resolve as resolve22 } from "path";
|
|
21217
21391
|
import { existsSync as existsSync25, readdirSync as readdirSync11, mkdirSync as mkdirSync5, cpSync as cpSync4, readFileSync as readFileSync15, writeFileSync as writeFileSync5 } from "fs";
|
|
21218
21392
|
function detectPlatform(pluginDir) {
|
|
21219
21393
|
const checks = [
|
|
@@ -21586,7 +21760,7 @@ function parseCursorRuleFile(pluginDir, filePath) {
|
|
|
21586
21760
|
...globs !== void 0 ? { globs } : {},
|
|
21587
21761
|
...alwaysApply !== void 0 ? { alwaysApply } : {},
|
|
21588
21762
|
...body.trim() ? { content: body.trim() } : {},
|
|
21589
|
-
path: `./${
|
|
21763
|
+
path: `./${relative14(pluginDir, filePath).replace(/\\/g, "/")}`
|
|
21590
21764
|
};
|
|
21591
21765
|
}
|
|
21592
21766
|
function collectFiles(dir, predicate) {
|
|
@@ -21631,7 +21805,7 @@ function findInstructionSources(pluginDir, detection) {
|
|
|
21631
21805
|
const extraPaths = /* @__PURE__ */ new Set();
|
|
21632
21806
|
const platformOverrides = {};
|
|
21633
21807
|
if (detection.platform === "cursor") {
|
|
21634
|
-
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);
|
|
21635
21809
|
if (nestedAgents.length > 0) {
|
|
21636
21810
|
platformOverrides.cursor = {
|
|
21637
21811
|
...platformOverrides.cursor ?? {},
|
|
@@ -22350,7 +22524,7 @@ function normalizeMigratedOpenCodeAgents(destDir) {
|
|
|
22350
22524
|
const normalized = [];
|
|
22351
22525
|
for (const filePath of walkMarkdownFiles3(destDir)) {
|
|
22352
22526
|
if (normalizeMigratedOpenCodeAgentFile(filePath)) {
|
|
22353
|
-
normalized.push(
|
|
22527
|
+
normalized.push(relative14(destDir, filePath).replace(/\\/g, "/"));
|
|
22354
22528
|
}
|
|
22355
22529
|
}
|
|
22356
22530
|
return normalized.sort();
|
|
@@ -24497,8 +24671,15 @@ echo
|
|
|
24497
24671
|
echo "Installed __DISPLAY_NAME__ across ${installerTargets.join(", ")}."
|
|
24498
24672
|
`.replaceAll("__REPO__", "REPO_PLACEHOLDER").replaceAll("__DISPLAY_NAME__", "DISPLAY_PLACEHOLDER");
|
|
24499
24673
|
}
|
|
24674
|
+
function collectInstallerUserConfigEntries(config, platforms) {
|
|
24675
|
+
const baseEntries = collectUserConfigEntries(config, platforms);
|
|
24676
|
+
return [
|
|
24677
|
+
...baseEntries,
|
|
24678
|
+
...collectNativeMcpAuthUserConfigEntries(config, platforms, baseEntries)
|
|
24679
|
+
];
|
|
24680
|
+
}
|
|
24500
24681
|
function renderInstallerUserConfigSnippet(config, platform, installDirVariable) {
|
|
24501
|
-
const entries =
|
|
24682
|
+
const entries = collectInstallerUserConfigEntries(config, [platform]).map((entry) => ({
|
|
24502
24683
|
key: entry.key,
|
|
24503
24684
|
title: entry.title,
|
|
24504
24685
|
type: entry.type ?? "string",
|
|
@@ -24506,6 +24687,7 @@ function renderInstallerUserConfigSnippet(config, platform, installDirVariable)
|
|
|
24506
24687
|
envVar: entry.envVar ?? defaultUserConfigEnvVar(entry.key)
|
|
24507
24688
|
}));
|
|
24508
24689
|
if (entries.length === 0) return "";
|
|
24690
|
+
const preserveSecretReferences = platform === "codex";
|
|
24509
24691
|
const promptLines = entries.map((entry) => {
|
|
24510
24692
|
const functionName = entry.type === "secret" ? "pluxx_prompt_secret_config" : "pluxx_prompt_text_config";
|
|
24511
24693
|
return `${functionName} ${JSON.stringify(entry.key)} ${JSON.stringify(entry.envVar)} ${JSON.stringify(entry.title)} ${entry.required ? "1" : "0"}`;
|
|
@@ -24516,6 +24698,8 @@ ${JSON.stringify(entries)}
|
|
|
24516
24698
|
PLUXX_USER_CONFIG_JSON
|
|
24517
24699
|
)"
|
|
24518
24700
|
PLUXX_REUSED_USER_CONFIG=0
|
|
24701
|
+
PLUXX_PRESERVE_SECRET_REFS="${preserveSecretReferences ? "1" : "0"}"
|
|
24702
|
+
export PLUXX_PRESERVE_SECRET_REFS
|
|
24519
24703
|
|
|
24520
24704
|
pluxx_is_placeholder_secret() {
|
|
24521
24705
|
case "$1" in
|
|
@@ -24542,12 +24726,14 @@ const fs = require('fs')
|
|
|
24542
24726
|
const filepath = process.env.PLUXX_SAVED_USER_CONFIG_PATH
|
|
24543
24727
|
const key = process.env.PLUXX_SAVED_CONFIG_KEY
|
|
24544
24728
|
const envVar = process.env.PLUXX_SAVED_CONFIG_ENV_VAR
|
|
24729
|
+
const preserveSecretRefs = process.env.PLUXX_PRESERVE_SECRET_REFS === '1'
|
|
24545
24730
|
|
|
24546
24731
|
try {
|
|
24547
24732
|
const payload = JSON.parse(fs.readFileSync(filepath, 'utf8'))
|
|
24548
24733
|
const candidates = [
|
|
24549
24734
|
payload && payload.env && envVar ? payload.env[envVar] : undefined,
|
|
24550
24735
|
payload && payload.values && key ? payload.values[key] : undefined,
|
|
24736
|
+
preserveSecretRefs && payload && payload.envRefs && envVar && payload.envRefs[envVar] === envVar ? envVar : undefined,
|
|
24551
24737
|
]
|
|
24552
24738
|
|
|
24553
24739
|
for (const candidate of candidates) {
|
|
@@ -24643,21 +24829,53 @@ const path = require('path')
|
|
|
24643
24829
|
|
|
24644
24830
|
const installDir = process.env.PLUXX_INSTALL_DIR
|
|
24645
24831
|
const spec = JSON.parse(process.env.PLUXX_USER_CONFIG_SPEC || '[]')
|
|
24832
|
+
const preserveSecretReferences = ${preserveSecretReferences ? "true" : "false"}
|
|
24646
24833
|
|
|
24647
24834
|
if (installDir && spec.length > 0) {
|
|
24648
24835
|
const env = {}
|
|
24649
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
|
+
)
|
|
24650
24846
|
|
|
24651
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
|
+
}
|
|
24652
24855
|
const value = process.env[entry.envVar]
|
|
24653
24856
|
if (value === undefined || value === '') continue
|
|
24857
|
+
if (preserveSecretReferences && entry.type === 'secret') {
|
|
24858
|
+
envRefs[entry.envVar] = entry.envVar
|
|
24859
|
+
continue
|
|
24860
|
+
}
|
|
24654
24861
|
values[entry.key] = value
|
|
24655
24862
|
env[entry.envVar] = value
|
|
24656
24863
|
}
|
|
24657
24864
|
|
|
24658
24865
|
fs.writeFileSync(
|
|
24659
24866
|
path.join(installDir, '.pluxx-user.json'),
|
|
24660
|
-
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',
|
|
24661
24879
|
)
|
|
24662
24880
|
|
|
24663
24881
|
const envScriptPath = path.join(installDir, 'scripts/check-env.sh')
|
|
@@ -24670,7 +24888,10 @@ if (installDir && spec.length > 0) {
|
|
|
24670
24888
|
|
|
24671
24889
|
const materialize = (value) =>
|
|
24672
24890
|
typeof value === 'string'
|
|
24673
|
-
? 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
|
+
)
|
|
24674
24895
|
: value
|
|
24675
24896
|
|
|
24676
24897
|
const materializeRecord = (record) => {
|
|
@@ -24694,7 +24915,7 @@ if (installDir && spec.length > 0) {
|
|
|
24694
24915
|
server.env = materializeRecord(server.env)
|
|
24695
24916
|
}
|
|
24696
24917
|
|
|
24697
|
-
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]) {
|
|
24698
24919
|
server.http_headers = {
|
|
24699
24920
|
...(server.http_headers || {}),
|
|
24700
24921
|
Authorization: 'Bearer ' + env[server.bearer_token_env_var],
|
|
@@ -24702,7 +24923,7 @@ if (installDir && spec.length > 0) {
|
|
|
24702
24923
|
delete server.bearer_token_env_var
|
|
24703
24924
|
}
|
|
24704
24925
|
|
|
24705
|
-
if (server.env_http_headers && typeof server.env_http_headers === 'object') {
|
|
24926
|
+
if (!preserveSecretReferences && server.env_http_headers && typeof server.env_http_headers === 'object') {
|
|
24706
24927
|
server.http_headers = {
|
|
24707
24928
|
...(server.http_headers || {}),
|
|
24708
24929
|
}
|
|
@@ -24727,7 +24948,7 @@ NODE
|
|
|
24727
24948
|
`;
|
|
24728
24949
|
}
|
|
24729
24950
|
function hasInstallerUserConfig(config, platform) {
|
|
24730
|
-
return
|
|
24951
|
+
return collectInstallerUserConfigEntries(config, [platform]).length > 0;
|
|
24731
24952
|
}
|
|
24732
24953
|
function renderInstallerSavedUserConfigCaptureSnippet(config, platform, installDirVariable) {
|
|
24733
24954
|
if (!hasInstallerUserConfig(config, platform)) return "";
|
|
@@ -24893,13 +25114,13 @@ for (const line of lines) {
|
|
|
24893
25114
|
continue
|
|
24894
25115
|
}
|
|
24895
25116
|
if (tableName === '') {
|
|
24896
|
-
const dottedMatch = trimmed.match(/^features\\.
|
|
25117
|
+
const dottedMatch = trimmed.match(/^features\\.hooks\\s*=\\s*(.+)$/)
|
|
24897
25118
|
if (dottedMatch && isTomlTrue(dottedMatch[1])) process.exit(0)
|
|
24898
25119
|
const inlineMatch = trimmed.match(/^features\\s*=\\s*(.+)$/)
|
|
24899
|
-
if (inlineMatch && /\\
|
|
25120
|
+
if (inlineMatch && /\\bhooks\\s*=\\s*true\\b/i.test(inlineMatch[1])) process.exit(0)
|
|
24900
25121
|
}
|
|
24901
25122
|
if (tableName !== 'features') continue
|
|
24902
|
-
const match = trimmed.match(/^
|
|
25123
|
+
const match = trimmed.match(/^hooks\\s*=\\s*(.+)$/)
|
|
24903
25124
|
if (match && isTomlTrue(match[1])) process.exit(0)
|
|
24904
25125
|
}
|
|
24905
25126
|
process.exit(1)
|
|
@@ -24918,7 +25139,7 @@ NODE
|
|
|
24918
25139
|
*)
|
|
24919
25140
|
if [[ -r /dev/tty ]]; then
|
|
24920
25141
|
echo "This Codex plugin bundle includes startup hooks." >/dev/tty
|
|
24921
|
-
echo "Codex requires [features].
|
|
25142
|
+
echo "Codex requires [features].hooks = true before plugin-bundled hooks can run." >/dev/tty
|
|
24922
25143
|
read -r -p "Enable Codex plugin-bundled hooks in $CODEX_CONFIG_PATH now? [Y/n] " PLUXX_CODEX_HOOKS_REPLY </dev/tty
|
|
24923
25144
|
case "$PLUXX_CODEX_HOOKS_REPLY" in
|
|
24924
25145
|
n|N|no|NO)
|
|
@@ -24990,7 +25211,7 @@ for (let index = 0; index < lines.length; index += 1) {
|
|
|
24990
25211
|
if (/^features\\.[A-Za-z0-9_-]+\\s*=/.test(trimmed) && firstTopLevelFeaturesDotted < 0) {
|
|
24991
25212
|
firstTopLevelFeaturesDotted = index
|
|
24992
25213
|
}
|
|
24993
|
-
if (/^features\\.
|
|
25214
|
+
if (/^features\\.hooks\\s*=/.test(trimmed)) {
|
|
24994
25215
|
topLevelPluginHooksDotted = index
|
|
24995
25216
|
}
|
|
24996
25217
|
if (/^features\\s*=\\s*\\{/.test(trimmed)) {
|
|
@@ -25009,28 +25230,28 @@ if (start >= 0) {
|
|
|
25009
25230
|
|
|
25010
25231
|
let updated = false
|
|
25011
25232
|
for (let index = start + 1; index < end; index += 1) {
|
|
25012
|
-
if (/^
|
|
25013
|
-
lines[index] = '
|
|
25233
|
+
if (/^hooks\\s*=/.test(stripTomlComment(lines[index]).trim())) {
|
|
25234
|
+
lines[index] = 'hooks = true'
|
|
25014
25235
|
updated = true
|
|
25015
25236
|
}
|
|
25016
25237
|
}
|
|
25017
|
-
if (!updated) lines.splice(start + 1, 0, '
|
|
25238
|
+
if (!updated) lines.splice(start + 1, 0, 'hooks = true')
|
|
25018
25239
|
} else if (topLevelPluginHooksDotted >= 0) {
|
|
25019
|
-
lines[topLevelPluginHooksDotted] = 'features.
|
|
25240
|
+
lines[topLevelPluginHooksDotted] = 'features.hooks = true'
|
|
25020
25241
|
} else if (firstTopLevelFeaturesDotted >= 0) {
|
|
25021
|
-
lines.splice(firstTopLevelFeaturesDotted + 1, 0, 'features.
|
|
25242
|
+
lines.splice(firstTopLevelFeaturesDotted + 1, 0, 'features.hooks = true')
|
|
25022
25243
|
} else if (topLevelInlineFeatures >= 0 && lines[topLevelInlineFeatures].includes('}')) {
|
|
25023
|
-
if (/\\
|
|
25244
|
+
if (/\\bhooks\\s*=/.test(lines[topLevelInlineFeatures])) {
|
|
25024
25245
|
lines[topLevelInlineFeatures] = lines[topLevelInlineFeatures].replace(
|
|
25025
|
-
/\\
|
|
25026
|
-
'
|
|
25246
|
+
/\\bhooks\\s*=\\s*(true|false)\\b/i,
|
|
25247
|
+
'hooks = true',
|
|
25027
25248
|
)
|
|
25028
25249
|
} else {
|
|
25029
|
-
lines[topLevelInlineFeatures] = lines[topLevelInlineFeatures].replace(/}/, ',
|
|
25250
|
+
lines[topLevelInlineFeatures] = lines[topLevelInlineFeatures].replace(/}/, ', hooks = true }')
|
|
25030
25251
|
}
|
|
25031
25252
|
} else {
|
|
25032
25253
|
if (lines.length > 0 && lines[lines.length - 1] !== '') lines.push('')
|
|
25033
|
-
lines.push('[features]', '
|
|
25254
|
+
lines.push('[features]', 'hooks = true')
|
|
25034
25255
|
}
|
|
25035
25256
|
|
|
25036
25257
|
fs.mkdirSync(path.dirname(filepath), { recursive: true })
|
|
@@ -25041,7 +25262,7 @@ NODE
|
|
|
25041
25262
|
else
|
|
25042
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
|
|
25043
25264
|
echo "[features]" >&2
|
|
25044
|
-
echo "
|
|
25265
|
+
echo "hooks = true" >&2
|
|
25045
25266
|
echo "Then restart or refresh Codex before relying on plugin startup hooks." >&2
|
|
25046
25267
|
echo "Set PLUXX_CODEX_ENABLE_PLUGIN_HOOKS=1 before running this installer to enable it noninteractively." >&2
|
|
25047
25268
|
fi
|
|
@@ -26235,6 +26456,7 @@ async function executeBehavioralCommand(platform, command2, cwd, timeoutMs) {
|
|
|
26235
26456
|
};
|
|
26236
26457
|
}
|
|
26237
26458
|
return await new Promise((resolvePromise, reject) => {
|
|
26459
|
+
const startedAt = Date.now();
|
|
26238
26460
|
const child = spawn5(command2[0], command2.slice(1), {
|
|
26239
26461
|
cwd,
|
|
26240
26462
|
detached: process.platform !== "win32",
|
|
@@ -26243,15 +26465,30 @@ async function executeBehavioralCommand(platform, command2, cwd, timeoutMs) {
|
|
|
26243
26465
|
});
|
|
26244
26466
|
const stdoutChunks = [];
|
|
26245
26467
|
const stderrChunks = [];
|
|
26468
|
+
let settled = false;
|
|
26469
|
+
let timedOut = false;
|
|
26470
|
+
const timeout = setTimeout(() => {
|
|
26471
|
+
timedOut = true;
|
|
26472
|
+
signalBehavioralProcess(child, "SIGKILL");
|
|
26473
|
+
}, timeoutMs);
|
|
26246
26474
|
child.stdout?.on("data", (chunk) => stdoutChunks.push(Buffer.from(chunk)));
|
|
26247
26475
|
child.stderr?.on("data", (chunk) => stderrChunks.push(Buffer.from(chunk)));
|
|
26248
|
-
child.on("error",
|
|
26476
|
+
child.on("error", (error) => {
|
|
26477
|
+
if (settled) return;
|
|
26478
|
+
settled = true;
|
|
26479
|
+
clearTimeout(timeout);
|
|
26480
|
+
reject(error);
|
|
26481
|
+
});
|
|
26249
26482
|
child.on("close", (code) => {
|
|
26483
|
+
if (settled) return;
|
|
26484
|
+
settled = true;
|
|
26485
|
+
clearTimeout(timeout);
|
|
26486
|
+
const exceededDeadline = timedOut || Date.now() - startedAt > timeoutMs;
|
|
26250
26487
|
const stdout = Buffer.concat(stdoutChunks).toString("utf-8");
|
|
26251
26488
|
const stderr = Buffer.concat(stderrChunks).toString("utf-8");
|
|
26252
26489
|
resolvePromise({
|
|
26253
|
-
exitCode: code ?? 1,
|
|
26254
|
-
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()
|
|
26255
26492
|
});
|
|
26256
26493
|
});
|
|
26257
26494
|
});
|
|
@@ -26293,6 +26530,22 @@ async function commandSucceeds2(command2) {
|
|
|
26293
26530
|
child.on("error", () => resolvePromise(false));
|
|
26294
26531
|
});
|
|
26295
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
|
+
}
|
|
26296
26549
|
function truncate2(value, length) {
|
|
26297
26550
|
if (value.length <= length) return value;
|
|
26298
26551
|
return `${value.slice(0, Math.max(0, length - 3))}...`;
|
|
@@ -26314,7 +26567,7 @@ function shellQuote2(value) {
|
|
|
26314
26567
|
// src/cli/discover-installed-mcp.ts
|
|
26315
26568
|
import { existsSync as existsSync30, readFileSync as readFileSync21, realpathSync as realpathSync3 } from "fs";
|
|
26316
26569
|
import { homedir as homedir3 } from "os";
|
|
26317
|
-
import { isAbsolute as isAbsolute2, relative as
|
|
26570
|
+
import { isAbsolute as isAbsolute2, relative as relative15, resolve as resolve28 } from "path";
|
|
26318
26571
|
var INSTALLED_MCP_HOSTS = ["claude-code", "cursor", "codex", "opencode"];
|
|
26319
26572
|
function discoverInstalledMcpServers(options = {}) {
|
|
26320
26573
|
const rootDir = options.rootDir ?? process.cwd();
|
|
@@ -26453,13 +26706,13 @@ function buildClaudeProjectSourceScope(projectPath, rootDir, homeDir) {
|
|
|
26453
26706
|
return `projects/absolute/${normalizeScopePath(projectPath)}`;
|
|
26454
26707
|
}
|
|
26455
26708
|
function buildRelativeScopePath(baseDir, targetPath) {
|
|
26456
|
-
const relativePath =
|
|
26709
|
+
const relativePath = relative15(baseDir, targetPath);
|
|
26457
26710
|
if (!relativePath.startsWith("..") && !isAbsolute2(relativePath)) {
|
|
26458
26711
|
return normalizeScopePath(relativePath);
|
|
26459
26712
|
}
|
|
26460
26713
|
if (!existsSync30(baseDir) || !existsSync30(targetPath)) return void 0;
|
|
26461
26714
|
try {
|
|
26462
|
-
const realRelativePath =
|
|
26715
|
+
const realRelativePath = relative15(realpathSync3(baseDir), realpathSync3(targetPath));
|
|
26463
26716
|
if (realRelativePath.startsWith("..") || isAbsolute2(realRelativePath)) return void 0;
|
|
26464
26717
|
return normalizeScopePath(realRelativePath);
|
|
26465
26718
|
} catch {
|
|
@@ -26659,7 +26912,7 @@ function buildInstalledMcpSourceLabel(discovered, rootDir, homeDir) {
|
|
|
26659
26912
|
return discovered.sourceScope ? `${base}:${discovered.sourceScope}` : base;
|
|
26660
26913
|
}
|
|
26661
26914
|
function buildRelativeSelectorLabel(prefix, baseDir, sourcePath) {
|
|
26662
|
-
const relativePath =
|
|
26915
|
+
const relativePath = relative15(baseDir, sourcePath);
|
|
26663
26916
|
if (relativePath === "") return `${prefix}:.`;
|
|
26664
26917
|
if (relativePath.startsWith("..") || isAbsolute2(relativePath)) return void 0;
|
|
26665
26918
|
return `${prefix}:${relativePath.replace(/\\/g, "/")}`;
|