@orchid-labs/pluxx 0.1.15 → 0.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/claude-hook-probe.d.ts +70 -0
- package/dist/claude-hook-probe.d.ts.map +1 -0
- package/dist/cli/behavioral.d.ts +1 -0
- package/dist/cli/behavioral.d.ts.map +1 -1
- package/dist/cli/discover-installed-mcp.d.ts +1 -0
- package/dist/cli/discover-installed-mcp.d.ts.map +1 -1
- package/dist/cli/doctor.d.ts +4 -1
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/index.js +7225 -6161
- package/dist/cli/install.d.ts +1 -0
- package/dist/cli/install.d.ts.map +1 -1
- package/dist/cli/lint.d.ts.map +1 -1
- package/dist/cli/migrate.d.ts.map +1 -1
- package/dist/cli/verify-install.d.ts +7 -0
- package/dist/cli/verify-install.d.ts.map +1 -1
- package/dist/codex-agent-probe-shared.d.ts +26 -0
- package/dist/codex-agent-probe-shared.d.ts.map +1 -0
- package/dist/codex-agent-probe.d.ts +82 -0
- package/dist/codex-agent-probe.d.ts.map +1 -0
- package/dist/codex-exec-runner.d.ts +21 -0
- package/dist/codex-exec-runner.d.ts.map +1 -0
- package/dist/codex-hook-probe.d.ts +41 -0
- package/dist/codex-hook-probe.d.ts.map +1 -0
- package/dist/codex-hooks-feature.d.ts +10 -0
- package/dist/codex-hooks-feature.d.ts.map +1 -0
- package/dist/codex-interactive-agent-probe.d.ts +62 -0
- package/dist/codex-interactive-agent-probe.d.ts.map +1 -0
- package/dist/codex-interactive-hook-probe.d.ts +90 -0
- package/dist/codex-interactive-hook-probe.d.ts.map +1 -0
- package/dist/codex-interactive-probe-shared.d.ts +4 -0
- package/dist/codex-interactive-probe-shared.d.ts.map +1 -0
- package/dist/codex-mcp-probe.d.ts +77 -0
- package/dist/codex-mcp-probe.d.ts.map +1 -0
- package/dist/codex-permissions-companion.d.ts +19 -0
- package/dist/codex-permissions-companion.d.ts.map +1 -0
- package/dist/codex-probe-shared.d.ts +3 -0
- package/dist/codex-probe-shared.d.ts.map +1 -0
- package/dist/compiler-intent.d.ts +6 -6
- package/dist/generators/codex/index.d.ts +1 -0
- package/dist/generators/codex/index.d.ts.map +1 -1
- package/dist/hook-translation-registry.d.ts.map +1 -1
- package/dist/index.js +732 -26
- package/dist/toml-lite.d.ts +5 -0
- package/dist/toml-lite.d.ts.map +1 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -7855,7 +7855,7 @@ function getEnabledRuntimeReadinessBindings(capability, plan) {
|
|
|
7855
7855
|
});
|
|
7856
7856
|
}
|
|
7857
7857
|
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.";
|
|
7858
|
-
var CODEX_EXTERNAL_NOTE = "Codex readiness now bundles translated hooks in the plugin,
|
|
7858
|
+
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 some Codex runtimes still gate hook activation behind a `[features]` flag. Current Codex config surfaces still accept both `hooks = true` and `codex_hooks = true`, but maintained interactive probes on May 13, 2026 showed local Codex CLI 0.130.0 timing out without a project-local hook side effect under either flag while emitting a deprecation warning for `codex_hooks` that points users to `hooks`.";
|
|
7859
7859
|
function getRuntimeReadinessNamedPromptTargetNote() {
|
|
7860
7860
|
return NAMED_PROMPT_TARGET_NOTE;
|
|
7861
7861
|
}
|
|
@@ -7920,7 +7920,7 @@ function getRuntimeReadinessCapability(platform, pluginRootVar = "PLUGIN_ROOT")
|
|
|
7920
7920
|
case "codex":
|
|
7921
7921
|
return {
|
|
7922
7922
|
platform,
|
|
7923
|
-
delivery: "
|
|
7923
|
+
delivery: "bundled-hooks",
|
|
7924
7924
|
bundleEnforced: false,
|
|
7925
7925
|
namedPromptTargetScope: "best-effort",
|
|
7926
7926
|
scriptPath: ".codex/pluxx-readiness.mjs",
|
|
@@ -8024,7 +8024,7 @@ var PLATFORM_LIMITS = {
|
|
|
8024
8024
|
manifestPromptCountMax: 3,
|
|
8025
8025
|
manifestPathPrefix: "./",
|
|
8026
8026
|
instructionsMaxBytes: 32768,
|
|
8027
|
-
hooksFeatureFlag: "
|
|
8027
|
+
hooksFeatureFlag: "hooks"
|
|
8028
8028
|
},
|
|
8029
8029
|
"cursor": {
|
|
8030
8030
|
...NULL_LIMITS,
|
|
@@ -8178,17 +8178,17 @@ var PLATFORM_VALIDATION_RULES = {
|
|
|
8178
8178
|
notes: "The manifest is optional; if present, name is the only required field."
|
|
8179
8179
|
},
|
|
8180
8180
|
mcp: {
|
|
8181
|
-
files: [".mcp.json", ".claude-plugin/plugin.json"],
|
|
8181
|
+
files: [".mcp.json", "~/.claude.json", "managed-mcp.json", ".claude-plugin/plugin.json"],
|
|
8182
8182
|
rootKey: "mcpServers",
|
|
8183
8183
|
transports: ["stdio", "http", "sse"],
|
|
8184
8184
|
auth: ["headers", "env interpolation", "OAuth 2.0", "bearer tokens", "dynamic headers"],
|
|
8185
|
-
notes: "Claude Code
|
|
8185
|
+
notes: "Claude Code project-scoped MCP lives in .mcp.json, while local and user scopes live in ~/.claude.json; managed deployments can also pin managed-mcp.json. Current docs explicitly warn that settings.json does not read mcpServers."
|
|
8186
8186
|
},
|
|
8187
8187
|
hooks: {
|
|
8188
8188
|
supported: true,
|
|
8189
|
-
files: ["hooks/hooks.json", ".claude-plugin/plugin.json", "~/.claude/settings.json", ".claude/settings.json", ".claude/settings.local.json"],
|
|
8189
|
+
files: ["hooks/hooks.json", ".claude-plugin/plugin.json", "managed settings", "~/.claude/settings.json", ".claude/settings.json", ".claude/settings.local.json"],
|
|
8190
8190
|
eventNames: ["SessionStart", "Setup", "UserPromptSubmit", "UserPromptExpansion", "PreToolUse", "PermissionRequest", "PermissionDenied", "PostToolUse", "PostToolUseFailure", "PostToolBatch", "Notification", "SubagentStart", "SubagentStop", "TaskCreated", "TaskCompleted", "Stop", "StopFailure", "TeammateIdle", "InstructionsLoaded", "ConfigChange", "CwdChanged", "FileChanged", "WorktreeCreate", "WorktreeRemove", "PreCompact", "PostCompact", "Elicitation", "ElicitationResult", "SessionEnd"],
|
|
8191
|
-
notes: "Hook configs can be stored in hooks/hooks.json, inlined in plugin.json, added in settings files, or
|
|
8191
|
+
notes: "Hook configs can be stored in hooks/hooks.json, inlined in plugin.json, added in user/project/local settings files, or controlled by managed policy settings; Claude also documents a broader lifecycle event set than the older simplified Pluxx model."
|
|
8192
8192
|
},
|
|
8193
8193
|
instructions: {
|
|
8194
8194
|
files: ["CLAUDE.md"],
|
|
@@ -8304,7 +8304,7 @@ var PLATFORM_VALIDATION_RULES = {
|
|
|
8304
8304
|
supported: true,
|
|
8305
8305
|
files: ["hooks/hooks.json", ".codex/hooks.json", "~/.codex/hooks.json"],
|
|
8306
8306
|
eventNames: ["SessionStart", "PreToolUse", "PermissionRequest", "PostToolUse", "UserPromptSubmit", "Stop"],
|
|
8307
|
-
notes: "Codex documents hooks in project and user config, and the
|
|
8307
|
+
notes: "Codex documents hooks in project and user config, and the current config schema still accepts both `[features].hooks = true` and `[features].codex_hooks = true`. Maintained interactive probes on May 13, 2026 showed local Codex CLI 0.130.0 timing out without a project-local `.codex/hooks.json` side effect or `/hooks` review gate under either flag; the `codex_hooks` variant also emitted a deprecation message that points users to `hooks`, while the official docs still show `codex_hooks`. The official hooks docs also cover plugin-bundled hooks, which Pluxx now emits at `hooks/hooks.json`."
|
|
8308
8308
|
},
|
|
8309
8309
|
instructions: {
|
|
8310
8310
|
files: ["AGENTS.md", "AGENTS.override.md"],
|
|
@@ -8738,22 +8738,22 @@ var CORE_FOUR_PRIMITIVE_CAPABILITIES = {
|
|
|
8738
8738
|
agents: {
|
|
8739
8739
|
mode: "translate",
|
|
8740
8740
|
nativeSurfaces: [".codex/agents/*.toml", "~/.codex/agents/*.toml", "subagent workflows"],
|
|
8741
|
-
notes:
|
|
8741
|
+
notes: 'Codex custom agents and subagents are real native surfaces, but they are not packaged the same way as Claude or Cursor plugin agents. Local May 13, 2026 headless probes now prove explicit invocation, built-in-name override, project-local precedence, and discovered `.agents/skills` inheritance. The same maintained headless suite also showed two config-depth caveats: a parent `[[skills.config]] enabled = false` entry did not disable a discovered project skill, and an agent-local `[[skills.config]]` entry did not preload an undiscovered `skills/` path. The maintained `bun scripts/probe-codex-mcp-runtime.ts --json` headless probe now also shows a more precise MCP approval split: default project-scoped and user-scoped root MCP both emit a real `mcp_tool_call` item but fail it with `user cancelled MCP tool call` before any server-side `tools/call`, while the default inline-agent path reaches startup plus `tools/list` and then falls back to `MCP_PROOF_MARKER_MISSING`. The same maintained suite now also proves five approved allow-paths: explicit `[mcp_servers.<id>.tools.<tool>] approval_mode = "approve"` works for project-scoped root MCP, user-scoped root MCP, agent-local inline `mcp_servers`, and custom agents that inherit an approved project-scoped or user-scoped root MCP server. All three approved custom-agent MCP paths still avoid a root `mcp_tool_call` item in the parent `codex exec --json` stream and instead surface child `agents_states` moving through `pending_init` to `completed`; project-scoped servers still do not appear in `codex mcp list`, and user-scoped servers do appear there. The maintained `bun scripts/probe-codex-agents-interactive-runtime.ts --json` trusted interactive probe also showed the same `sandbox_mode = "read-only"` child agent still wrote to the workspace there too, so these fields are not yet uniformly trustworthy runtime boundaries.'
|
|
8742
8742
|
},
|
|
8743
8743
|
hooks: {
|
|
8744
8744
|
mode: "translate",
|
|
8745
8745
|
nativeSurfaces: ["hooks/hooks.json", ".codex/hooks.json", "~/.codex/hooks.json"],
|
|
8746
|
-
notes: "Hooks are native. Pluxx bundles translated Codex hooks in the plugin and still tracks the project/user config paths plus the codex_hooks
|
|
8746
|
+
notes: "Hooks are native. Pluxx bundles translated Codex hooks in the plugin and still tracks the project/user config paths plus the mixed `[features].hooks` / `[features].codex_hooks` activation caveat and current runtime/doc drift."
|
|
8747
8747
|
},
|
|
8748
8748
|
permissions: {
|
|
8749
8749
|
mode: "translate",
|
|
8750
8750
|
nativeSurfaces: ["approvals", "sandbox policy", "hook matchers", "custom agent config"],
|
|
8751
|
-
notes:
|
|
8751
|
+
notes: 'Codex expresses permission intent through approvals, sandboxing, hooks, and custom agents rather than skill frontmatter. Pluxx now also emits `.codex/config.generated.toml` for the live-proven top-level MCP allow-path when canonical `MCP(...)` rules are concrete enough to materialize per-tool `approval_mode = "approve"` stanzas, while `.codex/permissions.generated.json` remains the broader advisory mirror.'
|
|
8752
8752
|
},
|
|
8753
8753
|
runtime: {
|
|
8754
8754
|
mode: "preserve",
|
|
8755
8755
|
nativeSurfaces: [".mcp.json", ".app.json", ".codex/config.toml", "scripts/", "assets/"],
|
|
8756
|
-
notes: `Bundle-local MCP config exists, but active MCP state also lives in config.toml. ${getRuntimeReadinessExternalConfigNote()}`
|
|
8756
|
+
notes: `Bundle-local MCP config exists, but active MCP state also lives in config.toml. Local May 13, 2026 headless Codex MCP probes reached startup plus \`tools/list\` across project-scoped, user-scoped, and inline-agent config; default root MCP emitted a real \`mcp_tool_call\` item but failed it with \`user cancelled MCP tool call\` before any server-side \`tools/call\`, while the default inline-agent path fell back to \`MCP_PROOF_MARKER_MISSING\` after startup plus \`tools/list\`. The same maintained suite now proves five concrete approval paths: project-scoped root MCP, user-scoped root MCP, agent-local inline \`mcp_servers\`, a custom agent inheriting an approved project-scoped root MCP server, and a custom agent inheriting an approved user-scoped root MCP server all reach real server-side \`tools/call\` once explicit \`[mcp_servers.<id>.tools.<tool>] approval_mode = "approve"\` is present in the relevant layer. All three approved custom-agent MCP paths still avoid a root \`mcp_tool_call\` item in the parent \`codex exec --json\` stream and instead surface child \`agents_states\` moving through \`pending_init\` to \`completed\`; project-scoped servers still do not appear in \`codex mcp list\`, and user-scoped servers do appear there. ${getRuntimeReadinessExternalConfigNote()}`
|
|
8757
8757
|
},
|
|
8758
8758
|
distribution: {
|
|
8759
8759
|
mode: "preserve",
|
|
@@ -10294,12 +10294,13 @@ function runPublish(config, options = {}) {
|
|
|
10294
10294
|
}
|
|
10295
10295
|
|
|
10296
10296
|
// src/cli/verify-install.ts
|
|
10297
|
-
import { existsSync as existsSync5, lstatSync as lstatSync2, readdirSync as readdirSync3, readFileSync as readFileSync5, readlinkSync, realpathSync, statSync } from "fs";
|
|
10297
|
+
import { existsSync as existsSync5, lstatSync as lstatSync2, readdirSync as readdirSync3, readFileSync as readFileSync5, readlinkSync, realpathSync as realpathSync2, statSync } from "fs";
|
|
10298
10298
|
import { resolve as resolve5 } from "path";
|
|
10299
10299
|
|
|
10300
10300
|
// src/cli/doctor.ts
|
|
10301
10301
|
import { spawn, spawnSync as spawnSync2 } from "child_process";
|
|
10302
|
-
import { accessSync, constants, existsSync as existsSync4, lstatSync, readFileSync as readFileSync4, readdirSync as readdirSync2 } from "fs";
|
|
10302
|
+
import { accessSync, constants, existsSync as existsSync4, lstatSync, readFileSync as readFileSync4, readdirSync as readdirSync2, realpathSync } from "fs";
|
|
10303
|
+
import { homedir } from "os";
|
|
10303
10304
|
import { basename, dirname as dirname2, resolve as resolve4 } from "path";
|
|
10304
10305
|
|
|
10305
10306
|
// node_modules/jiti/lib/jiti.mjs
|
|
@@ -10466,7 +10467,7 @@ function resolveBundleReference(rootDir, value) {
|
|
|
10466
10467
|
}
|
|
10467
10468
|
function readBundleManifestReferences(manifest) {
|
|
10468
10469
|
const references = [];
|
|
10469
|
-
for (const key of ["commands", "skills", "hooks", "mcpServers"]) {
|
|
10470
|
+
for (const key of ["commands", "skills", "hooks", "mcpServers", "rules", "apps"]) {
|
|
10470
10471
|
const value = manifest[key];
|
|
10471
10472
|
if (typeof value === "string") {
|
|
10472
10473
|
references.push(value);
|
|
@@ -10504,6 +10505,37 @@ function extractBundleCommandTargets(command) {
|
|
|
10504
10505
|
const matches = command.match(/\$\{(?:CLAUDE_PLUGIN_ROOT|CURSOR_PLUGIN_ROOT|PLUGIN_ROOT)\}[\\/][^\s"'`;$|&<>]+|\.\.?[\\/][^\s"'`;$|&<>]+/g);
|
|
10505
10506
|
return matches ?? [];
|
|
10506
10507
|
}
|
|
10508
|
+
function resolveInstalledHooksReference(rootDir, platform, manifest) {
|
|
10509
|
+
const manifestReference = typeof manifest.hooks === "string" ? manifest.hooks : void 0;
|
|
10510
|
+
if (manifestReference) {
|
|
10511
|
+
return {
|
|
10512
|
+
reference: manifestReference,
|
|
10513
|
+
path: resolveBundleReference(rootDir, manifestReference)
|
|
10514
|
+
};
|
|
10515
|
+
}
|
|
10516
|
+
if (platform === "claude-code") {
|
|
10517
|
+
const fallbackReference = "./hooks/hooks.json";
|
|
10518
|
+
const fallbackPath = resolve3(rootDir, "hooks/hooks.json");
|
|
10519
|
+
if (existsSync3(fallbackPath)) {
|
|
10520
|
+
return {
|
|
10521
|
+
reference: fallbackReference,
|
|
10522
|
+
path: fallbackPath
|
|
10523
|
+
};
|
|
10524
|
+
}
|
|
10525
|
+
}
|
|
10526
|
+
return {};
|
|
10527
|
+
}
|
|
10528
|
+
function getClaudeStandardHooksManifestIssue(rootDir, platform, manifest) {
|
|
10529
|
+
if (platform !== "claude-code") return void 0;
|
|
10530
|
+
const manifestReference = typeof manifest.hooks === "string" ? manifest.hooks : void 0;
|
|
10531
|
+
if (!manifestReference) return void 0;
|
|
10532
|
+
const manifestHooksPath = resolveBundleReference(rootDir, manifestReference);
|
|
10533
|
+
const standardHooksPath = resolve3(rootDir, "hooks/hooks.json");
|
|
10534
|
+
if (!manifestHooksPath || manifestHooksPath !== standardHooksPath || !existsSync3(standardHooksPath)) {
|
|
10535
|
+
return void 0;
|
|
10536
|
+
}
|
|
10537
|
+
return "Claude auto-loads hooks/hooks.json. Current Claude CLI releases report a duplicate hooks file load error when manifest.hooks also points at ./hooks/hooks.json, so manifest.hooks should only reference additional hook files.";
|
|
10538
|
+
}
|
|
10507
10539
|
function findInstalledBundleIntegrityIssues(rootDir, platform) {
|
|
10508
10540
|
const manifestPath = manifestPathForPlatform(platform);
|
|
10509
10541
|
if (!manifestPath) {
|
|
@@ -10537,17 +10569,19 @@ function findInstalledBundleIntegrityIssues(rootDir, platform) {
|
|
|
10537
10569
|
const resolved = resolveBundleReference(rootDir, value);
|
|
10538
10570
|
return resolved !== void 0 && !existsSync3(resolved);
|
|
10539
10571
|
}).sort();
|
|
10540
|
-
const
|
|
10572
|
+
const manifestIssue = getClaudeStandardHooksManifestIssue(rootDir, platform, manifest);
|
|
10573
|
+
const { reference: hooksReference, path: hooksPath } = resolveInstalledHooksReference(rootDir, platform, manifest);
|
|
10541
10574
|
if (!hooksReference) {
|
|
10542
10575
|
return {
|
|
10576
|
+
...manifestIssue ? { manifestIssue } : {},
|
|
10543
10577
|
missingManifestPaths,
|
|
10544
10578
|
missingHookTargets: [],
|
|
10545
10579
|
invalidRuntimeScripts: findInstalledRuntimeScriptIssues(rootDir, manifest)
|
|
10546
10580
|
};
|
|
10547
10581
|
}
|
|
10548
|
-
const hooksPath = resolveBundleReference(rootDir, hooksReference);
|
|
10549
10582
|
if (!hooksPath || !existsSync3(hooksPath)) {
|
|
10550
10583
|
return {
|
|
10584
|
+
...manifestIssue ? { manifestIssue } : {},
|
|
10551
10585
|
missingManifestPaths,
|
|
10552
10586
|
missingHookTargets: [],
|
|
10553
10587
|
invalidRuntimeScripts: findInstalledRuntimeScriptIssues(rootDir, manifest)
|
|
@@ -10564,12 +10598,15 @@ function findInstalledBundleIntegrityIssues(rootDir, platform) {
|
|
|
10564
10598
|
})
|
|
10565
10599
|
)].sort();
|
|
10566
10600
|
return {
|
|
10601
|
+
...manifestIssue ? { manifestIssue } : {},
|
|
10567
10602
|
missingManifestPaths,
|
|
10568
10603
|
missingHookTargets,
|
|
10569
10604
|
invalidRuntimeScripts: findInstalledRuntimeScriptIssues(rootDir, manifest)
|
|
10570
10605
|
};
|
|
10571
|
-
} catch {
|
|
10606
|
+
} catch (error) {
|
|
10572
10607
|
return {
|
|
10608
|
+
...manifestIssue ? { manifestIssue } : {},
|
|
10609
|
+
hookConfigIssue: `hooks config at ${hooksReference} is not parseable: ${error instanceof Error ? error.message : String(error)}`,
|
|
10573
10610
|
missingManifestPaths,
|
|
10574
10611
|
missingHookTargets: [],
|
|
10575
10612
|
invalidRuntimeScripts: findInstalledRuntimeScriptIssues(rootDir, manifest)
|
|
@@ -10754,6 +10791,180 @@ var MUTATING_PREFIXES = [
|
|
|
10754
10791
|
];
|
|
10755
10792
|
var MUTATING_PREFIX_PATTERN = new RegExp(`^(${MUTATING_PREFIXES.join("|")})\\b`, "i");
|
|
10756
10793
|
|
|
10794
|
+
// src/codex-hooks-feature.ts
|
|
10795
|
+
var RECOMMENDED_CODEX_HOOKS_FEATURE_FLAG = "hooks";
|
|
10796
|
+
var ALTERNATE_CODEX_HOOKS_FEATURE_FLAG = "codex_hooks";
|
|
10797
|
+
function getCodexHooksFeatureState(features) {
|
|
10798
|
+
if (!features) {
|
|
10799
|
+
return {
|
|
10800
|
+
recommended: false,
|
|
10801
|
+
alternate: false
|
|
10802
|
+
};
|
|
10803
|
+
}
|
|
10804
|
+
return {
|
|
10805
|
+
recommended: features[RECOMMENDED_CODEX_HOOKS_FEATURE_FLAG] === true,
|
|
10806
|
+
alternate: features[ALTERNATE_CODEX_HOOKS_FEATURE_FLAG] === true
|
|
10807
|
+
};
|
|
10808
|
+
}
|
|
10809
|
+
|
|
10810
|
+
// src/toml-lite.ts
|
|
10811
|
+
function stripTomlComment(line) {
|
|
10812
|
+
let inString = false;
|
|
10813
|
+
let quote = "";
|
|
10814
|
+
for (let i = 0; i < line.length; i += 1) {
|
|
10815
|
+
const char = line[i];
|
|
10816
|
+
if ((char === '"' || char === "'") && line[i - 1] !== "\\") {
|
|
10817
|
+
if (!inString) {
|
|
10818
|
+
inString = true;
|
|
10819
|
+
quote = char;
|
|
10820
|
+
} else if (quote === char) {
|
|
10821
|
+
inString = false;
|
|
10822
|
+
quote = "";
|
|
10823
|
+
}
|
|
10824
|
+
continue;
|
|
10825
|
+
}
|
|
10826
|
+
if (char === "#" && !inString) return line.slice(0, i);
|
|
10827
|
+
}
|
|
10828
|
+
return line;
|
|
10829
|
+
}
|
|
10830
|
+
function parseTomlValue(value) {
|
|
10831
|
+
if (value.startsWith('"') && value.endsWith('"')) return unquoteTomlString(value);
|
|
10832
|
+
if (value.startsWith("'") && value.endsWith("'")) return value.slice(1, -1);
|
|
10833
|
+
if (value.startsWith("[") && value.endsWith("]")) {
|
|
10834
|
+
const inner = value.slice(1, -1).trim();
|
|
10835
|
+
if (!inner) return [];
|
|
10836
|
+
return splitTomlList(inner).map((part) => parseTomlValue(part.trim()));
|
|
10837
|
+
}
|
|
10838
|
+
if (value.startsWith("{") && value.endsWith("}")) {
|
|
10839
|
+
const inner = value.slice(1, -1).trim();
|
|
10840
|
+
const result = {};
|
|
10841
|
+
if (!inner) return result;
|
|
10842
|
+
for (const part of splitTomlList(inner)) {
|
|
10843
|
+
const assignment = part.trim().match(/^([A-Za-z0-9_-]+)\s*=\s*(.+)$/);
|
|
10844
|
+
if (!assignment) continue;
|
|
10845
|
+
result[assignment[1]] = parseTomlValue(assignment[2].trim());
|
|
10846
|
+
}
|
|
10847
|
+
return result;
|
|
10848
|
+
}
|
|
10849
|
+
if (value === "true") return true;
|
|
10850
|
+
if (value === "false") return false;
|
|
10851
|
+
return value;
|
|
10852
|
+
}
|
|
10853
|
+
function splitTomlList(value) {
|
|
10854
|
+
const parts = [];
|
|
10855
|
+
let current = "";
|
|
10856
|
+
let inString = false;
|
|
10857
|
+
let quote = "";
|
|
10858
|
+
let braceDepth = 0;
|
|
10859
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
10860
|
+
const char = value[i];
|
|
10861
|
+
if ((char === '"' || char === "'") && value[i - 1] !== "\\") {
|
|
10862
|
+
if (!inString) {
|
|
10863
|
+
inString = true;
|
|
10864
|
+
quote = char;
|
|
10865
|
+
} else if (quote === char) {
|
|
10866
|
+
inString = false;
|
|
10867
|
+
quote = "";
|
|
10868
|
+
}
|
|
10869
|
+
}
|
|
10870
|
+
if (!inString && char === "{") braceDepth += 1;
|
|
10871
|
+
if (!inString && char === "}") braceDepth -= 1;
|
|
10872
|
+
if (!inString && braceDepth === 0 && char === ",") {
|
|
10873
|
+
parts.push(current);
|
|
10874
|
+
current = "";
|
|
10875
|
+
continue;
|
|
10876
|
+
}
|
|
10877
|
+
current += char;
|
|
10878
|
+
}
|
|
10879
|
+
if (current.trim()) parts.push(current);
|
|
10880
|
+
return parts;
|
|
10881
|
+
}
|
|
10882
|
+
function unquoteTomlString(value) {
|
|
10883
|
+
return value.slice(1, -1).replace(/\\"/g, '"').replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\\\/g, "\\");
|
|
10884
|
+
}
|
|
10885
|
+
|
|
10886
|
+
// src/codex-permissions-companion.ts
|
|
10887
|
+
function parseCodexApprovedMcpToolsFromToml(source) {
|
|
10888
|
+
const approvals = /* @__PURE__ */ new Map();
|
|
10889
|
+
let currentSection = null;
|
|
10890
|
+
const addApproval = (entry) => {
|
|
10891
|
+
approvals.set(`${entry.serverName}.${entry.toolName}`, entry);
|
|
10892
|
+
};
|
|
10893
|
+
for (const rawLine of source.split(/\r?\n/)) {
|
|
10894
|
+
const line = stripTomlComment(rawLine).trim();
|
|
10895
|
+
if (!line) continue;
|
|
10896
|
+
const sectionMatch = line.match(/^\[(.+)\]$/);
|
|
10897
|
+
if (sectionMatch) {
|
|
10898
|
+
currentSection = parseCodexApprovalSection(sectionMatch[1].trim());
|
|
10899
|
+
continue;
|
|
10900
|
+
}
|
|
10901
|
+
const assignmentMatch = line.match(/^([^=]+?)\s*=\s*(.+)$/);
|
|
10902
|
+
if (!assignmentMatch) continue;
|
|
10903
|
+
const key = assignmentMatch[1].trim();
|
|
10904
|
+
const parsedValue = parseTomlValue(assignmentMatch[2].trim());
|
|
10905
|
+
if (currentSection && key === "approval_mode" && parsedValue === "approve") {
|
|
10906
|
+
addApproval(currentSection);
|
|
10907
|
+
continue;
|
|
10908
|
+
}
|
|
10909
|
+
if (!key.includes(".")) continue;
|
|
10910
|
+
const entry = parseCodexApprovalAssignment(key, parsedValue);
|
|
10911
|
+
if (entry) addApproval(entry);
|
|
10912
|
+
}
|
|
10913
|
+
return [...approvals.values()].sort((a, b) => a.serverName.localeCompare(b.serverName) || a.toolName.localeCompare(b.toolName));
|
|
10914
|
+
}
|
|
10915
|
+
function parseCodexApprovalSection(sectionName) {
|
|
10916
|
+
const tokens = splitTomlDottedPath(sectionName);
|
|
10917
|
+
if (tokens.length !== 4) return null;
|
|
10918
|
+
if (tokens[0] !== "mcp_servers" || tokens[2] !== "tools") return null;
|
|
10919
|
+
return {
|
|
10920
|
+
serverName: tokens[1],
|
|
10921
|
+
toolName: tokens[3]
|
|
10922
|
+
};
|
|
10923
|
+
}
|
|
10924
|
+
function parseCodexApprovalAssignment(key, value) {
|
|
10925
|
+
if (value !== "approve") return null;
|
|
10926
|
+
const tokens = splitTomlDottedPath(key);
|
|
10927
|
+
if (tokens.length !== 5) return null;
|
|
10928
|
+
if (tokens[0] !== "mcp_servers" || tokens[2] !== "tools" || tokens[4] !== "approval_mode") return null;
|
|
10929
|
+
return {
|
|
10930
|
+
serverName: tokens[1],
|
|
10931
|
+
toolName: tokens[3]
|
|
10932
|
+
};
|
|
10933
|
+
}
|
|
10934
|
+
function splitTomlDottedPath(value) {
|
|
10935
|
+
const parts = [];
|
|
10936
|
+
let current = "";
|
|
10937
|
+
let inString = false;
|
|
10938
|
+
let quote = "";
|
|
10939
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
10940
|
+
const char = value[i];
|
|
10941
|
+
if ((char === '"' || char === "'") && value[i - 1] !== "\\") {
|
|
10942
|
+
if (!inString) {
|
|
10943
|
+
inString = true;
|
|
10944
|
+
quote = char;
|
|
10945
|
+
} else if (quote === char) {
|
|
10946
|
+
inString = false;
|
|
10947
|
+
quote = "";
|
|
10948
|
+
}
|
|
10949
|
+
current += char;
|
|
10950
|
+
continue;
|
|
10951
|
+
}
|
|
10952
|
+
if (!inString && char === ".") {
|
|
10953
|
+
const trimmed2 = current.trim();
|
|
10954
|
+
if (trimmed2) parts.push(trimmed2);
|
|
10955
|
+
current = "";
|
|
10956
|
+
continue;
|
|
10957
|
+
}
|
|
10958
|
+
current += char;
|
|
10959
|
+
}
|
|
10960
|
+
const trimmed = current.trim();
|
|
10961
|
+
if (trimmed) parts.push(trimmed);
|
|
10962
|
+
return parts.map((part) => {
|
|
10963
|
+
const parsed = parseTomlValue(part);
|
|
10964
|
+
return typeof parsed === "string" ? parsed : part;
|
|
10965
|
+
});
|
|
10966
|
+
}
|
|
10967
|
+
|
|
10757
10968
|
// src/cli/doctor.ts
|
|
10758
10969
|
var MATERIALIZED_ENV_MARKER = "materialized required config";
|
|
10759
10970
|
var MIN_NODE_MAJOR = 18;
|
|
@@ -10860,6 +11071,278 @@ function detectConsumerLayout(rootDir) {
|
|
|
10860
11071
|
function readJsonFile(rootDir, relativePath) {
|
|
10861
11072
|
return JSON.parse(readFileSync4(resolve4(rootDir, relativePath), "utf-8"));
|
|
10862
11073
|
}
|
|
11074
|
+
function listCodexConfigCandidates(projectRoot) {
|
|
11075
|
+
const projectCandidate = resolve4(projectRoot ?? process.cwd(), ".codex/config.toml");
|
|
11076
|
+
const homeDir = process.env.HOME?.trim() || homedir();
|
|
11077
|
+
const codexHome = process.env.CODEX_HOME?.trim() || resolve4(homeDir, ".codex");
|
|
11078
|
+
const userCandidate = resolve4(codexHome, "config.toml");
|
|
11079
|
+
const seen = /* @__PURE__ */ new Set();
|
|
11080
|
+
const candidates = [];
|
|
11081
|
+
for (const candidate of [
|
|
11082
|
+
{ path: projectCandidate, scope: "project" },
|
|
11083
|
+
{ path: userCandidate, scope: "user" }
|
|
11084
|
+
]) {
|
|
11085
|
+
if (seen.has(candidate.path)) continue;
|
|
11086
|
+
seen.add(candidate.path);
|
|
11087
|
+
candidates.push(candidate);
|
|
11088
|
+
}
|
|
11089
|
+
return candidates;
|
|
11090
|
+
}
|
|
11091
|
+
function readCodexHooksFeatureFlag(filePath) {
|
|
11092
|
+
let inFeaturesTable = false;
|
|
11093
|
+
const lines = readFileSync4(filePath, "utf-8").split(/\r?\n/);
|
|
11094
|
+
let recommended;
|
|
11095
|
+
let alternate;
|
|
11096
|
+
const assignFeatureFlag = (key, rawValue) => {
|
|
11097
|
+
const parsed = parseTomlValue(rawValue.trim());
|
|
11098
|
+
if (typeof parsed !== "boolean") return;
|
|
11099
|
+
if (key === RECOMMENDED_CODEX_HOOKS_FEATURE_FLAG) recommended = parsed;
|
|
11100
|
+
if (key === ALTERNATE_CODEX_HOOKS_FEATURE_FLAG) alternate = parsed;
|
|
11101
|
+
};
|
|
11102
|
+
for (const rawLine of lines) {
|
|
11103
|
+
const line = stripTomlComment(rawLine).trim();
|
|
11104
|
+
if (!line) continue;
|
|
11105
|
+
const sectionMatch = line.match(/^\[(.+)\]$/);
|
|
11106
|
+
if (sectionMatch) {
|
|
11107
|
+
inFeaturesTable = sectionMatch[1].trim() === "features";
|
|
11108
|
+
continue;
|
|
11109
|
+
}
|
|
11110
|
+
const dottedFeatureMatch = line.match(/^features\.(hooks|codex_hooks)\s*=\s*(.+)$/);
|
|
11111
|
+
if (dottedFeatureMatch) {
|
|
11112
|
+
assignFeatureFlag(dottedFeatureMatch[1], dottedFeatureMatch[2]);
|
|
11113
|
+
continue;
|
|
11114
|
+
}
|
|
11115
|
+
const inlineFeaturesMatch = line.match(/^features\s*=\s*(.+)$/);
|
|
11116
|
+
if (inlineFeaturesMatch) {
|
|
11117
|
+
const parsed = parseTomlValue(inlineFeaturesMatch[1].trim());
|
|
11118
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
11119
|
+
const featureState = getCodexHooksFeatureState(parsed);
|
|
11120
|
+
recommended = featureState.recommended;
|
|
11121
|
+
alternate = featureState.alternate;
|
|
11122
|
+
}
|
|
11123
|
+
continue;
|
|
11124
|
+
}
|
|
11125
|
+
if (!inFeaturesTable) continue;
|
|
11126
|
+
const featureMatch = line.match(/^(hooks|codex_hooks)\s*=\s*(.+)$/);
|
|
11127
|
+
if (!featureMatch) continue;
|
|
11128
|
+
assignFeatureFlag(featureMatch[1], featureMatch[2]);
|
|
11129
|
+
}
|
|
11130
|
+
if (recommended === void 0 && alternate === void 0) return void 0;
|
|
11131
|
+
return {
|
|
11132
|
+
recommended: recommended === true,
|
|
11133
|
+
alternate: alternate === true
|
|
11134
|
+
};
|
|
11135
|
+
}
|
|
11136
|
+
function probeCodexHooksFeatureFlags(projectRoot) {
|
|
11137
|
+
return listCodexConfigCandidates(projectRoot).map((candidate) => {
|
|
11138
|
+
if (!existsSync4(candidate.path)) {
|
|
11139
|
+
return {
|
|
11140
|
+
...candidate,
|
|
11141
|
+
exists: false,
|
|
11142
|
+
enabled: false,
|
|
11143
|
+
recommendedEnabled: false,
|
|
11144
|
+
alternateEnabled: false
|
|
11145
|
+
};
|
|
11146
|
+
}
|
|
11147
|
+
try {
|
|
11148
|
+
const featureState = readCodexHooksFeatureFlag(candidate.path);
|
|
11149
|
+
return {
|
|
11150
|
+
...candidate,
|
|
11151
|
+
exists: true,
|
|
11152
|
+
enabled: featureState?.recommended === true || featureState?.alternate === true,
|
|
11153
|
+
recommendedEnabled: featureState?.recommended === true,
|
|
11154
|
+
alternateEnabled: featureState?.alternate === true
|
|
11155
|
+
};
|
|
11156
|
+
} catch (error) {
|
|
11157
|
+
return {
|
|
11158
|
+
...candidate,
|
|
11159
|
+
exists: true,
|
|
11160
|
+
enabled: false,
|
|
11161
|
+
recommendedEnabled: false,
|
|
11162
|
+
alternateEnabled: false,
|
|
11163
|
+
parseError: error instanceof Error ? error.message : String(error)
|
|
11164
|
+
};
|
|
11165
|
+
}
|
|
11166
|
+
});
|
|
11167
|
+
}
|
|
11168
|
+
function probeCodexMcpApprovalEntries(projectRoot) {
|
|
11169
|
+
return listCodexConfigCandidates(projectRoot).map((candidate) => {
|
|
11170
|
+
if (!existsSync4(candidate.path)) {
|
|
11171
|
+
return {
|
|
11172
|
+
...candidate,
|
|
11173
|
+
exists: false,
|
|
11174
|
+
approvals: []
|
|
11175
|
+
};
|
|
11176
|
+
}
|
|
11177
|
+
try {
|
|
11178
|
+
return {
|
|
11179
|
+
...candidate,
|
|
11180
|
+
exists: true,
|
|
11181
|
+
approvals: parseCodexApprovedMcpToolsFromToml(readFileSync4(candidate.path, "utf-8"))
|
|
11182
|
+
};
|
|
11183
|
+
} catch (error) {
|
|
11184
|
+
return {
|
|
11185
|
+
...candidate,
|
|
11186
|
+
exists: true,
|
|
11187
|
+
approvals: [],
|
|
11188
|
+
parseError: error instanceof Error ? error.message : String(error)
|
|
11189
|
+
};
|
|
11190
|
+
}
|
|
11191
|
+
});
|
|
11192
|
+
}
|
|
11193
|
+
function getCodexProjectPathCandidates(projectRoot) {
|
|
11194
|
+
if (!projectRoot) return [];
|
|
11195
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
11196
|
+
const resolvedProjectRoot = resolve4(projectRoot);
|
|
11197
|
+
candidates.add(resolvedProjectRoot);
|
|
11198
|
+
try {
|
|
11199
|
+
candidates.add(realpathSync(resolvedProjectRoot));
|
|
11200
|
+
} catch {
|
|
11201
|
+
}
|
|
11202
|
+
return [...candidates];
|
|
11203
|
+
}
|
|
11204
|
+
function parseCodexProjectKeySegment(segment) {
|
|
11205
|
+
const trimmed = segment.trim();
|
|
11206
|
+
if (!trimmed) return null;
|
|
11207
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
11208
|
+
const parsed = parseTomlValue(trimmed);
|
|
11209
|
+
return typeof parsed === "string" ? parsed : null;
|
|
11210
|
+
}
|
|
11211
|
+
return null;
|
|
11212
|
+
}
|
|
11213
|
+
function readCodexProjectTrust(filePath, projectPaths) {
|
|
11214
|
+
const matchedPaths = /* @__PURE__ */ new Set();
|
|
11215
|
+
let currentProjectKey = null;
|
|
11216
|
+
const lines = readFileSync4(filePath, "utf-8").split(/\r?\n/);
|
|
11217
|
+
for (const rawLine of lines) {
|
|
11218
|
+
const line = stripTomlComment(rawLine).trim();
|
|
11219
|
+
if (!line) continue;
|
|
11220
|
+
const sectionMatch = line.match(/^\[(.+)\]$/);
|
|
11221
|
+
if (sectionMatch) {
|
|
11222
|
+
currentProjectKey = null;
|
|
11223
|
+
const section = sectionMatch[1].trim();
|
|
11224
|
+
if (section.startsWith("projects.")) {
|
|
11225
|
+
currentProjectKey = parseCodexProjectKeySegment(section.slice("projects.".length));
|
|
11226
|
+
}
|
|
11227
|
+
continue;
|
|
11228
|
+
}
|
|
11229
|
+
const dottedTrustMatch = line.match(/^projects\.(.+)\.trust_level\s*=\s*(.+)$/);
|
|
11230
|
+
if (dottedTrustMatch) {
|
|
11231
|
+
const projectKey = parseCodexProjectKeySegment(dottedTrustMatch[1]);
|
|
11232
|
+
const parsed2 = parseTomlValue(dottedTrustMatch[2].trim());
|
|
11233
|
+
if (projectKey && projectPaths.has(projectKey) && parsed2 === "trusted") {
|
|
11234
|
+
matchedPaths.add(projectKey);
|
|
11235
|
+
}
|
|
11236
|
+
continue;
|
|
11237
|
+
}
|
|
11238
|
+
if (!currentProjectKey || !projectPaths.has(currentProjectKey)) continue;
|
|
11239
|
+
const trustMatch = line.match(/^trust_level\s*=\s*(.+)$/);
|
|
11240
|
+
if (!trustMatch) continue;
|
|
11241
|
+
const parsed = parseTomlValue(trustMatch[1].trim());
|
|
11242
|
+
if (parsed === "trusted") {
|
|
11243
|
+
matchedPaths.add(currentProjectKey);
|
|
11244
|
+
}
|
|
11245
|
+
}
|
|
11246
|
+
return [...matchedPaths];
|
|
11247
|
+
}
|
|
11248
|
+
function probeCodexProjectTrust(projectRoot) {
|
|
11249
|
+
const projectPaths = getCodexProjectPathCandidates(projectRoot);
|
|
11250
|
+
if (projectPaths.length === 0) return void 0;
|
|
11251
|
+
const userConfig = listCodexConfigCandidates(projectRoot).find((candidate) => candidate.scope === "user");
|
|
11252
|
+
if (!userConfig) return void 0;
|
|
11253
|
+
if (!existsSync4(userConfig.path)) {
|
|
11254
|
+
return {
|
|
11255
|
+
path: userConfig.path,
|
|
11256
|
+
exists: false,
|
|
11257
|
+
trusted: false,
|
|
11258
|
+
matchedProjectPaths: []
|
|
11259
|
+
};
|
|
11260
|
+
}
|
|
11261
|
+
try {
|
|
11262
|
+
const matchedProjectPaths = readCodexProjectTrust(userConfig.path, new Set(projectPaths));
|
|
11263
|
+
return {
|
|
11264
|
+
path: userConfig.path,
|
|
11265
|
+
exists: true,
|
|
11266
|
+
trusted: matchedProjectPaths.length > 0,
|
|
11267
|
+
matchedProjectPaths
|
|
11268
|
+
};
|
|
11269
|
+
} catch (error) {
|
|
11270
|
+
return {
|
|
11271
|
+
path: userConfig.path,
|
|
11272
|
+
exists: true,
|
|
11273
|
+
trusted: false,
|
|
11274
|
+
matchedProjectPaths: [],
|
|
11275
|
+
parseError: error instanceof Error ? error.message : String(error)
|
|
11276
|
+
};
|
|
11277
|
+
}
|
|
11278
|
+
}
|
|
11279
|
+
function getClaudeManagedSettingsPath() {
|
|
11280
|
+
switch (process.platform) {
|
|
11281
|
+
case "darwin":
|
|
11282
|
+
return "/Library/Application Support/ClaudeCode/managed-settings.json";
|
|
11283
|
+
case "linux":
|
|
11284
|
+
return "/etc/claude-code/managed-settings.json";
|
|
11285
|
+
case "win32":
|
|
11286
|
+
return "C:\\Program Files\\ClaudeCode\\managed-settings.json";
|
|
11287
|
+
default:
|
|
11288
|
+
return void 0;
|
|
11289
|
+
}
|
|
11290
|
+
}
|
|
11291
|
+
function listClaudeSettingsCandidates(projectRoot) {
|
|
11292
|
+
const homeDir = process.env.HOME?.trim() || homedir();
|
|
11293
|
+
const candidates = [];
|
|
11294
|
+
const managedPath = getClaudeManagedSettingsPath();
|
|
11295
|
+
if (managedPath) {
|
|
11296
|
+
candidates.push({ path: managedPath, scope: "managed" });
|
|
11297
|
+
}
|
|
11298
|
+
candidates.push({ path: resolve4(homeDir, ".claude/settings.json"), scope: "user" });
|
|
11299
|
+
if (projectRoot) {
|
|
11300
|
+
candidates.push({ path: resolve4(projectRoot, ".claude/settings.json"), scope: "project" });
|
|
11301
|
+
candidates.push({ path: resolve4(projectRoot, ".claude/settings.local.json"), scope: "local" });
|
|
11302
|
+
}
|
|
11303
|
+
const seen = /* @__PURE__ */ new Set();
|
|
11304
|
+
return candidates.filter((candidate) => {
|
|
11305
|
+
if (seen.has(candidate.path)) return false;
|
|
11306
|
+
seen.add(candidate.path);
|
|
11307
|
+
return true;
|
|
11308
|
+
});
|
|
11309
|
+
}
|
|
11310
|
+
function readClaudeDisableAllHooks(filePath) {
|
|
11311
|
+
const parsed = readJsonFile(dirname2(filePath), basename(filePath));
|
|
11312
|
+
return typeof parsed.disableAllHooks === "boolean" ? parsed.disableAllHooks : void 0;
|
|
11313
|
+
}
|
|
11314
|
+
function probeClaudeDisableAllHooks(projectRoot) {
|
|
11315
|
+
return listClaudeSettingsCandidates(projectRoot).map((candidate) => {
|
|
11316
|
+
if (!existsSync4(candidate.path)) {
|
|
11317
|
+
return {
|
|
11318
|
+
...candidate,
|
|
11319
|
+
exists: false,
|
|
11320
|
+
disableAllHooks: false
|
|
11321
|
+
};
|
|
11322
|
+
}
|
|
11323
|
+
try {
|
|
11324
|
+
return {
|
|
11325
|
+
...candidate,
|
|
11326
|
+
exists: true,
|
|
11327
|
+
disableAllHooks: readClaudeDisableAllHooks(candidate.path) === true
|
|
11328
|
+
};
|
|
11329
|
+
} catch (error) {
|
|
11330
|
+
return {
|
|
11331
|
+
...candidate,
|
|
11332
|
+
exists: true,
|
|
11333
|
+
disableAllHooks: false,
|
|
11334
|
+
parseError: error instanceof Error ? error.message : String(error)
|
|
11335
|
+
};
|
|
11336
|
+
}
|
|
11337
|
+
});
|
|
11338
|
+
}
|
|
11339
|
+
function getInstalledClaudeHooksReference(rootDir, manifest) {
|
|
11340
|
+
if (existsSync4(resolve4(rootDir, "hooks/hooks.json"))) {
|
|
11341
|
+
return "./hooks/hooks.json";
|
|
11342
|
+
}
|
|
11343
|
+
const manifestReference = typeof manifest.hooks === "string" ? manifest.hooks : void 0;
|
|
11344
|
+
return manifestReference;
|
|
11345
|
+
}
|
|
10863
11346
|
function checkConsumerBundlePath(checks, rootDir) {
|
|
10864
11347
|
try {
|
|
10865
11348
|
accessSync(rootDir, constants.R_OK);
|
|
@@ -11159,6 +11642,9 @@ function checkInstalledBundleIntegrity(checks, rootDir, layout) {
|
|
|
11159
11642
|
if (issues.manifestIssue) {
|
|
11160
11643
|
details.push(issues.manifestIssue);
|
|
11161
11644
|
}
|
|
11645
|
+
if (issues.hookConfigIssue) {
|
|
11646
|
+
details.push(issues.hookConfigIssue);
|
|
11647
|
+
}
|
|
11162
11648
|
if (issues.missingManifestPaths.length > 0) {
|
|
11163
11649
|
details.push(`manifest references missing path${issues.missingManifestPaths.length === 1 ? "" : "s"}: ${issues.missingManifestPaths.join(", ")}`);
|
|
11164
11650
|
}
|
|
@@ -11182,12 +11668,207 @@ function checkInstalledBundleIntegrity(checks, rootDir, layout) {
|
|
|
11182
11668
|
addCheck(checks, {
|
|
11183
11669
|
level: "error",
|
|
11184
11670
|
code: "consumer-bundle-integrity-invalid",
|
|
11185
|
-
title: "Installed bundle is
|
|
11671
|
+
title: "Installed bundle integrity is broken",
|
|
11186
11672
|
detail: details.join("; "),
|
|
11187
|
-
fix: "Reinstall the plugin or rebuild the bundle so every manifest path and hook target
|
|
11673
|
+
fix: "Reinstall the plugin or rebuild the bundle so every manifest path, hook config, and hook target is valid inside the installed plugin.",
|
|
11188
11674
|
path: layout.manifestPath
|
|
11189
11675
|
});
|
|
11190
11676
|
}
|
|
11677
|
+
function checkInstalledClaudeHookSettings(checks, rootDir, layout, options) {
|
|
11678
|
+
if (layout.platform !== "claude-code") return;
|
|
11679
|
+
if (checks.some((check) => check.code === "consumer-bundle-integrity-invalid")) return;
|
|
11680
|
+
let manifest;
|
|
11681
|
+
try {
|
|
11682
|
+
manifest = readJsonFile(rootDir, layout.manifestPath);
|
|
11683
|
+
} catch {
|
|
11684
|
+
return;
|
|
11685
|
+
}
|
|
11686
|
+
const hooksReference = getInstalledClaudeHooksReference(rootDir, manifest);
|
|
11687
|
+
if (!hooksReference) return;
|
|
11688
|
+
const probes = probeClaudeDisableAllHooks(options.projectRoot);
|
|
11689
|
+
const disabled = probes.filter((probe) => probe.disableAllHooks);
|
|
11690
|
+
const parseErrors = probes.filter((probe) => probe.parseError).map((probe) => `${probe.path}: ${probe.parseError}`);
|
|
11691
|
+
const checkedPaths = probes.map((probe) => `${probe.scope} ${probe.exists ? "settings" : "path"}: ${probe.path}${probe.exists ? "" : " (missing)"}`).join("; ");
|
|
11692
|
+
if (disabled.length > 0) {
|
|
11693
|
+
addCheck(checks, {
|
|
11694
|
+
level: "warning",
|
|
11695
|
+
code: "consumer-claude-hooks-disabled",
|
|
11696
|
+
title: "Claude settings may currently suppress bundled hook activation",
|
|
11697
|
+
detail: `This installed Claude bundle uses hooks at ${hooksReference}, and \`disableAllHooks = true\` was found in ${disabled.map((probe) => `${probe.scope} settings ${probe.path}`).join(" and ")}. Live Claude CLI 2.1.140 headless probes on 2026-05-13 showed this setting suppressing SessionStart settings-hook execution across user, project, and local layers.${parseErrors.length > 0 ? ` Unparseable settings: ${parseErrors.join("; ")}.` : ""} This check only inspects the file-based managed path plus user/project/local settings; Claude enterprise policy can also arrive through registry, plist/MDM, or server-managed policy, and \`allowManagedHooksOnly\` is not evaluated here.`,
|
|
11698
|
+
fix: "Remove or flip `disableAllHooks` in the active Claude settings layer, run /reload-plugins or restart Claude, rerun pluxx verify-install, and use Claude /status when enterprise managed policy may still control hook activation.",
|
|
11699
|
+
path: disabled[0].path
|
|
11700
|
+
});
|
|
11701
|
+
return;
|
|
11702
|
+
}
|
|
11703
|
+
if (parseErrors.length > 0) {
|
|
11704
|
+
addCheck(checks, {
|
|
11705
|
+
level: "warning",
|
|
11706
|
+
code: "consumer-claude-hook-settings-invalid",
|
|
11707
|
+
title: "Claude hook settings could not be fully inspected",
|
|
11708
|
+
detail: `This installed Claude bundle uses hooks at ${hooksReference}, but some checked Claude settings files were not parseable. Checked ${checkedPaths}. Unparseable settings: ${parseErrors.join("; ")}. This check only inspects the file-based managed path plus user/project/local settings; it does not verify registry, plist/MDM, server-managed policy, or \`allowManagedHooksOnly\`.`,
|
|
11709
|
+
fix: "Fix the malformed Claude settings JSON, reload Claude, rerun pluxx verify-install, and use Claude /status if enterprise policy may still control hook activation.",
|
|
11710
|
+
path: probes.find((probe) => probe.parseError)?.path ?? hooksReference
|
|
11711
|
+
});
|
|
11712
|
+
return;
|
|
11713
|
+
}
|
|
11714
|
+
addCheck(checks, {
|
|
11715
|
+
level: "success",
|
|
11716
|
+
code: "consumer-claude-hook-settings-clear",
|
|
11717
|
+
title: "No checked Claude settings layer is disabling hooks",
|
|
11718
|
+
detail: `This installed Claude bundle uses hooks at ${hooksReference}, and no checked Claude settings layer currently sets \`disableAllHooks = true\`. Checked ${checkedPaths}. This success result only covers the file-based managed path plus user/project/local settings; it does not prove registry, plist/MDM, server-managed policy, or \`allowManagedHooksOnly\` are clear.`,
|
|
11719
|
+
fix: "No action needed.",
|
|
11720
|
+
path: hooksReference
|
|
11721
|
+
});
|
|
11722
|
+
}
|
|
11723
|
+
function checkInstalledCodexHooksFeatureFlag(checks, rootDir, layout, options) {
|
|
11724
|
+
if (layout.platform !== "codex") return;
|
|
11725
|
+
if (checks.some((check) => check.code === "consumer-bundle-integrity-invalid")) return;
|
|
11726
|
+
let manifest;
|
|
11727
|
+
try {
|
|
11728
|
+
manifest = readJsonFile(rootDir, layout.manifestPath);
|
|
11729
|
+
} catch {
|
|
11730
|
+
return;
|
|
11731
|
+
}
|
|
11732
|
+
const hooksReference = typeof manifest.hooks === "string" ? manifest.hooks : void 0;
|
|
11733
|
+
if (!hooksReference) return;
|
|
11734
|
+
const probes = probeCodexHooksFeatureFlags(options.projectRoot);
|
|
11735
|
+
const enabledProbes = probes.filter((probe) => probe.enabled);
|
|
11736
|
+
const checkedPaths = probes.map((probe) => `${probe.scope} ${probe.exists ? "config" : "path"}: ${probe.path}${probe.exists ? "" : " (missing)"}`).join("; ");
|
|
11737
|
+
const describeEnabledFlags = (probe) => {
|
|
11738
|
+
const enabledFlags = [];
|
|
11739
|
+
if (probe.recommendedEnabled) enabledFlags.push(RECOMMENDED_CODEX_HOOKS_FEATURE_FLAG);
|
|
11740
|
+
if (probe.alternateEnabled) enabledFlags.push(ALTERNATE_CODEX_HOOKS_FEATURE_FLAG);
|
|
11741
|
+
return enabledFlags.join(" + ");
|
|
11742
|
+
};
|
|
11743
|
+
if (enabledProbes.length > 0) {
|
|
11744
|
+
const legacyOnlyProbes = enabledProbes.filter((probe) => probe.alternateEnabled && !probe.recommendedEnabled);
|
|
11745
|
+
if (legacyOnlyProbes.length > 0) {
|
|
11746
|
+
addCheck(checks, {
|
|
11747
|
+
level: "warning",
|
|
11748
|
+
code: "consumer-codex-hooks-feature-flag-legacy-only",
|
|
11749
|
+
title: "Codex hooks are enabled only through the legacy compatibility flag",
|
|
11750
|
+
detail: `This installed Codex bundle declares hooks at ${hooksReference}, and the checked Codex config enables only \`${ALTERNATE_CODEX_HOOKS_FEATURE_FLAG} = true\` in ${legacyOnlyProbes.map((probe) => `${probe.scope} config ${probe.path}`).join(" and ")}. Maintained interactive probes on May 13, 2026 showed local Codex CLI 0.130.0 emitting a deprecation warning for \`${ALTERNATE_CODEX_HOOKS_FEATURE_FLAG}\` that points users to \`${RECOMMENDED_CODEX_HOOKS_FEATURE_FLAG}\`.`,
|
|
11751
|
+
fix: `Prefer \`${RECOMMENDED_CODEX_HOOKS_FEATURE_FLAG} = true\` under \`[features]\` in the active Codex config, keep \`${ALTERNATE_CODEX_HOOKS_FEATURE_FLAG}\` only as a compatibility fallback if needed, reload Codex, and rerun pluxx verify-install.`,
|
|
11752
|
+
path: legacyOnlyProbes[0].path
|
|
11753
|
+
});
|
|
11754
|
+
}
|
|
11755
|
+
addCheck(checks, {
|
|
11756
|
+
level: "success",
|
|
11757
|
+
code: "consumer-codex-hooks-feature-flag-enabled",
|
|
11758
|
+
title: "Codex hook feature flag found for this install",
|
|
11759
|
+
detail: `This installed Codex bundle declares hooks at ${hooksReference}, and a Codex hook feature flag 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: maintained local probes on May 13, 2026 showed local Codex CLI 0.130.0 still failing to execute the project-local hook under \`${RECOMMENDED_CODEX_HOOKS_FEATURE_FLAG} = true\`, \`${ALTERNATE_CODEX_HOOKS_FEATURE_FLAG} = true\`, and the current CLI feature path \`--enable hooks\`.`,
|
|
11760
|
+
fix: "No action needed.",
|
|
11761
|
+
path: enabledProbes[0].path
|
|
11762
|
+
});
|
|
11763
|
+
return;
|
|
11764
|
+
}
|
|
11765
|
+
const parseErrors = probes.filter((probe) => probe.parseError).map((probe) => `${probe.path}: ${probe.parseError}`);
|
|
11766
|
+
addCheck(checks, {
|
|
11767
|
+
level: "warning",
|
|
11768
|
+
code: "consumer-codex-hooks-feature-flag-missing",
|
|
11769
|
+
title: "Codex hook activation is missing its known feature-gate prerequisite",
|
|
11770
|
+
detail: `This installed Codex bundle declares hooks at ${hooksReference}, but neither \`${RECOMMENDED_CODEX_HOOKS_FEATURE_FLAG} = true\` nor \`${ALTERNATE_CODEX_HOOKS_FEATURE_FLAG} = true\` was found in the checked Codex config layers. Current Codex config surfaces still accept both keys under \`[features]\`, but maintained probes on May 13, 2026 showed local Codex CLI 0.130.0 still failing to execute the project-local hook under \`${RECOMMENDED_CODEX_HOOKS_FEATURE_FLAG} = true\`, \`${ALTERNATE_CODEX_HOOKS_FEATURE_FLAG} = true\`, and the current CLI feature path \`--enable hooks\`, while also emitting a deprecation warning for \`${ALTERNATE_CODEX_HOOKS_FEATURE_FLAG}\` that points users to \`${RECOMMENDED_CODEX_HOOKS_FEATURE_FLAG}\`. Checked ${checkedPaths}.${parseErrors.length > 0 ? ` Unparseable config: ${parseErrors.join("; ")}.` : ""}`,
|
|
11771
|
+
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. Keep \`${ALTERNATE_CODEX_HOOKS_FEATURE_FLAG}\` only as a compatibility fallback if your Codex runtime still needs it, and treat any enabled flag as best-effort activation rather than proof that hooks will execute.`,
|
|
11772
|
+
path: probes.find((probe) => probe.exists)?.path ?? hooksReference
|
|
11773
|
+
});
|
|
11774
|
+
}
|
|
11775
|
+
function checkInstalledCodexProjectTrust(checks, rootDir, layout, options) {
|
|
11776
|
+
if (layout.platform !== "codex") return;
|
|
11777
|
+
if (checks.some((check) => check.code === "consumer-bundle-integrity-invalid")) return;
|
|
11778
|
+
if (!options.projectRoot) return;
|
|
11779
|
+
let manifest;
|
|
11780
|
+
try {
|
|
11781
|
+
manifest = readJsonFile(rootDir, layout.manifestPath);
|
|
11782
|
+
} catch {
|
|
11783
|
+
return;
|
|
11784
|
+
}
|
|
11785
|
+
const hooksReference = typeof manifest.hooks === "string" ? manifest.hooks : void 0;
|
|
11786
|
+
if (!hooksReference) return;
|
|
11787
|
+
const trustProbe = probeCodexProjectTrust(options.projectRoot);
|
|
11788
|
+
if (!trustProbe) return;
|
|
11789
|
+
const projectPaths = getCodexProjectPathCandidates(options.projectRoot);
|
|
11790
|
+
const displayProjectPaths = projectPaths.join(" or ");
|
|
11791
|
+
const parseErrorDetail = trustProbe.parseError ? ` Unparseable user config: ${trustProbe.parseError}.` : "";
|
|
11792
|
+
if (trustProbe.trusted) {
|
|
11793
|
+
addCheck(checks, {
|
|
11794
|
+
level: "success",
|
|
11795
|
+
code: "consumer-codex-project-trust-enabled",
|
|
11796
|
+
title: "Codex trusted-project entry found for this install",
|
|
11797
|
+
detail: `This installed Codex bundle declares hooks at ${hooksReference}, and the user Codex config ${trustProbe.path} trusts ${trustProbe.matchedProjectPaths.join(" and ")} for project-local hook loading.`,
|
|
11798
|
+
fix: "No action needed.",
|
|
11799
|
+
path: trustProbe.path
|
|
11800
|
+
});
|
|
11801
|
+
return;
|
|
11802
|
+
}
|
|
11803
|
+
addCheck(checks, {
|
|
11804
|
+
level: "warning",
|
|
11805
|
+
code: "consumer-codex-project-trust-missing",
|
|
11806
|
+
title: "Codex project trust may still block hook activation",
|
|
11807
|
+
detail: `This installed Codex bundle declares hooks at ${hooksReference}, but the user Codex config ${trustProbe.exists ? trustProbe.path : `${trustProbe.path} (missing)`} does not currently trust ${displayProjectPaths}. Current Codex CLI releases can keep project-local config, hooks, and exec policies disabled until the project is trusted.${parseErrorDetail}`,
|
|
11808
|
+
fix: `Trust the project in Codex, or add a matching \`[projects."<absolute-project-path>"]\\ntrust_level = "trusted"\` entry in ${trustProbe.path}, reload Codex, retry a trusted interactive prompt, and rerun pluxx verify-install if hooks still do not activate.`,
|
|
11809
|
+
path: trustProbe.path
|
|
11810
|
+
});
|
|
11811
|
+
}
|
|
11812
|
+
function checkInstalledCodexPermissionCompanion(checks, rootDir, layout, options) {
|
|
11813
|
+
if (layout.platform !== "codex") return;
|
|
11814
|
+
if (checks.some((check) => check.code === "consumer-bundle-integrity-invalid")) return;
|
|
11815
|
+
const companionPath = resolve4(rootDir, ".codex/config.generated.toml");
|
|
11816
|
+
const companionReference = ".codex/config.generated.toml";
|
|
11817
|
+
if (!existsSync4(companionPath)) return;
|
|
11818
|
+
let expectedEntries;
|
|
11819
|
+
try {
|
|
11820
|
+
expectedEntries = parseCodexApprovedMcpToolsFromToml(readFileSync4(companionPath, "utf-8"));
|
|
11821
|
+
} catch (error) {
|
|
11822
|
+
addCheck(checks, {
|
|
11823
|
+
level: "error",
|
|
11824
|
+
code: "consumer-codex-mcp-approval-companion-invalid",
|
|
11825
|
+
title: "Generated Codex MCP approval companion is malformed",
|
|
11826
|
+
detail: `${companionReference} could not be parsed: ${error instanceof Error ? error.message : String(error)}`,
|
|
11827
|
+
fix: "Rebuild or reinstall the plugin so the generated Codex MCP approval companion is written correctly before you merge it into active Codex config.",
|
|
11828
|
+
path: companionReference
|
|
11829
|
+
});
|
|
11830
|
+
return;
|
|
11831
|
+
}
|
|
11832
|
+
if (expectedEntries.length === 0) return;
|
|
11833
|
+
const probes = probeCodexMcpApprovalEntries(options.projectRoot);
|
|
11834
|
+
const checkedPaths = probes.map((probe) => `${probe.scope} ${probe.exists ? "config" : "path"}: ${probe.path}${probe.exists ? "" : " (missing)"}`).join("; ");
|
|
11835
|
+
const parseErrors = probes.filter((probe) => probe.parseError).map((probe) => `${probe.path}: ${probe.parseError}`);
|
|
11836
|
+
const activeEntries = new Set(
|
|
11837
|
+
probes.flatMap((probe) => probe.approvals.map((entry) => `${entry.serverName}.${entry.toolName}`))
|
|
11838
|
+
);
|
|
11839
|
+
const missingEntries = expectedEntries.filter((entry) => !activeEntries.has(`${entry.serverName}.${entry.toolName}`));
|
|
11840
|
+
const formatEntries = (entries) => entries.map((entry) => `${entry.serverName}.${entry.toolName}`).join(", ");
|
|
11841
|
+
if (missingEntries.length > 0) {
|
|
11842
|
+
addCheck(checks, {
|
|
11843
|
+
level: "warning",
|
|
11844
|
+
code: "consumer-codex-mcp-approval-config-missing",
|
|
11845
|
+
title: "Generated Codex MCP approval stanzas are not fully merged into active config",
|
|
11846
|
+
detail: `This installed Codex bundle includes ${companionReference} with ${expectedEntries.length} per-tool approval stanza${expectedEntries.length === 1 ? "" : "s"}, but the checked Codex config layers are still missing ${missingEntries.length === expectedEntries.length ? "all of them" : formatEntries(missingEntries)}. Checked ${checkedPaths}.${parseErrors.length > 0 ? ` Unparseable config: ${parseErrors.join("; ")}.` : ""} Live maintained Codex MCP probes on May 13, 2026 only proved this approval path once explicit per-tool \`approval_mode = "approve"\` entries were merged into active config.`,
|
|
11847
|
+
fix: `Merge the missing stanza${missingEntries.length === 1 ? "" : "s"} from ${companionReference} into the active project or user Codex config.toml, reload Codex, and rerun pluxx verify-install. Keep .codex/permissions.generated.json as the broader mirror for selectors that still remain external.`,
|
|
11848
|
+
path: companionReference
|
|
11849
|
+
});
|
|
11850
|
+
return;
|
|
11851
|
+
}
|
|
11852
|
+
if (parseErrors.length > 0) {
|
|
11853
|
+
addCheck(checks, {
|
|
11854
|
+
level: "warning",
|
|
11855
|
+
code: "consumer-codex-mcp-approval-config-unparseable",
|
|
11856
|
+
title: "Codex MCP approval config could not be fully inspected",
|
|
11857
|
+
detail: `This installed Codex bundle includes ${companionReference}, and matching approval stanzas were found in the checked Codex config layers, but some config files were not parseable. Checked ${checkedPaths}. Unparseable config: ${parseErrors.join("; ")}.`,
|
|
11858
|
+
fix: "Fix the malformed Codex config, reload Codex, and rerun pluxx verify-install so Pluxx can confirm the generated approval stanzas are still merged correctly.",
|
|
11859
|
+
path: probes.find((probe) => probe.parseError)?.path ?? companionReference
|
|
11860
|
+
});
|
|
11861
|
+
return;
|
|
11862
|
+
}
|
|
11863
|
+
addCheck(checks, {
|
|
11864
|
+
level: "success",
|
|
11865
|
+
code: "consumer-codex-mcp-approval-config-merged",
|
|
11866
|
+
title: "Generated Codex MCP approval stanzas are present in active config",
|
|
11867
|
+
detail: `This installed Codex bundle includes ${companionReference} with ${expectedEntries.length} per-tool approval stanza${expectedEntries.length === 1 ? "" : "s"}, and matching entries were found in the checked Codex config layers. Checked ${checkedPaths}.`,
|
|
11868
|
+
fix: "No action needed.",
|
|
11869
|
+
path: companionReference
|
|
11870
|
+
});
|
|
11871
|
+
}
|
|
11191
11872
|
function parseInstalledPermissionRules(scriptSource) {
|
|
11192
11873
|
const match = scriptSource.match(/const RULES = (\[[\s\S]*?\]);\nconst ACTION_PRIORITY/);
|
|
11193
11874
|
if (!match?.[1]) {
|
|
@@ -11552,7 +12233,7 @@ function checkInstalledOpenCodeSkills(checks, rootDir) {
|
|
|
11552
12233
|
const missingDetail = missingSkills.length > 0 ? `missing exported skills: ${missingSkills.join(", ")}` : void 0;
|
|
11553
12234
|
const malformedDetail = malformedSkills.length > 0 ? `malformed exported skills: ${malformedSkills.join(", ")}` : void 0;
|
|
11554
12235
|
addCheck(checks, {
|
|
11555
|
-
level: "
|
|
12236
|
+
level: "error",
|
|
11556
12237
|
code: "consumer-opencode-skill-sync-incomplete",
|
|
11557
12238
|
title: "OpenCode exported skills are incomplete",
|
|
11558
12239
|
detail: [missingDetail, malformedDetail].filter(Boolean).join("; "),
|
|
@@ -11570,7 +12251,7 @@ function checkInstalledOpenCodeSkills(checks, rootDir) {
|
|
|
11570
12251
|
path: "skills"
|
|
11571
12252
|
});
|
|
11572
12253
|
}
|
|
11573
|
-
async function doctorConsumer(rootDir = process.cwd()) {
|
|
12254
|
+
async function doctorConsumer(rootDir = process.cwd(), options = {}) {
|
|
11574
12255
|
const checks = [];
|
|
11575
12256
|
addRuntimeChecks(checks, "consumer");
|
|
11576
12257
|
checkConsumerBundlePath(checks, rootDir);
|
|
@@ -11615,6 +12296,10 @@ async function doctorConsumer(rootDir = process.cwd()) {
|
|
|
11615
12296
|
});
|
|
11616
12297
|
checkConsumerManifest(checks, rootDir, layout);
|
|
11617
12298
|
checkInstalledBundleIntegrity(checks, rootDir, layout);
|
|
12299
|
+
checkInstalledClaudeHookSettings(checks, rootDir, layout, options);
|
|
12300
|
+
checkInstalledCodexHooksFeatureFlag(checks, rootDir, layout, options);
|
|
12301
|
+
checkInstalledCodexProjectTrust(checks, rootDir, layout, options);
|
|
12302
|
+
checkInstalledCodexPermissionCompanion(checks, rootDir, layout, options);
|
|
11618
12303
|
checkInstalledPermissionHook(checks, rootDir, layout);
|
|
11619
12304
|
checkInstalledUserConfig(checks, rootDir);
|
|
11620
12305
|
checkInstalledEnvValidation(checks, rootDir);
|
|
@@ -11632,6 +12317,7 @@ function buildCheckFromReport(target, pluginName, report) {
|
|
|
11632
12317
|
const consumerPath = resolveInstalledConsumerPath(target, pluginName);
|
|
11633
12318
|
const staleReason = target.built && existsSync5(consumerPath) ? detectStaleInstall(target, pluginName, consumerPath) : void 0;
|
|
11634
12319
|
const stale = staleReason !== void 0;
|
|
12320
|
+
const issues = listVerifyInstallIssues(report.checks);
|
|
11635
12321
|
return {
|
|
11636
12322
|
platform: target.platform,
|
|
11637
12323
|
installPath: consumerPath,
|
|
@@ -11643,9 +12329,22 @@ function buildCheckFromReport(target, pluginName, report) {
|
|
|
11643
12329
|
ok: report.errors === 0 && !stale,
|
|
11644
12330
|
errors: report.errors + (stale ? 1 : 0),
|
|
11645
12331
|
warnings: report.warnings,
|
|
11646
|
-
infos: report.infos
|
|
12332
|
+
infos: report.infos,
|
|
12333
|
+
issues
|
|
11647
12334
|
};
|
|
11648
12335
|
}
|
|
12336
|
+
function listVerifyInstallIssues(checks) {
|
|
12337
|
+
return checks.filter(
|
|
12338
|
+
(check) => !check.code.startsWith("primitive-") && check.level !== "success"
|
|
12339
|
+
).map((check) => ({
|
|
12340
|
+
level: check.level,
|
|
12341
|
+
code: check.code,
|
|
12342
|
+
title: check.title,
|
|
12343
|
+
detail: check.detail,
|
|
12344
|
+
fix: check.fix,
|
|
12345
|
+
...check.path ? { path: check.path } : {}
|
|
12346
|
+
}));
|
|
12347
|
+
}
|
|
11649
12348
|
function manifestPathForPlatform2(platform) {
|
|
11650
12349
|
switch (platform) {
|
|
11651
12350
|
case "claude-code":
|
|
@@ -11708,8 +12407,8 @@ function detectStaleInstall(target, pluginName, consumerPath) {
|
|
|
11708
12407
|
try {
|
|
11709
12408
|
const details = lstatSync2(consumerPath);
|
|
11710
12409
|
if (details.isSymbolicLink()) {
|
|
11711
|
-
const installedRealPath =
|
|
11712
|
-
const builtRealPath =
|
|
12410
|
+
const installedRealPath = realpathSync2(consumerPath);
|
|
12411
|
+
const builtRealPath = realpathSync2(target.sourceDir);
|
|
11713
12412
|
if (installedRealPath !== builtRealPath) {
|
|
11714
12413
|
return `installed symlink points to ${readlinkSync(consumerPath)}, not the current build at ${target.sourceDir}`;
|
|
11715
12414
|
}
|
|
@@ -11736,7 +12435,7 @@ async function verifyInstall(config, options = {}) {
|
|
|
11736
12435
|
const checks = await Promise.all(
|
|
11737
12436
|
filteredPlan.map(async (target) => {
|
|
11738
12437
|
const consumerPath = resolveInstalledConsumerPath(target, config.name);
|
|
11739
|
-
const report = await doctorConsumer(consumerPath);
|
|
12438
|
+
const report = await doctorConsumer(consumerPath, { projectRoot: rootDir });
|
|
11740
12439
|
return buildCheckFromReport(target, config.name, report);
|
|
11741
12440
|
})
|
|
11742
12441
|
);
|
|
@@ -11754,6 +12453,13 @@ function printVerifyInstallResult(result) {
|
|
|
11754
12453
|
console.log(`${prefix} ${check.platform}: ${check.consumerPath}`);
|
|
11755
12454
|
console.log(` install path: ${check.installPath}`);
|
|
11756
12455
|
console.log(` built: ${check.built ? "yes" : "no"}; installed: ${check.installed ? "yes" : "no"}; errors: ${check.errors}; warnings: ${check.warnings}; infos: ${check.infos}`);
|
|
12456
|
+
for (const issue of check.issues) {
|
|
12457
|
+
const issuePrefix = issue.level.toUpperCase().padEnd(7, " ");
|
|
12458
|
+
const pathLabel = issue.path ? ` [${issue.path}]` : "";
|
|
12459
|
+
console.log(` ${issuePrefix} ${issue.code}${pathLabel} ${issue.title}`);
|
|
12460
|
+
console.log(` ${issue.detail}`);
|
|
12461
|
+
console.log(` Fix: ${issue.fix}`);
|
|
12462
|
+
}
|
|
11757
12463
|
if (check.stale) {
|
|
11758
12464
|
console.log(` stale install: ${check.staleReason}`);
|
|
11759
12465
|
}
|