codex-plugin-doctor 0.8.0 → 0.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 +16 -10
- package/dist/core/environment-doctor.js +9 -1
- package/dist/core/fix-plan.d.ts +1 -1
- package/dist/reporting/render-text-report.d.ts +1 -0
- package/dist/reporting/render-text-report.js +10 -0
- package/dist/run-cli.d.ts +1 -0
- package/dist/run-cli.js +53 -8
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -166,6 +166,7 @@ codex-plugin-doctor self-test
|
|
|
166
166
|
codex-plugin-doctor doctor
|
|
167
167
|
codex-plugin-doctor init my-plugin
|
|
168
168
|
codex-plugin-doctor compat .
|
|
169
|
+
codex-plugin-doctor compat . --all --scorecard
|
|
169
170
|
codex-plugin-doctor compat . --client codex
|
|
170
171
|
codex-plugin-doctor compat . --client generic-mcp
|
|
171
172
|
codex-plugin-doctor compat . --client claude-desktop
|
|
@@ -184,6 +185,7 @@ codex-plugin-doctor check . --profile ci
|
|
|
184
185
|
codex-plugin-doctor check . --profile strict
|
|
185
186
|
codex-plugin-doctor check . --profile publish
|
|
186
187
|
codex-plugin-doctor check . --json
|
|
188
|
+
codex-plugin-doctor check . --explain
|
|
187
189
|
codex-plugin-doctor check . --json --output report.json
|
|
188
190
|
codex-plugin-doctor check . --markdown --output report.md
|
|
189
191
|
codex-plugin-doctor check . --badge-json --output doctor-badge.json
|
|
@@ -198,13 +200,14 @@ codex-plugin-doctor history validation-history.jsonl
|
|
|
198
200
|
codex-plugin-doctor history validation-history.jsonl --json
|
|
199
201
|
codex-plugin-doctor history validation-history.jsonl --fail-on-regression
|
|
200
202
|
codex-plugin-doctor fix . --dry-run
|
|
203
|
+
codex-plugin-doctor fix . --interactive --backup
|
|
201
204
|
codex-plugin-doctor fix . --apply --backup
|
|
202
205
|
codex-plugin-doctor check . --json --runtime --verbose-runtime
|
|
203
206
|
```
|
|
204
207
|
|
|
205
208
|
`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`.
|
|
206
209
|
|
|
207
|
-
`doctor` checks the local environment, including package version, platform, Node version, npm global prefix, Codex home, and Codex plugin cache visibility.
|
|
210
|
+
`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.
|
|
208
211
|
|
|
209
212
|
`compat --client claude-desktop` checks whether the MCP package can be added to the local Claude Desktop setup. On Windows it looks for `%APPDATA%\Claude\claude_desktop_config.json`; on macOS it looks for `~/Library/Application Support/Claude/claude_desktop_config.json`. A valid existing config returns `PASS`, a missing Claude Desktop install returns `WARN`, and a malformed local config returns `FAIL` so you do not add new servers into a broken config file. If the package server name already exists in Claude Desktop, the command returns `WARN` with the duplicate server name. Add `--install-preview` to print the JSON snippet that should be merged into `claude_desktop_config.json`; it does not modify files. Use `--apply --backup` only when you want the CLI to create a timestamped backup and merge the server config. Apply mode refuses to overwrite duplicate server names.
|
|
210
213
|
|
|
@@ -212,15 +215,17 @@ codex-plugin-doctor check . --json --runtime --verbose-runtime
|
|
|
212
215
|
|
|
213
216
|
`compat --client cline` checks whether the MCP package can be added to Cline. It uses `CLINE_DIR/data/settings/cline_mcp_settings.json` when `CLINE_DIR` is set, otherwise `~/.cline/data/settings/cline_mcp_settings.json`. Add `--install-preview` to print the JSON snippet that should be merged into `cline_mcp_settings.json`.
|
|
214
217
|
|
|
215
|
-
`compat --scorecard` turns the compatibility matrix into a compact score summary. `PASS` maps to `100`, `WARN` maps to `70`, and `FAIL` or `SKIPPED` maps to `0`.
|
|
218
|
+
`compat --all` makes the all-client matrix explicit when you want Codex, Generic MCP, Claude Desktop, Cursor, Cline, and Windsurf in one run. `compat --scorecard` turns the compatibility matrix into a compact score summary. `PASS` maps to `100`, `WARN` maps to `70`, and `FAIL` or `SKIPPED` maps to `0`.
|
|
216
219
|
|
|
217
220
|
`check --profile ci|strict|publish` applies named validation policies. `ci` keeps default behavior, `strict` fails on warnings, and `publish` fails on warnings while enabling runtime probing by default.
|
|
218
221
|
|
|
222
|
+
`check --explain` adds inline rule catalog context to text reports, including why a finding matters, a more detailed fix path, and a compact example.
|
|
223
|
+
|
|
219
224
|
`check --badge-json` emits Shields endpoint-compatible JSON such as `{"schemaVersion":1,"label":"doctor","message":"PASS","color":"brightgreen"}`. `check --badge-markdown` emits a static shields.io Markdown badge for README or release notes. Badge output is intentionally limited to single package checks, not `check --installed`.
|
|
220
225
|
|
|
221
226
|
`check --history <path>` appends a compact JSONL validation snapshot after a single package check. `history <path>` reads the JSONL file and compares the latest run to the previous run, including status, finding-count deltas, and whether the latest run regressed. Add `history --json` for automation output or `history --fail-on-regression` when CI should fail after a worse latest run.
|
|
222
227
|
|
|
223
|
-
`fix --dry-run` renders safe automatic fix plans without changing files. `fix --
|
|
228
|
+
`fix --dry-run` renders safe automatic fix plans without changing files. `fix --interactive --backup` shows the same plan, then applies only after you type `yes`. `fix --apply --backup` applies supported safe fixes, such as manifest defaults and missing skills directories, after creating backups.
|
|
224
229
|
|
|
225
230
|
Optional local policy file:
|
|
226
231
|
|
|
@@ -255,9 +260,9 @@ jobs:
|
|
|
255
260
|
runs-on: ubuntu-latest
|
|
256
261
|
steps:
|
|
257
262
|
- uses: actions/checkout@v4
|
|
258
|
-
- uses: Esquetta/CodexPluginDoctor@v0.
|
|
263
|
+
- uses: Esquetta/CodexPluginDoctor@v0.9.0
|
|
259
264
|
with:
|
|
260
|
-
version: "0.
|
|
265
|
+
version: "0.9.0"
|
|
261
266
|
path: .
|
|
262
267
|
runtime: "false"
|
|
263
268
|
```
|
|
@@ -300,11 +305,12 @@ Recent validation waves covered:
|
|
|
300
305
|
|
|
301
306
|
Release preparation is reproducible from the repository:
|
|
302
307
|
|
|
303
|
-
```bash
|
|
304
|
-
npm run prepare-release
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
+
```bash
|
|
309
|
+
npm run prepare-release
|
|
310
|
+
npm run release-check
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
`prepare-release` runs tests, builds the TypeScript output, and performs `npm pack --dry-run`. `release-check` adds release preflight checks for a clean git tree, existing npm versions, existing version tags, tests, build, and pack dry-run.
|
|
308
314
|
|
|
309
315
|
Related docs:
|
|
310
316
|
|
|
@@ -32,7 +32,15 @@ export async function renderEnvironmentDoctor(terminalContext) {
|
|
|
32
32
|
`Node: ${report.node}`,
|
|
33
33
|
`npm global prefix: ${report.npmGlobalPrefix}`,
|
|
34
34
|
`Codex home: ${report.codexHome.status.toUpperCase()}${report.codexHome.path ? ` (${report.codexHome.path})` : ""}`,
|
|
35
|
-
`Codex plugin cache: ${report.codexPluginCache.status.toUpperCase()}${report.codexPluginCache.path ? ` (${report.codexPluginCache.path})` : ""}
|
|
35
|
+
`Codex plugin cache: ${report.codexPluginCache.status.toUpperCase()}${report.codexPluginCache.path ? ` (${report.codexPluginCache.path})` : ""}`,
|
|
36
|
+
"",
|
|
37
|
+
"Recommended next commands",
|
|
38
|
+
"-------------------------",
|
|
39
|
+
"codex-plugin-doctor self-test",
|
|
40
|
+
"codex-plugin-doctor list --installed",
|
|
41
|
+
"codex-plugin-doctor check . --runtime --explain",
|
|
42
|
+
"codex-plugin-doctor compat . --all --scorecard",
|
|
43
|
+
"codex-plugin-doctor init-ci ."
|
|
36
44
|
].join("\n");
|
|
37
45
|
}
|
|
38
46
|
export async function buildEnvironmentDoctorReport(terminalContext) {
|
package/dist/core/fix-plan.d.ts
CHANGED
|
@@ -25,7 +25,7 @@ export interface FixPlanJsonReport {
|
|
|
25
25
|
}>;
|
|
26
26
|
}
|
|
27
27
|
export declare function buildFixPlan(targetPath: string): Promise<FixPlan>;
|
|
28
|
-
export declare function renderFixPlan(plan: FixPlan, mode: "dry-run"): string;
|
|
28
|
+
export declare function renderFixPlan(plan: FixPlan, mode: "dry-run" | "interactive"): string;
|
|
29
29
|
export declare function applyFixPlan(targetPath: string): Promise<ApplyFixPlanResult>;
|
|
30
30
|
export declare function renderApplyFixResult(result: ApplyFixPlanResult): string;
|
|
31
31
|
export declare function renderFixPlanJsonReport(plan: FixPlan, options: {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { findRuleDefinition } from "../rules/rule-catalog.js";
|
|
1
2
|
function getCounts(result) {
|
|
2
3
|
const failCount = result.findings.filter((finding) => finding.severity === "fail").length;
|
|
3
4
|
const warnCount = result.findings.filter((finding) => finding.severity === "warn").length;
|
|
@@ -22,6 +23,7 @@ function getGlyphs(ascii) {
|
|
|
22
23
|
}
|
|
23
24
|
export function renderTextReport(result, options = {}) {
|
|
24
25
|
const ascii = options.ascii ?? false;
|
|
26
|
+
const explain = options.explain ?? false;
|
|
25
27
|
const glyphs = getGlyphs(ascii);
|
|
26
28
|
const { failCount, warnCount, totalCount } = getCounts(result);
|
|
27
29
|
const lines = [
|
|
@@ -58,6 +60,14 @@ export function renderTextReport(result, options = {}) {
|
|
|
58
60
|
lines.push(` Message: ${finding.message}`);
|
|
59
61
|
lines.push(` Impact: ${finding.impact}`);
|
|
60
62
|
lines.push(` Suggested fix: ${finding.suggestedFix}`);
|
|
63
|
+
if (explain) {
|
|
64
|
+
const rule = findRuleDefinition(finding.id);
|
|
65
|
+
if (rule) {
|
|
66
|
+
lines.push(` Why: ${rule.why}`);
|
|
67
|
+
lines.push(` Fix detail: ${rule.fix}`);
|
|
68
|
+
lines.push(` Example: ${rule.example}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
61
71
|
}
|
|
62
72
|
};
|
|
63
73
|
appendSection("Failures", failures, glyphs.fail);
|
package/dist/run-cli.d.ts
CHANGED
package/dist/run-cli.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { writeFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { createInterface } from "node:readline/promises";
|
|
3
4
|
import { fileURLToPath } from "node:url";
|
|
4
5
|
import { discoverInstalledPlugins, filterInstalledPlugins } from "./core/discover-installed-plugins.js";
|
|
5
6
|
import { appendValidationHistoryEntry, readValidationHistory, summarizeValidationHistory } from "./core/validation-history.js";
|
|
@@ -36,10 +37,22 @@ const defaultIo = {
|
|
|
36
37
|
},
|
|
37
38
|
writeStderr(message) {
|
|
38
39
|
process.stderr.write(`${message}\n`);
|
|
40
|
+
},
|
|
41
|
+
async readStdin(prompt) {
|
|
42
|
+
const readline = createInterface({
|
|
43
|
+
input: process.stdin,
|
|
44
|
+
output: process.stdout
|
|
45
|
+
});
|
|
46
|
+
try {
|
|
47
|
+
return await readline.question(prompt);
|
|
48
|
+
}
|
|
49
|
+
finally {
|
|
50
|
+
readline.close();
|
|
51
|
+
}
|
|
39
52
|
}
|
|
40
53
|
};
|
|
41
54
|
function printUsage(io) {
|
|
42
|
-
io.writeStderr("Usage: codex-plugin-doctor check <path|--installed> [filter] [--json|--markdown|--badge-json|--badge-markdown] [--output <path>] [--history <path>] [--runtime] [--verbose-runtime] [--no-animations] [--ascii]\n codex-plugin-doctor compat <path> [--client <client>] [--json] [--scorecard] [--output <path>] [--install-preview|--apply --backup]\n codex-plugin-doctor fix <path> (--dry-run|--apply --backup)\n codex-plugin-doctor history <history.jsonl> [--json] [--fail-on-regression]\n codex-plugin-doctor doctor\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");
|
|
55
|
+
io.writeStderr("Usage: codex-plugin-doctor check <path|--installed> [filter] [--json|--markdown|--badge-json|--badge-markdown] [--output <path>] [--history <path>] [--runtime] [--verbose-runtime] [--explain] [--no-animations] [--ascii]\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\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");
|
|
43
56
|
}
|
|
44
57
|
function renderInstalledPlugins(plugins) {
|
|
45
58
|
const lines = [
|
|
@@ -212,19 +225,24 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
212
225
|
}
|
|
213
226
|
if (command === "fix") {
|
|
214
227
|
if (!maybePath || maybePath.startsWith("--")) {
|
|
215
|
-
io.writeStderr("Missing target path. Usage: codex-plugin-doctor fix <path> (--dry-run|--apply --backup)");
|
|
228
|
+
io.writeStderr("Missing target path. Usage: codex-plugin-doctor fix <path> (--dry-run|--interactive --backup|--apply --backup)");
|
|
216
229
|
return 2;
|
|
217
230
|
}
|
|
218
231
|
const dryRun = remainingArgs.includes("--dry-run");
|
|
219
232
|
const apply = remainingArgs.includes("--apply");
|
|
233
|
+
const interactive = remainingArgs.includes("--interactive");
|
|
220
234
|
const backup = remainingArgs.includes("--backup");
|
|
221
235
|
const jsonOutput = remainingArgs.includes("--json");
|
|
222
|
-
if (apply && !backup) {
|
|
223
|
-
io.writeStderr("Fix
|
|
236
|
+
if ((apply || interactive) && !backup) {
|
|
237
|
+
io.writeStderr("Fix mode requires --backup.");
|
|
238
|
+
return 2;
|
|
239
|
+
}
|
|
240
|
+
if ([dryRun, apply, interactive].filter(Boolean).length !== 1) {
|
|
241
|
+
io.writeStderr("Choose exactly one fix mode: --dry-run, --interactive --backup, or --apply --backup.");
|
|
224
242
|
return 2;
|
|
225
243
|
}
|
|
226
|
-
if (
|
|
227
|
-
io.writeStderr("
|
|
244
|
+
if (interactive && jsonOutput) {
|
|
245
|
+
io.writeStderr("Interactive fix mode does not support --json.");
|
|
228
246
|
return 2;
|
|
229
247
|
}
|
|
230
248
|
if (dryRun) {
|
|
@@ -234,6 +252,21 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
234
252
|
: renderFixPlan(plan, "dry-run"));
|
|
235
253
|
return 0;
|
|
236
254
|
}
|
|
255
|
+
if (interactive) {
|
|
256
|
+
const plan = await buildFixPlan(maybePath);
|
|
257
|
+
io.writeStdout([
|
|
258
|
+
renderFixPlan(plan, "interactive"),
|
|
259
|
+
"",
|
|
260
|
+
"Type yes to apply these fixes with a backup. Anything else cancels."
|
|
261
|
+
].join("\n"));
|
|
262
|
+
const answer = (await io.readStdin?.("Apply fixes? ") ?? "").trim().toLowerCase();
|
|
263
|
+
if (answer !== "yes") {
|
|
264
|
+
io.writeStdout("Fix cancelled. No files changed.");
|
|
265
|
+
return 0;
|
|
266
|
+
}
|
|
267
|
+
io.writeStdout(renderApplyFixResult(await applyFixPlan(maybePath)));
|
|
268
|
+
return 0;
|
|
269
|
+
}
|
|
237
270
|
const result = await applyFixPlan(maybePath);
|
|
238
271
|
io.writeStdout(jsonOutput
|
|
239
272
|
? renderFixPlanJsonReport(result.plan, {
|
|
@@ -254,6 +287,7 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
254
287
|
const installPreview = compatFlags.includes("--install-preview");
|
|
255
288
|
const applyInstall = compatFlags.includes("--apply");
|
|
256
289
|
const backupInstall = compatFlags.includes("--backup");
|
|
290
|
+
const allClients = compatFlags.includes("--all");
|
|
257
291
|
const clientIndex = compatFlags.indexOf("--client");
|
|
258
292
|
const clientFilter = clientIndex === -1 ? null : compatFlags[clientIndex + 1];
|
|
259
293
|
const outputIndex = compatFlags.indexOf("--output");
|
|
@@ -262,6 +296,10 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
262
296
|
io.writeStderr("Missing client after --client.");
|
|
263
297
|
return 2;
|
|
264
298
|
}
|
|
299
|
+
if (allClients && clientFilter) {
|
|
300
|
+
io.writeStderr("Use either --all or --client, not both.");
|
|
301
|
+
return 2;
|
|
302
|
+
}
|
|
265
303
|
if (outputIndex !== -1 && (!outputPath || outputPath.startsWith("--"))) {
|
|
266
304
|
io.writeStderr("Missing path after --output.");
|
|
267
305
|
return 2;
|
|
@@ -378,6 +416,7 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
378
416
|
const sarifOutput = normalizedFlags.includes("--sarif");
|
|
379
417
|
const runtimeProbeEnabled = normalizedFlags.includes("--runtime");
|
|
380
418
|
const verboseRuntime = normalizedFlags.includes("--verbose-runtime");
|
|
419
|
+
const explainFindings = normalizedFlags.includes("--explain");
|
|
381
420
|
const noAnimations = normalizedFlags.includes("--no-animations");
|
|
382
421
|
const asciiMode = normalizedFlags.includes("--ascii");
|
|
383
422
|
const installedSummary = normalizedFlags.includes("--all-summary");
|
|
@@ -460,7 +499,10 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
460
499
|
? buildMarkdownReport(item.result, { runtimeProbeEnabled: effectiveRuntimeProbeEnabled })
|
|
461
500
|
: jsonOutput
|
|
462
501
|
? renderJsonReport(item.result, { runtimeProbeEnabled: effectiveRuntimeProbeEnabled })
|
|
463
|
-
: renderTextReport(item.result, {
|
|
502
|
+
: renderTextReport(item.result, {
|
|
503
|
+
ascii: outputPolicy.style === "ascii",
|
|
504
|
+
explain: explainFindings
|
|
505
|
+
}))
|
|
464
506
|
.join("\n\n");
|
|
465
507
|
if (outputPath) {
|
|
466
508
|
await writeFile(outputPath, report, "utf8");
|
|
@@ -497,7 +539,10 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
497
539
|
? renderBadgeJson(result)
|
|
498
540
|
: badgeMarkdownOutput
|
|
499
541
|
? renderBadgeMarkdown(result)
|
|
500
|
-
: renderTextReport(result, {
|
|
542
|
+
: renderTextReport(result, {
|
|
543
|
+
ascii: outputPolicy.style === "ascii",
|
|
544
|
+
explain: explainFindings
|
|
545
|
+
});
|
|
501
546
|
if (outputPath) {
|
|
502
547
|
await writeFile(outputPath, report, "utf8");
|
|
503
548
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codex-plugin-doctor",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.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",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"generate-validation-artifacts": "node scripts/generate-validation-artifacts.mjs",
|
|
27
27
|
"prepare-rc": "tsx scripts/prepare-release-candidate.ts",
|
|
28
28
|
"prepare-release": "npm test && npm run build && npm pack --dry-run",
|
|
29
|
+
"release-check": "node scripts/release-check.mjs",
|
|
29
30
|
"prepublishOnly": "npm test && npm run build",
|
|
30
31
|
"test": "vitest run",
|
|
31
32
|
"test:watch": "vitest"
|