codex-plugin-doctor 1.7.0 → 1.9.0
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 +12 -10
- package/dist/core/review-bundle.d.ts +8 -0
- package/dist/core/review-bundle.js +77 -3
- package/dist/run-cli.js +13 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -292,7 +292,7 @@ codex-plugin-doctor check . --json --runtime --verbose-runtime
|
|
|
292
292
|
|
|
293
293
|
`self-test` runs the bundled runtime-complete sample through static validation, runtime MCP probes, and the compatibility scorecard. It is the fastest post-install check after `npm install -g codex-plugin-doctor`.
|
|
294
294
|
|
|
295
|
-
`doctor` checks the local environment, including package version, platform, Node version, npm global prefix, Codex home, and Codex plugin cache visibility. The text output also includes recommended next commands for self-test, installed plugin discovery, runtime checks, compatibility scoring, and CI setup. `doctor contract` publishes the machine-readable output contract, including public JSON schema surfaces, stable-through-1.0 compatibility metadata, and a frozen rule catalog digest. Add `--json` for automation or `--output output-contract.json` to write the contract to disk. `doctor corpus` runs the bundled validation corpus against healthy runtime, risky security, starter skill, and generic MCP packages, then reports whether each case matched its expected outcome. Add `--json` for automation or `--output validation-corpus.json` to write the corpus report to disk. `doctor npm <package>` runs a preinstall scan by packing the npm package with scripts disabled, extracting the publish tarball, and running validation, security, trust, and recommendation checks against the shipped contents. Use a published Codex plugin package as the target; scanning `codex-plugin-doctor` itself intentionally reports a missing plugin manifest because this CLI package is not a plugin package. Add `--json` for automation or `--output npm-preinstall.json` to write the report to disk. `doctor attest <path>` creates a local attestation with stable package/report digests, validation/security/compatibility/trust summary, and verification metadata. Add `--sign-key-env NAME` to attach a local HMAC-SHA256 signature without printing the secret, or `--json --output attestation.json` to write the artifact to disk. `doctor attest verify <attestation.json> --target <path> --sign-key-env NAME` recomputes the package fingerprint, report digest, and HMAC signature offline; verification intentionally treats `generatedAt`, `targetPath`, `verification`, and `signature.keyHint` as unsigned display metadata. `doctor runtime-plan <path>` creates a non-executing runtime plan that lists MCP server commands, safe probe methods, risk reasons, and a stable approval digest before any local server is started. Add `--markdown --output runtime-plan.md` to preserve a review-ready approval artifact with the execution boundary, checklist, servers, probes, and risk reasons. `doctor runtime-policy <path>` evaluates the same runtime plan and security signals, then recommends `allow`, `review`, `sandbox_recommended`, or `deny` before local MCP execution starts. `doctor review-bundle <path> --output <dir> --sign-key-env NAME` writes a signed review directory with runtime plan, runtime policy, attestation, release evidence, manifest,
|
|
295
|
+
`doctor` checks the local environment, including package version, platform, Node version, npm global prefix, Codex home, and Codex plugin cache visibility. The text output also includes recommended next commands for self-test, installed plugin discovery, runtime checks, compatibility scoring, and CI setup. `doctor contract` publishes the machine-readable output contract, including public JSON schema surfaces, stable-through-1.0 compatibility metadata, and a frozen rule catalog digest. Add `--json` for automation or `--output output-contract.json` to write the contract to disk. `doctor corpus` runs the bundled validation corpus against healthy runtime, risky security, starter skill, and generic MCP packages, then reports whether each case matched its expected outcome. Add `--json` for automation or `--output validation-corpus.json` to write the corpus report to disk. `doctor npm <package>` runs a preinstall scan by packing the npm package with scripts disabled, extracting the publish tarball, and running validation, security, trust, and recommendation checks against the shipped contents. Use a published Codex plugin package as the target; scanning `codex-plugin-doctor` itself intentionally reports a missing plugin manifest because this CLI package is not a plugin package. Add `--json` for automation or `--output npm-preinstall.json` to write the report to disk. `doctor attest <path>` creates a local attestation with stable package/report digests, validation/security/compatibility/trust summary, and verification metadata. Add `--sign-key-env NAME` to attach a local HMAC-SHA256 signature without printing the secret, or `--json --output attestation.json` to write the artifact to disk. `doctor attest verify <attestation.json> --target <path> --sign-key-env NAME` recomputes the package fingerprint, report digest, and HMAC signature offline; verification intentionally treats `generatedAt`, `targetPath`, `verification`, and `signature.keyHint` as unsigned display metadata. `doctor runtime-plan <path>` creates a non-executing runtime plan that lists MCP server commands, safe probe methods, risk reasons, and a stable approval digest before any local server is started. Add `--markdown --output runtime-plan.md` to preserve a review-ready approval artifact with the execution boundary, checklist, servers, probes, and risk reasons. `doctor runtime-policy <path>` evaluates the same runtime plan and security signals, then recommends `allow`, `review`, `sandbox_recommended`, or `deny` before local MCP execution starts. `doctor review-bundle <path> --output <dir> --sign-key-env NAME` writes a signed review directory with runtime plan, runtime policy, attestation, release evidence, manifest, Markdown summary files, and SHA-256 file integrity digests. `doctor review-bundle verify <bundle-dir> --target <path> --sign-key-env NAME` verifies the bundle manifest, expected files, manifest integrity digests, runtime artifacts, signed attestation, and signed release evidence offline before a reviewer trusts the handoff. `doctor review-bundle diff --before <dir> --after <dir>` compares two review bundles and flags risk-increasing changes in status, runtime policy, release readiness, signatures, release evidence, and runtime plan digest. `check --runtime --require-runtime-approval --runtime-approval-digest <digest>` refuses to run runtime probes unless the current plan digest matches the approved digest. `doctor release-evidence <path> --sign-key-env NAME` creates one redacted release bundle with signed attestation, offline verification, corpus, performance, security, trust, package metadata, git release gates, and runtime approval status. Strict release evidence requires a clean tagged worktree; use `--allow-dirty` or `--allow-untagged` only for local rehearsal. `doctor release-evidence verify <evidence.json> --target <path> --sign-key-env NAME` verifies a shared release evidence artifact offline against an explicit package path; the artifact target path is treated as display metadata, not trusted input. `doctor release-evidence asset <path> --tag <tag> --output <evidence.json> --sign-key-env NAME` writes a signed release evidence file and prints the `gh release upload` command; add `--upload` to run the upload through GitHub CLI with `--clobber`. `doctor inspector <path>` builds a safe MCP Inspector launch command from a packaged `.mcp.json` file without starting the Inspector proxy automatically. Use `--server <name>` when the package contains multiple MCP server entries. `doctor diff --before <path> --after <path>` compares two package roots and reports new findings, resolved findings, trust score delta, and whether risk increased. `doctor recommend <path>` turns validation, security, and compatibility signals into a prioritized action plan with blocker, high, medium, and info actions. Add `--json` for automation or `--output recommendations.json` to write the report to disk. `doctor trust <path>` creates a local trust score from package lifecycle scripts, dependency specs, and MCP security findings. Use it before release when you want supply-chain risks summarized as one score. `doctor perf <path>` profiles the shared package analysis pipeline and reports per-stage durations for validation, config, security, compatibility, trust, recommendations, and total runtime. Add `--max-total-ms <ms>` or repeatable `--max-stage-ms stage=ms` to fail CI when a budget is exceeded. `doctor mcp <path>` exposes the generic MCP static health report under the doctor command family without starting local MCP servers. `doctor export --bundle <path>` creates a redacted operator handoff bundle that includes validation JSON, security scorecard data, compatibility matrix, recommendations, and trust score in one file. `doctor snapshot` creates a redacted diagnostics bundle with environment health, client config readiness, installed plugin metadata, and next commands. Add `--json` for machine-readable output or `--output doctor-snapshot.json` to write the bundle to disk. `doctor clients` reports local Codex, Claude Desktop, Cursor, Cline, and Windsurf config readiness. `doctor --update-check` compares the installed CLI version with the latest npm version and prints the upgrade command when a newer release is available.
|
|
296
296
|
|
|
297
297
|
`audit --installed` runs a local ecosystem audit against every discovered Codex plugin in the installed plugin cache. Add `--security` to include security scorecards, `--compat` to include the all-client compatibility matrix, and `--json --output local-audit.json` when you want a shareable machine-readable report. Add `--cache` to reuse unchanged plugin results between runs; add `--changed` to only report plugins whose fingerprint changed since the last cached audit. Use `--cache-file path/to/audit-cache.json` when CI or scripted runs need an explicit cache location.
|
|
298
298
|
|
|
@@ -358,17 +358,19 @@ jobs:
|
|
|
358
358
|
runs-on: ubuntu-latest
|
|
359
359
|
steps:
|
|
360
360
|
- uses: actions/checkout@v5
|
|
361
|
-
- uses: Esquetta/CodexPluginDoctor@v1.
|
|
361
|
+
- uses: Esquetta/CodexPluginDoctor@v1.9.0
|
|
362
362
|
with:
|
|
363
|
-
version: "1.
|
|
363
|
+
version: "1.9.0"
|
|
364
364
|
path: .
|
|
365
|
-
runtime: "true"
|
|
366
|
-
policy: codex-publish
|
|
367
|
-
upload-artifact: "true"
|
|
368
|
-
artifact-name: codex-plugin-doctor-reports
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
365
|
+
runtime: "true"
|
|
366
|
+
policy: codex-publish
|
|
367
|
+
upload-artifact: "true"
|
|
368
|
+
artifact-name: codex-plugin-doctor-reports
|
|
369
|
+
review-bundle: "true"
|
|
370
|
+
review-bundle-verify: "true"
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
The action writes `codex-plugin-doctor-summary.md`, `codex-plugin-doctor-report.json`, optional `codex-plugin-doctor.sarif`, and optional signed `review-bundle/` files to `codex-plugin-doctor-reports`, appends the Markdown report to the GitHub Actions step summary, uploads the report directory as an artifact, and then returns the real validation exit code. Review bundle generation requires a signing key environment variable such as `CODEX_PLUGIN_DOCTOR_SIGNING_KEY`. For runtime probing, SARIF output, review bundle artifacts, installed plugin cache checks, CI policy presets, and pinned release examples, see [GitHub Action Usage](./docs/engineering/github-action-usage.md).
|
|
372
374
|
|
|
373
375
|
To self-test this repository after cloning it:
|
|
374
376
|
|
|
@@ -34,6 +34,14 @@ export interface DoctorReviewBundleManifest {
|
|
|
34
34
|
attestationJson: string;
|
|
35
35
|
releaseEvidenceJson: string;
|
|
36
36
|
};
|
|
37
|
+
integrity?: {
|
|
38
|
+
algorithm: "sha256";
|
|
39
|
+
files: Record<string, {
|
|
40
|
+
path: string;
|
|
41
|
+
digest: string;
|
|
42
|
+
bytes: number;
|
|
43
|
+
}>;
|
|
44
|
+
};
|
|
37
45
|
}
|
|
38
46
|
export interface DoctorReviewBundle {
|
|
39
47
|
manifest: DoctorReviewBundleManifest;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { buildDoctorAttestation, renderDoctorAttestationJson, verifyDoctorAttestation } from "./attestation.js";
|
|
4
5
|
import { buildDoctorReleaseEvidenceReport, renderDoctorReleaseEvidenceJson, verifyDoctorReleaseEvidence } from "./release-evidence.js";
|
|
@@ -30,6 +31,9 @@ function runtimePolicyRank(policy) {
|
|
|
30
31
|
? 1
|
|
31
32
|
: 0;
|
|
32
33
|
}
|
|
34
|
+
function sha256(content) {
|
|
35
|
+
return `sha256:${createHash("sha256").update(content).digest("hex")}`;
|
|
36
|
+
}
|
|
33
37
|
function relativeBundleFiles() {
|
|
34
38
|
return {
|
|
35
39
|
manifest: "manifest.json",
|
|
@@ -82,7 +86,6 @@ async function writeBundleFiles(bundle) {
|
|
|
82
86
|
const files = bundle.manifest.files;
|
|
83
87
|
await mkdir(outputDirectory, { recursive: true });
|
|
84
88
|
await Promise.all([
|
|
85
|
-
writeFile(path.join(outputDirectory, files.manifest), JSON.stringify(bundle.manifest, null, 2), "utf8"),
|
|
86
89
|
writeFile(path.join(outputDirectory, files.summary), renderSummary(bundle), "utf8"),
|
|
87
90
|
writeFile(path.join(outputDirectory, files.runtimePlanJson), renderDoctorRuntimePlanJson(bundle.runtimePlan), "utf8"),
|
|
88
91
|
writeFile(path.join(outputDirectory, files.runtimePlanMarkdown), renderDoctorRuntimePlanMarkdown(bundle.runtimePlan), "utf8"),
|
|
@@ -91,6 +94,27 @@ async function writeBundleFiles(bundle) {
|
|
|
91
94
|
writeFile(path.join(outputDirectory, files.attestationJson), renderDoctorAttestationJson(bundle.attestation), "utf8"),
|
|
92
95
|
writeFile(path.join(outputDirectory, files.releaseEvidenceJson), renderDoctorReleaseEvidenceJson(bundle.releaseEvidence), "utf8")
|
|
93
96
|
]);
|
|
97
|
+
bundle.manifest.integrity = await buildBundleIntegrity(outputDirectory, files);
|
|
98
|
+
await writeFile(path.join(outputDirectory, files.manifest), JSON.stringify(bundle.manifest, null, 2), "utf8");
|
|
99
|
+
}
|
|
100
|
+
async function buildBundleIntegrity(outputDirectory, files) {
|
|
101
|
+
const integrityEntries = await Promise.all(Object.entries(files)
|
|
102
|
+
.filter(([fileKey]) => fileKey !== "manifest")
|
|
103
|
+
.map(async ([fileKey, relativePath]) => {
|
|
104
|
+
const content = await readFile(path.join(outputDirectory, relativePath));
|
|
105
|
+
return [
|
|
106
|
+
fileKey,
|
|
107
|
+
{
|
|
108
|
+
path: relativePath,
|
|
109
|
+
digest: sha256(content),
|
|
110
|
+
bytes: content.byteLength
|
|
111
|
+
}
|
|
112
|
+
];
|
|
113
|
+
}));
|
|
114
|
+
return {
|
|
115
|
+
algorithm: "sha256",
|
|
116
|
+
files: Object.fromEntries(integrityEntries)
|
|
117
|
+
};
|
|
94
118
|
}
|
|
95
119
|
export async function buildDoctorReviewBundle(targetPath, options) {
|
|
96
120
|
const outputDirectory = path.resolve(options.outputDirectory);
|
|
@@ -291,6 +315,7 @@ export async function verifyDoctorReviewBundle(bundleDirectory, options) {
|
|
|
291
315
|
let runtimePolicyStatus = "fail";
|
|
292
316
|
let attestation = null;
|
|
293
317
|
let releaseEvidence = null;
|
|
318
|
+
let integrityStatus = "pass";
|
|
294
319
|
try {
|
|
295
320
|
const manifestArtifact = await readBundleJsonFile(resolvedBundleDirectory, "manifest.json");
|
|
296
321
|
if (isDoctorReviewBundleManifest(manifestArtifact)) {
|
|
@@ -336,6 +361,55 @@ export async function verifyDoctorReviewBundle(bundleDirectory, options) {
|
|
|
336
361
|
});
|
|
337
362
|
}
|
|
338
363
|
}
|
|
364
|
+
if (manifest?.integrity) {
|
|
365
|
+
if (manifest.integrity.algorithm !== "sha256") {
|
|
366
|
+
integrityStatus = "fail";
|
|
367
|
+
checks.push({
|
|
368
|
+
id: "review_bundle.integrity.algorithm",
|
|
369
|
+
status: "fail",
|
|
370
|
+
message: "The review bundle manifest uses an unsupported integrity algorithm."
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
else {
|
|
374
|
+
checks.push({
|
|
375
|
+
id: "review_bundle.integrity.algorithm",
|
|
376
|
+
status: "pass",
|
|
377
|
+
message: "The review bundle manifest uses SHA-256 integrity digests."
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
for (const [fileKey, expected] of Object.entries(manifest.integrity.files)) {
|
|
381
|
+
try {
|
|
382
|
+
const content = await readFile(path.join(resolvedBundleDirectory, expected.path));
|
|
383
|
+
const digest = sha256(content);
|
|
384
|
+
const matches = digest === expected.digest && content.byteLength === expected.bytes;
|
|
385
|
+
if (!matches) {
|
|
386
|
+
integrityStatus = "fail";
|
|
387
|
+
}
|
|
388
|
+
checks.push({
|
|
389
|
+
id: `review_bundle.integrity.${fileKey}`,
|
|
390
|
+
status: matches ? "pass" : "fail",
|
|
391
|
+
message: matches
|
|
392
|
+
? `${expected.path} matches the manifest integrity digest.`
|
|
393
|
+
: `${expected.path} does not match the manifest integrity digest.`
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
catch {
|
|
397
|
+
integrityStatus = "fail";
|
|
398
|
+
checks.push({
|
|
399
|
+
id: `review_bundle.integrity.${fileKey}`,
|
|
400
|
+
status: "fail",
|
|
401
|
+
message: `${expected.path} could not be read for integrity verification.`
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
checks.push({
|
|
408
|
+
id: "review_bundle.integrity.present",
|
|
409
|
+
status: "pass",
|
|
410
|
+
message: "No manifest integrity block was present; skipping digest checks for backward compatibility."
|
|
411
|
+
});
|
|
412
|
+
}
|
|
339
413
|
try {
|
|
340
414
|
const runtimePlan = await readBundleJsonFile(resolvedBundleDirectory, files.runtimePlanJson);
|
|
341
415
|
runtimePlanStatus = isPlainObject(runtimePlan) && runtimePlan.kind === "doctor.runtime.plan" ? "pass" : "fail";
|
|
@@ -422,7 +496,7 @@ export async function verifyDoctorReviewBundle(bundleDirectory, options) {
|
|
|
422
496
|
exitCode: failedChecks.length === 0 ? 0 : 1,
|
|
423
497
|
summary: {
|
|
424
498
|
manifest: manifestStatus,
|
|
425
|
-
files: fileChecks.every((check) => check.status === "pass") ? "pass" : "fail",
|
|
499
|
+
files: fileChecks.every((check) => check.status === "pass") && integrityStatus === "pass" ? "pass" : "fail",
|
|
426
500
|
runtimePlan: runtimePlanStatus,
|
|
427
501
|
runtimePolicy: runtimePolicyStatus,
|
|
428
502
|
attestation: attestation?.status ?? "fail",
|
package/dist/run-cli.js
CHANGED
|
@@ -73,7 +73,7 @@ const defaultIo = {
|
|
|
73
73
|
}
|
|
74
74
|
};
|
|
75
75
|
function printUsage(io) {
|
|
76
|
-
io.writeStderr("Usage: codex-plugin-doctor check <path|--installed> [filter] [--policy codex-publish|mcp-strict|security] [--compat] [--json|--markdown|--badge-json|--badge-markdown] [--output <path>] [--history <path>] [--runtime] [--require-runtime-approval --runtime-approval-digest <digest>] [--verbose-runtime] [--explain] [--no-animations] [--ascii]\n codex-plugin-doctor audit --installed [filter] [--policy codex-publish|mcp-strict|security] [--security] [--compat] [--json] [--output <path>] [--cache] [--changed]\n codex-plugin-doctor mcp <path> [--json] [--output <path>]\n codex-plugin-doctor security <path> [--policy security] [--json|--scorecard]\n codex-plugin-doctor compat <path> [--all|--client <client>] [--json] [--scorecard] [--output <path>] [--install-preview|--apply --backup]\n codex-plugin-doctor fix <path> (--dry-run|--interactive --backup|--apply --backup)\n codex-plugin-doctor history <history.jsonl> [--json] [--fail-on-regression]\n codex-plugin-doctor doctor [npm <package>|contract|corpus|runtime-plan <path> [--json|--markdown] [--output <path>]|runtime-policy <path> [--json] [--output <path>]|review-bundle <path> --output <dir> --sign-key-env NAME [--json] [--allow-dirty] [--allow-untagged]|review-bundle verify <bundle-dir> --target <path> --sign-key-env NAME [--json]|review-bundle diff --before <dir> --after <dir> [--json]|attest <path> [--sign-key-env NAME]|attest verify <attestation.json> --target <path> --sign-key-env NAME|release-evidence <path> --sign-key-env NAME [--allow-dirty] [--allow-untagged] [--require-runtime-approval --runtime-approval-digest <digest>]|release-evidence verify <evidence.json> --target <path> --sign-key-env NAME|release-evidence asset <path> --tag <tag> --output <evidence.json> --sign-key-env NAME [--upload]|mcp <path>|inspector <path>|diff --before <path> --after <path>|recommend <path>|trust <path>|perf <path> [--max-total-ms <ms>] [--max-stage-ms stage=ms]|export --bundle <path>|snapshot|clients|--json|--update-check]\n codex-plugin-doctor init [path] [--template skill-only|mcp-stdio|mcp-http|full-runtime]\n codex-plugin-doctor init-ci [path]\n codex-plugin-doctor self-test\n codex-plugin-doctor list --installed\n codex-plugin-doctor explain <finding-id>\n codex-plugin-doctor --version\n\nFirst run:\n codex-plugin-doctor doctor\n codex-plugin-doctor self-test\n codex-plugin-doctor init my-plugin\n codex-plugin-doctor check . --runtime --explain");
|
|
76
|
+
io.writeStderr("Usage: codex-plugin-doctor check <path|--installed> [filter] [--policy codex-publish|mcp-strict|security] [--compat] [--json|--markdown|--badge-json|--badge-markdown] [--output <path>] [--history <path>] [--runtime] [--require-runtime-approval --runtime-approval-digest <digest>] [--verbose-runtime] [--explain] [--no-animations] [--ascii]\n codex-plugin-doctor audit --installed [filter] [--policy codex-publish|mcp-strict|security] [--security] [--compat] [--json] [--output <path>] [--cache] [--changed]\n codex-plugin-doctor mcp <path> [--json] [--output <path>]\n codex-plugin-doctor security <path> [--policy security] [--json|--scorecard]\n codex-plugin-doctor compat <path> [--all|--client <client>] [--json] [--scorecard] [--output <path>] [--install-preview|--apply --backup]\n codex-plugin-doctor fix <path> (--dry-run|--interactive --backup|--apply --backup)\n codex-plugin-doctor history <history.jsonl> [--json] [--fail-on-regression]\n codex-plugin-doctor doctor [npm <package>|contract|corpus|runtime-plan <path> [--json|--markdown] [--output <path>]|runtime-policy <path> [--json] [--output <path>]|review-bundle <path> --output <dir> --sign-key-env NAME [--json] [--allow-dirty] [--allow-untagged]|review-bundle verify <bundle-dir> --target <path> --sign-key-env NAME [--json] [--output <path>]|review-bundle diff --before <dir> --after <dir> [--json]|attest <path> [--sign-key-env NAME]|attest verify <attestation.json> --target <path> --sign-key-env NAME|release-evidence <path> --sign-key-env NAME [--allow-dirty] [--allow-untagged] [--require-runtime-approval --runtime-approval-digest <digest>]|release-evidence verify <evidence.json> --target <path> --sign-key-env NAME|release-evidence asset <path> --tag <tag> --output <evidence.json> --sign-key-env NAME [--upload]|mcp <path>|inspector <path>|diff --before <path> --after <path>|recommend <path>|trust <path>|perf <path> [--max-total-ms <ms>] [--max-stage-ms stage=ms]|export --bundle <path>|snapshot|clients|--json|--update-check]\n codex-plugin-doctor init [path] [--template skill-only|mcp-stdio|mcp-http|full-runtime]\n codex-plugin-doctor init-ci [path]\n codex-plugin-doctor self-test\n codex-plugin-doctor list --installed\n codex-plugin-doctor explain <finding-id>\n codex-plugin-doctor --version\n\nFirst run:\n codex-plugin-doctor doctor\n codex-plugin-doctor self-test\n codex-plugin-doctor init my-plugin\n codex-plugin-doctor check . --runtime --explain");
|
|
77
77
|
}
|
|
78
78
|
const performanceStageNames = new Set([
|
|
79
79
|
"validation",
|
|
@@ -449,6 +449,8 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
449
449
|
: null;
|
|
450
450
|
const verifyFlags = bundleDirectory ? remainingArgs.slice(2) : remainingArgs.slice(1);
|
|
451
451
|
const jsonOutput = verifyFlags.includes("--json");
|
|
452
|
+
const outputIndex = verifyFlags.indexOf("--output");
|
|
453
|
+
const outputPath = outputIndex === -1 ? null : verifyFlags[outputIndex + 1];
|
|
452
454
|
const targetIndex = verifyFlags.indexOf("--target");
|
|
453
455
|
const targetPath = targetIndex === -1 ? null : verifyFlags[targetIndex + 1];
|
|
454
456
|
const signKeyEnvIndex = verifyFlags.indexOf("--sign-key-env");
|
|
@@ -465,6 +467,10 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
465
467
|
io.writeStderr("Missing path after --target.");
|
|
466
468
|
return 2;
|
|
467
469
|
}
|
|
470
|
+
if (outputIndex !== -1 && (!outputPath || outputPath.startsWith("--"))) {
|
|
471
|
+
io.writeStderr("Missing path after --output.");
|
|
472
|
+
return 2;
|
|
473
|
+
}
|
|
468
474
|
if (signKeyEnvIndex === -1) {
|
|
469
475
|
io.writeStderr("Missing signing key. Use --sign-key-env <name>.");
|
|
470
476
|
return 2;
|
|
@@ -482,9 +488,13 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
482
488
|
signingKey,
|
|
483
489
|
targetPath
|
|
484
490
|
});
|
|
485
|
-
|
|
491
|
+
const renderedReport = jsonOutput
|
|
486
492
|
? renderDoctorReviewBundleVerificationJson(report)
|
|
487
|
-
: renderDoctorReviewBundleVerification(report)
|
|
493
|
+
: renderDoctorReviewBundleVerification(report);
|
|
494
|
+
if (outputPath) {
|
|
495
|
+
await writeFile(outputPath, renderedReport, "utf8");
|
|
496
|
+
}
|
|
497
|
+
io.writeStdout(renderedReport);
|
|
488
498
|
return report.exitCode;
|
|
489
499
|
}
|
|
490
500
|
const targetPath = remainingArgs[0] && !remainingArgs[0].startsWith("--")
|
package/package.json
CHANGED