codex-plugin-doctor 1.26.0 → 1.28.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
@@ -44,6 +44,7 @@ import { renderCompatibilityReport } from "./reporting/render-compatibility-repo
44
44
  import { renderHistorySummary } from "./reporting/render-history-summary.js";
45
45
  import { renderJsonReport } from "./reporting/render-json-report.js";
46
46
  import { buildMarkdownReport } from "./reporting/render-markdown-report.js";
47
+ import { renderSuppressionList, renderSuppressionListJson, renderSuppressionMutation, renderSuppressionMutationJson } from "./reporting/render-suppression-management.js";
47
48
  import { renderRuleExplanation } from "./reporting/render-rule-explanation.js";
48
49
  import { renderSarifReport } from "./reporting/render-sarif-report.js";
49
50
  import { renderTextReport } from "./reporting/render-text-report.js";
@@ -55,6 +56,7 @@ import { createLiveStatusRenderer } from "./terminal/live-status-renderer.js";
55
56
  import { determineOutputPolicy } from "./terminal/output-policy.js";
56
57
  import { getSpinner } from "./terminal/spinner-registry.js";
57
58
  import { packageVersion } from "./version.js";
59
+ import { addSuppression, listSuppressions, readRawDoctorConfig, removeSuppressionByFingerprint, removeSuppressionByIndex, SuppressionManagementError, writeRawDoctorConfig } from "./index.js";
58
60
  const defaultIo = {
59
61
  writeStdout(message) {
60
62
  process.stdout.write(`${message}\n`);
@@ -76,7 +78,16 @@ const defaultIo = {
76
78
  }
77
79
  };
78
80
  function printUsage(io) {
79
- 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 audit deps <path> [--json] [--output <path>]\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 watch <path> [--runtime] [--json] [--output <path>] [--debounce-ms <ms>]\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>] [--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 [--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 init-git-hooks [path] [--force] [--json]\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");
81
+ 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 audit deps <path> [--json] [--output <path>]\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 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>]\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>] [--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 [--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 init-git-hooks [path] [--force] [--json]\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");
82
+ }
83
+ const suppressUsageText = [
84
+ "Usage:",
85
+ " codex-plugin-doctor suppress add <path> [--fingerprint <sha256> --reason <text> --expires-at YYYY-MM-DD] [--config <path>] [--json]",
86
+ " codex-plugin-doctor suppress list <path> [--config <path>] [--json]",
87
+ " codex-plugin-doctor suppress remove <path> [--fingerprint <sha256>|--index <n>] [--config <path>] [--json]"
88
+ ].join("\n");
89
+ function printSuppressUsage(io) {
90
+ io.writeStderr(suppressUsageText);
80
91
  }
81
92
  const performanceStageNames = new Set([
82
93
  "validation",
@@ -94,6 +105,390 @@ function parseNonNegativeNumber(value) {
94
105
  const parsed = Number(value);
95
106
  return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
96
107
  }
108
+ function parseSuppressCommand(args) {
109
+ const [action, targetPath, ...flags] = args;
110
+ if (!action) {
111
+ return {
112
+ message: "Missing suppress action.",
113
+ showUsage: true
114
+ };
115
+ }
116
+ if (action !== "add" && action !== "list" && action !== "remove") {
117
+ return {
118
+ message: `Unknown suppress action: ${action}.`,
119
+ showUsage: true
120
+ };
121
+ }
122
+ if (!targetPath || targetPath.startsWith("--")) {
123
+ return {
124
+ message: "Missing suppression target path.",
125
+ showUsage: true
126
+ };
127
+ }
128
+ let configPath = null;
129
+ let jsonOutput = false;
130
+ let fingerprint = null;
131
+ let reason = null;
132
+ let expiresAt = null;
133
+ let indexValue = null;
134
+ const seenFlags = new Set();
135
+ const markSeenFlag = (flag) => {
136
+ if (seenFlags.has(flag)) {
137
+ return {
138
+ message: `Duplicate suppress flag: ${flag}.`,
139
+ showUsage: false
140
+ };
141
+ }
142
+ seenFlags.add(flag);
143
+ return null;
144
+ };
145
+ for (let index = 0; index < flags.length; index += 1) {
146
+ const flag = flags[index];
147
+ if (flag === "--json") {
148
+ const duplicateError = markSeenFlag(flag);
149
+ if (duplicateError) {
150
+ return duplicateError;
151
+ }
152
+ jsonOutput = true;
153
+ continue;
154
+ }
155
+ if (flag === "--config") {
156
+ const duplicateError = markSeenFlag(flag);
157
+ if (duplicateError) {
158
+ return duplicateError;
159
+ }
160
+ const value = flags[index + 1];
161
+ if (!value || value.startsWith("--")) {
162
+ return {
163
+ message: "Missing path after --config.",
164
+ showUsage: false
165
+ };
166
+ }
167
+ configPath = value;
168
+ index += 1;
169
+ continue;
170
+ }
171
+ if (action === "add" && flag === "--fingerprint") {
172
+ const duplicateError = markSeenFlag(flag);
173
+ if (duplicateError) {
174
+ return duplicateError;
175
+ }
176
+ const value = flags[index + 1];
177
+ if (!value || value.startsWith("--")) {
178
+ return {
179
+ message: "Missing fingerprint after --fingerprint.",
180
+ showUsage: false
181
+ };
182
+ }
183
+ fingerprint = value;
184
+ index += 1;
185
+ continue;
186
+ }
187
+ if (action === "add" && flag === "--reason") {
188
+ const duplicateError = markSeenFlag(flag);
189
+ if (duplicateError) {
190
+ return duplicateError;
191
+ }
192
+ const value = flags[index + 1];
193
+ if (!value || value.startsWith("--")) {
194
+ return {
195
+ message: "Missing reason after --reason.",
196
+ showUsage: false
197
+ };
198
+ }
199
+ reason = value;
200
+ index += 1;
201
+ continue;
202
+ }
203
+ if (action === "add" && flag === "--expires-at") {
204
+ const duplicateError = markSeenFlag(flag);
205
+ if (duplicateError) {
206
+ return duplicateError;
207
+ }
208
+ const value = flags[index + 1];
209
+ if (!value || value.startsWith("--")) {
210
+ return {
211
+ message: "Missing date after --expires-at.",
212
+ showUsage: false
213
+ };
214
+ }
215
+ expiresAt = value;
216
+ index += 1;
217
+ continue;
218
+ }
219
+ if (action === "remove" && flag === "--fingerprint") {
220
+ const duplicateError = markSeenFlag(flag);
221
+ if (duplicateError) {
222
+ return duplicateError;
223
+ }
224
+ const value = flags[index + 1];
225
+ if (!value || value.startsWith("--")) {
226
+ return {
227
+ message: "Missing fingerprint after --fingerprint.",
228
+ showUsage: false
229
+ };
230
+ }
231
+ fingerprint = value;
232
+ index += 1;
233
+ continue;
234
+ }
235
+ if (action === "remove" && flag === "--index") {
236
+ const duplicateError = markSeenFlag(flag);
237
+ if (duplicateError) {
238
+ return duplicateError;
239
+ }
240
+ const value = flags[index + 1];
241
+ if (!value || value.startsWith("--")) {
242
+ return {
243
+ message: "Missing value after --index.",
244
+ showUsage: false
245
+ };
246
+ }
247
+ indexValue = value;
248
+ index += 1;
249
+ continue;
250
+ }
251
+ return {
252
+ message: `Unknown suppress flag: ${flag}.`,
253
+ showUsage: false
254
+ };
255
+ }
256
+ if (action === "list") {
257
+ return {
258
+ action,
259
+ targetPath,
260
+ configPath,
261
+ jsonOutput
262
+ };
263
+ }
264
+ if (action === "add") {
265
+ const suppliedFlagCount = [fingerprint, reason, expiresAt].filter((value) => value !== null).length;
266
+ if (suppliedFlagCount === 0) {
267
+ return {
268
+ action,
269
+ targetPath,
270
+ configPath,
271
+ jsonOutput,
272
+ interactive: true
273
+ };
274
+ }
275
+ if (suppliedFlagCount !== 3 || !fingerprint || !reason || !expiresAt) {
276
+ return {
277
+ message: "suppress add requires --fingerprint, --reason, and --expires-at together.",
278
+ showUsage: false
279
+ };
280
+ }
281
+ return {
282
+ action,
283
+ targetPath,
284
+ configPath,
285
+ jsonOutput,
286
+ interactive: false,
287
+ fingerprint,
288
+ reason,
289
+ expiresAt
290
+ };
291
+ }
292
+ if (fingerprint && indexValue !== null) {
293
+ return {
294
+ message: "Use exactly one of --index or --fingerprint for suppress remove.",
295
+ showUsage: false
296
+ };
297
+ }
298
+ if (!fingerprint && indexValue === null) {
299
+ return {
300
+ action,
301
+ targetPath,
302
+ configPath,
303
+ jsonOutput,
304
+ interactive: true
305
+ };
306
+ }
307
+ if (indexValue !== null) {
308
+ if (!/^\d+$/.test(indexValue)) {
309
+ return {
310
+ message: "--index must be a non-negative integer.",
311
+ showUsage: false
312
+ };
313
+ }
314
+ const parsedIndex = Number(indexValue);
315
+ if (!Number.isSafeInteger(parsedIndex)) {
316
+ return {
317
+ message: "--index must be a non-negative integer.",
318
+ showUsage: false
319
+ };
320
+ }
321
+ return {
322
+ action,
323
+ targetPath,
324
+ configPath,
325
+ jsonOutput,
326
+ interactive: false,
327
+ selector: {
328
+ type: "index",
329
+ index: parsedIndex
330
+ }
331
+ };
332
+ }
333
+ return {
334
+ action,
335
+ targetPath,
336
+ configPath,
337
+ jsonOutput,
338
+ interactive: false,
339
+ selector: {
340
+ type: "fingerprint",
341
+ fingerprint: fingerprint
342
+ }
343
+ };
344
+ }
345
+ function formatLocalDate(date) {
346
+ const year = date.getFullYear();
347
+ const month = String(date.getMonth() + 1).padStart(2, "0");
348
+ const day = String(date.getDate()).padStart(2, "0");
349
+ return `${year}-${month}-${day}`;
350
+ }
351
+ function defaultSuppressionExpiry(now) {
352
+ const expiry = new Date(now.getFullYear(), now.getMonth(), now.getDate());
353
+ expiry.setDate(expiry.getDate() + 30);
354
+ return formatLocalDate(expiry);
355
+ }
356
+ function parseInteractiveSelection(answer, count) {
357
+ const trimmed = answer.trim();
358
+ if (!/^\d+$/.test(trimmed)) {
359
+ return null;
360
+ }
361
+ const selection = Number(trimmed);
362
+ return Number.isSafeInteger(selection) && selection >= 1 && selection <= count
363
+ ? selection - 1
364
+ : null;
365
+ }
366
+ function renderInteractiveFindingCandidates(findings) {
367
+ return findings
368
+ .map((finding, index) => `[${index + 1}] ${finding.severity.toUpperCase()} ${finding.id} - ${finding.message}\n ${finding.fingerprint}`)
369
+ .join("\n");
370
+ }
371
+ function renderInteractiveSuppressionCandidates(suppressions) {
372
+ return suppressions
373
+ .map((suppression, selectionIndex) => {
374
+ const prefix = `[${selectionIndex + 1}] ${suppression.status.toUpperCase()} config index ${suppression.index}`;
375
+ return suppression.status === "invalid"
376
+ ? `${prefix} ${suppression.invalidField}`
377
+ : `${prefix} ${suppression.fingerprint}`;
378
+ })
379
+ .join("\n");
380
+ }
381
+ async function executeSuppressCommand(command, io, options = {}) {
382
+ const writeConfig = options.writeRawDoctorConfigImpl ?? writeRawDoctorConfig;
383
+ if (command.action !== "list" && command.interactive) {
384
+ if (command.jsonOutput) {
385
+ io.writeStderr(`Interactive suppress ${command.action} does not support --json.`);
386
+ return 2;
387
+ }
388
+ if (!io.readStdin) {
389
+ io.writeStderr(`Interactive suppress ${command.action} requires stdin input.`);
390
+ return 2;
391
+ }
392
+ }
393
+ try {
394
+ const rawConfig = await readRawDoctorConfig(command.targetPath, command.configPath);
395
+ const now = options.now?.() ?? new Date();
396
+ if (command.action === "list") {
397
+ const suppressions = listSuppressions(rawConfig.value, now);
398
+ io.writeStdout(command.jsonOutput
399
+ ? renderSuppressionListJson(rawConfig.configPath, suppressions)
400
+ : renderSuppressionList(rawConfig.configPath, suppressions));
401
+ return 0;
402
+ }
403
+ if (command.action === "add") {
404
+ if (command.interactive) {
405
+ listSuppressions(rawConfig.value, now);
406
+ const configuredResult = applyDoctorConfig(await (options.runCheckImpl ?? runCheck)(command.targetPath), await loadDoctorConfig(command.targetPath, command.configPath), { now });
407
+ const candidates = configuredResult.findings.filter((finding) => Boolean(finding.fingerprint) &&
408
+ !finding.id.startsWith("suppression."));
409
+ if (candidates.length === 0) {
410
+ io.writeStderr("No active fingerprinted findings are available to suppress.");
411
+ return 1;
412
+ }
413
+ io.writeStdout(renderInteractiveFindingCandidates(candidates));
414
+ const selection = parseInteractiveSelection(await io.readStdin("Select finding to suppress: "), candidates.length);
415
+ if (selection === null) {
416
+ io.writeStderr(`Selection must be a number from 1 to ${candidates.length}.`);
417
+ return 2;
418
+ }
419
+ const reason = (await io.readStdin("Reason: ")).trim();
420
+ if (!reason) {
421
+ io.writeStderr("Suppression reason must not be blank.");
422
+ return 2;
423
+ }
424
+ const defaultExpiry = defaultSuppressionExpiry(now);
425
+ const expiryAnswer = (await io.readStdin(`Expiration date [${defaultExpiry}]: `)).trim();
426
+ const result = addSuppression(rawConfig.value, {
427
+ fingerprint: candidates[selection].fingerprint,
428
+ reason,
429
+ expiresAt: expiryAnswer || defaultExpiry
430
+ });
431
+ const confirmation = await io.readStdin("Type yes to confirm: ");
432
+ if (confirmation.trim().toLowerCase() !== "yes") {
433
+ io.writeStdout("Suppression add cancelled.");
434
+ return 0;
435
+ }
436
+ await writeConfig(rawConfig.configPath, result.config);
437
+ io.writeStdout(renderSuppressionMutation("suppress.add", rawConfig.configPath, result));
438
+ return 0;
439
+ }
440
+ const result = addSuppression(rawConfig.value, {
441
+ fingerprint: command.fingerprint,
442
+ reason: command.reason,
443
+ expiresAt: command.expiresAt
444
+ });
445
+ await writeConfig(rawConfig.configPath, result.config);
446
+ io.writeStdout(command.jsonOutput
447
+ ? renderSuppressionMutationJson("suppress.add", rawConfig.configPath, result)
448
+ : renderSuppressionMutation("suppress.add", rawConfig.configPath, result));
449
+ return 0;
450
+ }
451
+ if (command.interactive) {
452
+ const suppressions = listSuppressions(rawConfig.value, now);
453
+ if (suppressions.length === 0) {
454
+ io.writeStderr("No suppressions are available to remove.");
455
+ return 1;
456
+ }
457
+ io.writeStdout(renderInteractiveSuppressionCandidates(suppressions));
458
+ const selection = parseInteractiveSelection(await io.readStdin("Select suppression to remove: "), suppressions.length);
459
+ if (selection === null) {
460
+ io.writeStderr(`Selection must be a number from 1 to ${suppressions.length}.`);
461
+ return 2;
462
+ }
463
+ const result = removeSuppressionByIndex(rawConfig.value, suppressions[selection].index);
464
+ const confirmation = await io.readStdin("Type yes to confirm: ");
465
+ if (confirmation.trim().toLowerCase() !== "yes") {
466
+ io.writeStdout("Suppression remove cancelled.");
467
+ return 0;
468
+ }
469
+ await writeConfig(rawConfig.configPath, result.config);
470
+ io.writeStdout(renderSuppressionMutation("suppress.remove", rawConfig.configPath, result));
471
+ return 0;
472
+ }
473
+ const result = command.selector.type === "index"
474
+ ? removeSuppressionByIndex(rawConfig.value, command.selector.index)
475
+ : removeSuppressionByFingerprint(rawConfig.value, command.selector.fingerprint);
476
+ await writeConfig(rawConfig.configPath, result.config);
477
+ io.writeStdout(command.jsonOutput
478
+ ? renderSuppressionMutationJson("suppress.remove", rawConfig.configPath, result)
479
+ : renderSuppressionMutation("suppress.remove", rawConfig.configPath, result));
480
+ return 0;
481
+ }
482
+ catch (error) {
483
+ io.writeStderr(error instanceof Error ? error.message : "Unknown suppression command error.");
484
+ return command.action === "add" &&
485
+ command.interactive &&
486
+ error instanceof SuppressionManagementError &&
487
+ error.code === "suppression_invalid_record"
488
+ ? 2
489
+ : 1;
490
+ }
491
+ }
97
492
  function buildGenericMcpDoctorCommandArgs(commandTarget, flags) {
98
493
  if (!commandTarget || commandTarget.startsWith("--")) {
99
494
  return "Missing target path. Usage: codex-plugin-doctor mcp <path> [--json] [--output <path>]";
@@ -1586,6 +1981,21 @@ export async function runCli(args, io = defaultIo, options = {}) {
1586
1981
  io.writeStdout(report);
1587
1982
  return matrixExitCode(matrix);
1588
1983
  }
1984
+ if (command === "suppress") {
1985
+ const parsedSuppressCommand = parseSuppressCommand([maybePath ?? "", ...remainingArgs]);
1986
+ if ("message" in parsedSuppressCommand) {
1987
+ io.writeStderr(parsedSuppressCommand.message);
1988
+ if (parsedSuppressCommand.showUsage) {
1989
+ printSuppressUsage(io);
1990
+ }
1991
+ return 2;
1992
+ }
1993
+ return executeSuppressCommand(parsedSuppressCommand, io, {
1994
+ runCheckImpl: options.runCheckImpl,
1995
+ writeRawDoctorConfigImpl: options.writeRawDoctorConfigImpl,
1996
+ now: options.now
1997
+ });
1998
+ }
1589
1999
  if (command !== "check") {
1590
2000
  printUsage(io);
1591
2001
  return 2;
package/package.json CHANGED
@@ -1,64 +1,64 @@
1
- {
2
- "name": "codex-plugin-doctor",
3
- "version": "1.26.0",
4
- "description": "CLI-first validator for Codex plugins, skills, and MCP package surfaces with runtime MCP protocol validation.",
5
- "type": "module",
6
- "main": "./dist/index.js",
7
- "types": "./dist/index.d.ts",
8
- "bin": {
9
- "codex-plugin-doctor": "dist/cli.js"
10
- },
11
- "exports": {
12
- ".": {
13
- "types": "./dist/index.d.ts",
14
- "import": "./dist/index.js"
15
- },
16
- "./package.json": "./package.json"
17
- },
18
- "files": [
19
- "dist",
20
- "examples"
21
- ],
22
- "scripts": {
23
- "clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
24
- "build": "npm run clean && tsc -p tsconfig.json",
25
- "dev": "tsx src/cli.ts",
26
- "generate-validation-artifacts": "node scripts/generate-validation-artifacts.mjs",
27
- "prepare-rc": "tsx scripts/prepare-release-candidate.ts",
28
- "prepare-release": "npm test && npm run build && npm pack --dry-run",
29
- "release-check": "node scripts/release-check.mjs",
30
- "verify-release-sync": "node scripts/verify-release-sync.mjs",
31
- "prepublishOnly": "npm test && npm run build",
32
- "test": "vitest run",
33
- "test:watch": "vitest"
34
- },
35
- "packageManager": "npm@11.11.1",
36
- "engines": {
37
- "node": ">=22"
38
- },
39
- "keywords": [
40
- "codex",
41
- "mcp",
42
- "plugin",
43
- "skills",
44
- "validation"
45
- ],
46
- "license": "MIT",
47
- "repository": {
48
- "type": "git",
49
- "url": "git+https://github.com/Esquetta/CodexPluginDoctor.git"
50
- },
51
- "bugs": {
52
- "url": "https://github.com/Esquetta/CodexPluginDoctor/issues"
53
- },
54
- "homepage": "https://github.com/Esquetta/CodexPluginDoctor#readme",
55
- "publishConfig": {
56
- "access": "public"
57
- },
58
- "devDependencies": {
59
- "@types/node": "^24.2.1",
60
- "tsx": "^4.20.4",
61
- "typescript": "^5.9.2",
62
- "vitest": "^3.2.4"
63
- }
64
- }
1
+ {
2
+ "name": "codex-plugin-doctor",
3
+ "version": "1.28.0",
4
+ "description": "CLI-first validator for Codex plugins, skills, and MCP package surfaces with runtime MCP protocol validation.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "bin": {
9
+ "codex-plugin-doctor": "dist/cli.js"
10
+ },
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js"
15
+ },
16
+ "./package.json": "./package.json"
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "examples"
21
+ ],
22
+ "scripts": {
23
+ "clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
24
+ "build": "npm run clean && tsc -p tsconfig.json",
25
+ "dev": "tsx src/cli.ts",
26
+ "generate-validation-artifacts": "node scripts/generate-validation-artifacts.mjs",
27
+ "prepare-rc": "tsx scripts/prepare-release-candidate.ts",
28
+ "prepare-release": "npm test && npm run build && npm pack --dry-run",
29
+ "release-check": "node scripts/release-check.mjs",
30
+ "verify-release-sync": "node scripts/verify-release-sync.mjs",
31
+ "prepublishOnly": "npm test && npm run build",
32
+ "test": "vitest run",
33
+ "test:watch": "vitest"
34
+ },
35
+ "packageManager": "npm@11.11.1",
36
+ "engines": {
37
+ "node": ">=22"
38
+ },
39
+ "keywords": [
40
+ "codex",
41
+ "mcp",
42
+ "plugin",
43
+ "skills",
44
+ "validation"
45
+ ],
46
+ "license": "MIT",
47
+ "repository": {
48
+ "type": "git",
49
+ "url": "git+https://github.com/Esquetta/CodexPluginDoctor.git"
50
+ },
51
+ "bugs": {
52
+ "url": "https://github.com/Esquetta/CodexPluginDoctor/issues"
53
+ },
54
+ "homepage": "https://github.com/Esquetta/CodexPluginDoctor#readme",
55
+ "publishConfig": {
56
+ "access": "public"
57
+ },
58
+ "devDependencies": {
59
+ "@types/node": "^24.2.1",
60
+ "tsx": "^4.20.4",
61
+ "typescript": "^5.9.2",
62
+ "vitest": "^3.2.4"
63
+ }
64
+ }