codex-plugin-doctor 1.59.0 → 1.61.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/dist/run-cli.js CHANGED
@@ -19,7 +19,10 @@ import { buildDoctorRecommendations, renderDoctorRecommendations, renderDoctorRe
19
19
  import { buildDoctorExportBundle, renderDoctorExportBundle, renderDoctorExportBundleJson } from "./core/doctor-export-bundle.js";
20
20
  import { buildDoctorAttestation, renderDoctorAttestation, renderDoctorAttestationJson, renderDoctorAttestationVerification, renderDoctorAttestationVerificationJson, verifyDoctorAttestation } from "./core/attestation.js";
21
21
  import { buildDoctorOutputContract, renderDoctorOutputContract, renderDoctorOutputContractJson } from "./core/output-contract.js";
22
+ import { buildSubmissionArchivePreflight, submissionArchiveExitCode } from "./core/submission-archive-preflight.js";
22
23
  import { buildSubmissionPreflight } from "./core/submission-preflight.js";
24
+ import { discoverMcpServer } from "./core/mcp-discovery.js";
25
+ import { renderMcpDiscoveryReport } from "./reporting/render-mcp-discovery-report.js";
23
26
  import { buildDoctorValidationCorpusReport, renderDoctorValidationCorpusJson, renderDoctorValidationCorpusReport } from "./core/validation-corpus.js";
24
27
  import { buildExternalValidationCorpusReport, ExternalCorpusManifestError } from "./core/external-validation-corpus.js";
25
28
  import { buildCorpusQualityMetricsDiffReport, buildCorpusQualityMetricsReport, CorpusMetricsDiffError, CorpusMetricsManifestError, renderCorpusQualityMetricsDiffJson, renderCorpusQualityMetricsDiffMarkdown, renderCorpusQualityMetricsDiffText, renderCorpusQualityMetricsJson, renderCorpusQualityMetricsMarkdown, renderCorpusQualityMetricsText } from "./core/corpus-quality-metrics.js";
@@ -58,6 +61,7 @@ import { renderSuppressionList, renderSuppressionListJson, renderSuppressionMuta
58
61
  import { renderRuleExplanation } from "./reporting/render-rule-explanation.js";
59
62
  import { renderSarifReport } from "./reporting/render-sarif-report.js";
60
63
  import { renderTextReport } from "./reporting/render-text-report.js";
64
+ import { renderSubmissionArchiveJson, renderSubmissionArchiveMarkdown, renderSubmissionArchiveText } from "./reporting/render-submission-archive-report.js";
61
65
  import { renderSubmissionPreflightJson, renderSubmissionPreflightMarkdown, renderSubmissionPreflightText, submissionPreflightExitCode } from "./reporting/render-submission-report.js";
62
66
  import { applyPolicyToDepAudit, applyPolicyToDoctorConfig, applyPolicyToSecurityAudit, parsePolicyPack, policyEnablesRuntime, policyFailsOnWarnings, policyPackNames } from "./policy/policy-packs.js";
63
67
  import { findRuleDefinition } from "./rules/rule-catalog.js";
@@ -97,6 +101,7 @@ function writeExactStdout(io, message) {
97
101
  }
98
102
  class CliUsageError extends Error {
99
103
  }
104
+ const discoveryUsage = "Usage: codex-plugin-doctor doctor discover <url> --allow-network [--allow-local-network] [--json]";
100
105
  function parseRuntimeSandbox(flags, options = { requireRuntime: true }) {
101
106
  const index = flags.indexOf("--sandbox");
102
107
  if (index === -1) {
@@ -179,6 +184,7 @@ function parseRemoteNetworkFlags(flags, runtime) {
179
184
  }
180
185
  function printUsage(io) {
181
186
  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 [--allow-network [--allow-local-network]]] [--sandbox docker] [--require-runtime-approval --runtime-approval-digest <digest>] [--verbose-runtime] [--explain] [--no-animations] [--ascii] [--changed-since <ref>] [--fail-on <rule-id>]\n codex-plugin-doctor audit --installed [filter] [--policy codex-publish|mcp-strict|security] [--security] [--compat] [--json] [--output <path>] [--cache] [--changed]\n codex-plugin-doctor audit deps <path> [--policy codex-publish|mcp-strict|security] [--recommend] [--json|--sarif] [--output <path>]\n codex-plugin-doctor mcp <path> [--runtime [--allow-network [--allow-local-network]]] [--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 suppress add <path> [--fingerprint <sha256> --reason <text> --expires-at YYYY-MM-DD] [--config <path>] [--json]\n codex-plugin-doctor suppress list <path> [--config <path>] [--json]\n codex-plugin-doctor suppress remove <path> [--fingerprint <sha256>|--index <n>] [--config <path>] [--json]\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 watch <path> [--runtime] [--json] [--output <path>] [--debounce-ms <ms>] [--max-iterations <n>] [--fail-fast] [--accumulate-json <path>]\n codex-plugin-doctor doctor [npm <package>|contract|corpus [--manifest <corpus.json>] [--json] [--output <path>]|corpus metrics --manifest <corpus.json> [--json|--markdown] [--output <path>] [--min-precision <0..1>] [--min-recall <0..1>] [--max-false-positive-rate <0..1>]|runtime-plan <path> [--sandbox docker] [--json|--markdown] [--output <path>]|runtime-policy <path> [--sandbox docker] [--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>] [--failures-only]|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 [--runtime [--allow-network [--allow-local-network]]] [--sandbox docker] [--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 [--runtime [--allow-network [--allow-local-network]]] [--sandbox docker] [--allow-dirty] [--allow-untagged] [--require-runtime-approval --runtime-approval-digest <digest>] [--upload]|mcp <path> [--runtime [--allow-network [--allow-local-network]]]|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 init-git-hooks [path] [--force] [--json]\n codex-plugin-doctor init-git-hooks [path] --remove [--json]\n codex-plugin-doctor completion bash|zsh|fish\n codex-plugin-doctor config validate <path> [--json]\n codex-plugin-doctor release check <path> [--json] [--runtime [--allow-network [--allow-local-network]]] [--sandbox docker]\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");
187
+ io.writeStderr(discoveryUsage);
182
188
  io.writeStderr("Registry readiness: codex-plugin-doctor registry check <server.json|directory> [--json] [--output <path>] [--require-registry-readiness]\n"
183
189
  + " codex-plugin-doctor registry inspect <server-name> --allow-network [--json] [--output <path>] [--require-registry-readiness]\n"
184
190
  + "Registry publication preflight: codex-plugin-doctor registry preflight <server.json|directory> [--allow-network] [--json] [--output <path>] [--require-publish-ready]");
@@ -186,7 +192,8 @@ function printUsage(io) {
186
192
  io.writeStderr("Suppression governance: codex-plugin-doctor suppress list <path> [--fail-on-expired] [--fail-on-invalid] [--warn-expiring-within-days <days>]\n codex-plugin-doctor suppress prune <path> [--apply] [--json]");
187
193
  io.writeStderr("Baseline gating: codex-plugin-doctor baseline create <path> --output <path> [--runtime]\n codex-plugin-doctor check <path> --baseline <path>");
188
194
  io.writeStderr("Remote MCP runtime flags (check, mcp, release check, and doctor release-evidence): --runtime --allow-network [--allow-local-network] [--allow-session-lifecycle] [--require-remote-reliability]\n --allow-session-lifecycle requires --runtime --allow-network and can terminate a remote session.\n --require-remote-reliability requires --runtime --allow-network, blocks non-pass reliability, and does not grant network consent.");
189
- io.writeStderr(" codex-plugin-doctor doctor submission <path> [--json|--markdown] [--output <path>] [--require-ready]");
195
+ io.writeStderr(" codex-plugin-doctor doctor submission <path> [--json|--markdown] [--output <path>] [--require-ready]"
196
+ + "\n codex-plugin-doctor doctor submission archive <zip> [--json|--markdown] [--output <path>] [--require-ready]");
190
197
  }
191
198
  const suppressUsageText = [
192
199
  "Usage:",
@@ -1048,6 +1055,90 @@ function parseSubmissionCommandArgs(args) {
1048
1055
  return new CliUsageError("Use either --json or --markdown, not both.");
1049
1056
  return { targetPath, jsonOutput, markdownOutput, outputPath, requireReady };
1050
1057
  }
1058
+ function parseSubmissionArchiveCommandArgs(args) {
1059
+ let targetPath = null;
1060
+ let jsonOutput = false;
1061
+ let markdownOutput = false;
1062
+ let outputPath = null;
1063
+ let requireReady = false;
1064
+ let optionsEnded = false;
1065
+ for (let index = 0; index < args.length; index += 1) {
1066
+ const argument = args[index];
1067
+ if (argument === "--" && !optionsEnded)
1068
+ optionsEnded = true;
1069
+ else if (optionsEnded) {
1070
+ if (targetPath === null)
1071
+ targetPath = argument;
1072
+ else
1073
+ return new CliUsageError(`Unexpected submission archive argument: ${argument}.`);
1074
+ }
1075
+ else if (argument === "--json") {
1076
+ if (jsonOutput)
1077
+ return new CliUsageError("Duplicate submission archive flag: --json.");
1078
+ jsonOutput = true;
1079
+ }
1080
+ else if (argument === "--markdown") {
1081
+ if (markdownOutput)
1082
+ return new CliUsageError("Duplicate submission archive flag: --markdown.");
1083
+ markdownOutput = true;
1084
+ }
1085
+ else if (argument === "--require-ready") {
1086
+ if (requireReady)
1087
+ return new CliUsageError("Duplicate submission archive flag: --require-ready.");
1088
+ requireReady = true;
1089
+ }
1090
+ else if (argument === "--output" || argument.startsWith("--output=")) {
1091
+ if (outputPath !== null)
1092
+ return new CliUsageError("Duplicate submission archive flag: --output.");
1093
+ const value = argument === "--output" ? args[index + 1] : argument.slice("--output=".length);
1094
+ if (!value || (argument === "--output" && value.startsWith("--")))
1095
+ return new CliUsageError("Missing path after --output.");
1096
+ outputPath = value;
1097
+ if (argument === "--output")
1098
+ index += 1;
1099
+ }
1100
+ else if (argument.startsWith("--"))
1101
+ return new CliUsageError(`Unknown submission archive flag: ${argument}.`);
1102
+ else if (targetPath === null)
1103
+ targetPath = argument;
1104
+ else
1105
+ return new CliUsageError(`Unexpected submission archive argument: ${argument}.`);
1106
+ }
1107
+ if (targetPath === null)
1108
+ return new CliUsageError("Missing archive path for submission.");
1109
+ if (jsonOutput && markdownOutput)
1110
+ return new CliUsageError("Use either --json or --markdown, not both.");
1111
+ return { targetPath, jsonOutput, markdownOutput, outputPath, requireReady };
1112
+ }
1113
+ function hasArchiveSubcommandTarget(args) {
1114
+ let optionsEnded = false;
1115
+ for (let index = 0; index < args.length; index += 1) {
1116
+ const argument = args[index];
1117
+ if (optionsEnded)
1118
+ return true;
1119
+ if (argument === "--") {
1120
+ optionsEnded = true;
1121
+ }
1122
+ else if (argument === "--output") {
1123
+ index += 1;
1124
+ }
1125
+ else if (!argument.startsWith("--")) {
1126
+ return true;
1127
+ }
1128
+ }
1129
+ return false;
1130
+ }
1131
+ async function submissionTargetExists(targetPath) {
1132
+ try {
1133
+ const { stat } = await import("node:fs/promises");
1134
+ await stat(targetPath);
1135
+ return true;
1136
+ }
1137
+ catch (error) {
1138
+ return !(typeof error === "object" && error !== null && "code" in error
1139
+ && error.code === "ENOENT");
1140
+ }
1141
+ }
1051
1142
  export async function runCli(args, io = defaultIo, options = {}) {
1052
1143
  const [command, maybePath, ...remainingArgs] = args;
1053
1144
  const runtimeSandboxSupported = (command === "check" &&
@@ -1104,6 +1195,34 @@ export async function runCli(args, io = defaultIo, options = {}) {
1104
1195
  return 0;
1105
1196
  }
1106
1197
  if (command === "doctor") {
1198
+ if (maybePath === "discover") {
1199
+ if (remainingArgs.length === 1 && remainingArgs[0] === "--help") {
1200
+ io.writeStdout(`${discoveryUsage}\nHTTP discovery only; runtime behavior is not tested.`);
1201
+ return 0;
1202
+ }
1203
+ const [url, ...flags] = remainingArgs;
1204
+ if (!url || url.startsWith("-")) {
1205
+ io.writeStderr(discoveryUsage);
1206
+ return 2;
1207
+ }
1208
+ const allowedFlags = new Set(["--allow-network", "--allow-local-network", "--json"]);
1209
+ if (flags.some((flag) => !allowedFlags.has(flag)) || new Set(flags).size !== flags.length) {
1210
+ io.writeStderr(`Invalid or duplicate discovery arguments. ${discoveryUsage}`);
1211
+ return 2;
1212
+ }
1213
+ if (!flags.includes("--allow-network")) {
1214
+ io.writeStderr("doctor discover requires explicit --allow-network consent.");
1215
+ return 2;
1216
+ }
1217
+ const report = await (options.discoverMcpServerImpl ?? discoverMcpServer)(url, {
1218
+ allowNetwork: true,
1219
+ allowLocalNetwork: flags.includes("--allow-local-network")
1220
+ });
1221
+ io.writeStdout(flags.includes("--json")
1222
+ ? JSON.stringify(report, null, 2)
1223
+ : renderMcpDiscoveryReport(report));
1224
+ return report.status === "discovered" ? 0 : report.status === "unsupported" ? 1 : 2;
1225
+ }
1107
1226
  const doctorFlags = maybePath?.startsWith("--")
1108
1227
  ? [maybePath, ...remainingArgs]
1109
1228
  : remainingArgs;
@@ -1158,6 +1277,25 @@ export async function runCli(args, io = defaultIo, options = {}) {
1158
1277
  return 0;
1159
1278
  }
1160
1279
  if (maybePath === "submission") {
1280
+ if (remainingArgs[0] === "archive") {
1281
+ const useLegacyDirectoryTarget = !hasArchiveSubcommandTarget(remainingArgs.slice(1))
1282
+ && await submissionTargetExists("archive");
1283
+ if (!useLegacyDirectoryTarget) {
1284
+ const parsedArchiveArgs = parseSubmissionArchiveCommandArgs(remainingArgs.slice(1));
1285
+ if (parsedArchiveArgs instanceof CliUsageError) {
1286
+ io.writeStderr(parsedArchiveArgs.message);
1287
+ return 2;
1288
+ }
1289
+ const report = await buildSubmissionArchivePreflight(parsedArchiveArgs.targetPath);
1290
+ const renderedReport = parsedArchiveArgs.jsonOutput
1291
+ ? renderSubmissionArchiveJson(report)
1292
+ : parsedArchiveArgs.markdownOutput ? renderSubmissionArchiveMarkdown(report) : renderSubmissionArchiveText(report);
1293
+ if (parsedArchiveArgs.outputPath)
1294
+ await writeFile(parsedArchiveArgs.outputPath, renderedReport, "utf8");
1295
+ writeExactStdout(io, renderedReport);
1296
+ return submissionArchiveExitCode(report, parsedArchiveArgs.requireReady);
1297
+ }
1298
+ }
1161
1299
  const parsedSubmissionArgs = parseSubmissionCommandArgs(remainingArgs);
1162
1300
  if (parsedSubmissionArgs instanceof CliUsageError) {
1163
1301
  io.writeStderr(parsedSubmissionArgs.message);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-plugin-doctor",
3
- "version": "1.59.0",
3
+ "version": "1.61.0",
4
4
  "description": "CLI-first validator for Codex plugins, skills, and MCP package surfaces with runtime MCP protocol validation.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -57,12 +57,15 @@
57
57
  },
58
58
  "devDependencies": {
59
59
  "@types/node": "^24.2.1",
60
+ "@types/yauzl": "^3.4.0",
60
61
  "tsx": "^4.20.4",
61
62
  "typescript": "^5.9.2",
62
63
  "vitest": "^3.2.4"
63
64
  },
64
65
  "dependencies": {
65
66
  "fast-xml-parser": "^5.11.0",
66
- "yaml": "^2.9.0"
67
+ "iconv-lite": "^0.7.3",
68
+ "yaml": "^2.9.0",
69
+ "yauzl": "^3.4.0"
67
70
  }
68
71
  }