codex-plugin-doctor 1.64.0 → 1.66.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/LICENSE +21 -21
- package/README.md +11 -4
- package/dist/core/mcp-tool-catalog.d.ts +10 -0
- package/dist/core/mcp-tool-catalog.js +31 -11
- package/dist/core/mcp-tool-diff.d.ts +6 -0
- package/dist/core/mcp-tool-diff.js +25 -7
- package/dist/core/mcp-tool-file.d.ts +2 -0
- package/dist/core/mcp-tool-file.js +4 -4
- package/dist/core/mcp-tool-impact.d.ts +13 -0
- package/dist/core/mcp-tool-impact.js +230 -0
- package/dist/core/mcp-tool-response-file.d.ts +10 -0
- package/dist/core/mcp-tool-response-file.js +52 -0
- package/dist/core/output-contract.js +11 -6
- package/dist/reporting/render-mcp-tool-diff-report.js +6 -2
- package/dist/run-cli.d.ts +2 -1
- package/dist/run-cli.js +76 -15
- package/examples/README.md +94 -94
- package/examples/codex-doctor-generic-mcp/.mcp.json +10 -10
- package/examples/codex-doctor-generic-mcp/server.js +1 -1
- package/examples/codex-doctor-risky/.codex-plugin/plugin.json +7 -7
- package/examples/codex-doctor-risky/.mcp.json +12 -12
- package/examples/codex-doctor-risky/mock-server.js +1 -1
- package/examples/codex-doctor-runtime/.codex-plugin/plugin.json +8 -8
- package/examples/codex-doctor-runtime/.mcp.json +9 -9
- package/examples/codex-doctor-runtime/mock-server.js +237 -237
- package/examples/codex-doctor-runtime/skills/context-check/SKILL.md +7 -7
- package/examples/codex-doctor-starter/.codex-plugin/plugin.json +7 -7
- package/examples/codex-doctor-starter/skills/repo-scan/SKILL.md +7 -7
- package/package.json +1 -1
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { chmod, lstat, rename, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { isBlockedOfflinePath, MAX_TOOL_FILE_BYTES } from "./mcp-tool-file.js";
|
|
5
|
+
// Serializes an enumerated catalog as one complete tools/list response that doctor tools-file and tools-diff accept.
|
|
6
|
+
export function serializeMcpToolsListResponse(tools, cache) {
|
|
7
|
+
return `${JSON.stringify({
|
|
8
|
+
jsonrpc: "2.0",
|
|
9
|
+
id: "tools-list",
|
|
10
|
+
result: { resultType: "complete", tools, ttlMs: cache.ttlMs, cacheScope: cache.cacheScope }
|
|
11
|
+
}, null, 2)}\n`;
|
|
12
|
+
}
|
|
13
|
+
export async function saveMcpToolsListResponse(filePath, tools, cache) {
|
|
14
|
+
if (isBlockedOfflinePath(filePath))
|
|
15
|
+
return { kind: "path-blocked" };
|
|
16
|
+
let content;
|
|
17
|
+
let bytes;
|
|
18
|
+
try {
|
|
19
|
+
content = serializeMcpToolsListResponse(tools, cache);
|
|
20
|
+
bytes = Buffer.byteLength(content);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return { kind: "write-failed" };
|
|
24
|
+
}
|
|
25
|
+
if (bytes > MAX_TOOL_FILE_BYTES)
|
|
26
|
+
return { kind: "too-large" };
|
|
27
|
+
let existingMode = null;
|
|
28
|
+
try {
|
|
29
|
+
const existing = await lstat(filePath);
|
|
30
|
+
if (existing.isSymbolicLink() || !existing.isFile())
|
|
31
|
+
return { kind: "not-regular" };
|
|
32
|
+
existingMode = existing.mode & 0o777;
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
if (error.code !== "ENOENT")
|
|
36
|
+
return { kind: "write-failed" };
|
|
37
|
+
}
|
|
38
|
+
// Write beside the target and rename so a failed write never leaves a partial response file.
|
|
39
|
+
const tempPath = path.join(path.dirname(filePath), `.${path.basename(filePath)}.${process.pid}.${randomUUID()}.tmp`);
|
|
40
|
+
try {
|
|
41
|
+
// Create with the replaced baseline's permission bits, which may protect private tool definitions.
|
|
42
|
+
await writeFile(tempPath, content, { encoding: "utf8", flag: "wx", mode: existingMode ?? 0o666 });
|
|
43
|
+
if (existingMode !== null)
|
|
44
|
+
await chmod(tempPath, existingMode);
|
|
45
|
+
await rename(tempPath, filePath);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
await rm(tempPath, { force: true }).catch(() => undefined);
|
|
49
|
+
return { kind: "write-failed" };
|
|
50
|
+
}
|
|
51
|
+
return { kind: "saved", bytes, tools: tools.length };
|
|
52
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { packageVersion } from "../version.js";
|
|
3
|
+
import { TOOL_CHANGE_REASONS } from "./mcp-tool-impact.js";
|
|
3
4
|
import { ruleCatalog } from "../rules/rule-catalog.js";
|
|
4
5
|
const runtimeCapabilityStatusSchema = {
|
|
5
6
|
type: "string",
|
|
@@ -785,33 +786,37 @@ publicSchemaDefinitions.push({
|
|
|
785
786
|
before: embeddedToolFileReportSchema,
|
|
786
787
|
after: embeddedToolFileReportSchema,
|
|
787
788
|
comparison: {
|
|
788
|
-
type: "object", required: ["complete", "reason", "added", "removed", "changed", "unchanged"],
|
|
789
|
+
type: "object", required: ["complete", "reason", "added", "removed", "changed", "unchanged", "breaking", "unclassified"],
|
|
789
790
|
properties: {
|
|
790
791
|
complete: { type: "boolean" },
|
|
791
792
|
reason: { enum: [null, "input-blocked", "input-incomplete", "invalid-definitions", "ambiguous-names"] },
|
|
792
793
|
added: { type: ["integer", "null"], minimum: 0, maximum: 500 },
|
|
793
794
|
removed: { type: ["integer", "null"], minimum: 0, maximum: 500 },
|
|
794
795
|
changed: { type: ["integer", "null"], minimum: 0, maximum: 500 },
|
|
795
|
-
unchanged: { type: ["integer", "null"], minimum: 0, maximum: 500 }
|
|
796
|
+
unchanged: { type: ["integer", "null"], minimum: 0, maximum: 500 },
|
|
797
|
+
breaking: { type: ["integer", "null"], minimum: 0, maximum: 1000 },
|
|
798
|
+
unclassified: { type: ["integer", "null"], minimum: 0, maximum: 1000 }
|
|
796
799
|
}, additionalProperties: false
|
|
797
800
|
},
|
|
798
801
|
coverage: {
|
|
799
|
-
type: "object", required: ["comparison", "schema", "compatibility", "serverCatalog", "toolExecution"],
|
|
802
|
+
type: "object", required: ["comparison", "schema", "compatibility", "serverCatalog", "toolExecution", "impactClassification"],
|
|
800
803
|
properties: {
|
|
801
804
|
comparison: { const: "structural-only" }, schema: { const: "root-shape-only" },
|
|
802
805
|
compatibility: { const: "not-tested" }, serverCatalog: { const: "not-tested" },
|
|
803
|
-
toolExecution: { const: "not-tested" }
|
|
806
|
+
toolExecution: { const: "not-tested" }, impactClassification: { const: "heuristic" }
|
|
804
807
|
}, additionalProperties: false
|
|
805
808
|
},
|
|
806
809
|
changes: {
|
|
807
810
|
type: "array", maxItems: 1000,
|
|
808
811
|
items: {
|
|
809
|
-
type: "object", required: ["kind", "beforeToolIndex", "afterToolIndex", "fields"],
|
|
812
|
+
type: "object", required: ["kind", "beforeToolIndex", "afterToolIndex", "fields", "impact", "reasons"],
|
|
810
813
|
properties: {
|
|
811
814
|
kind: { enum: ["added", "removed", "changed"] },
|
|
812
815
|
beforeToolIndex: { type: ["integer", "null"], minimum: 1, maximum: 500 },
|
|
813
816
|
afterToolIndex: { type: ["integer", "null"], minimum: 1, maximum: 500 },
|
|
814
|
-
fields: { type: "array", maxItems: 6, uniqueItems: true, items: { enum: ["inputSchema", "outputSchema", "description", "title", "annotations", "other"] } }
|
|
817
|
+
fields: { type: "array", maxItems: 6, uniqueItems: true, items: { enum: ["inputSchema", "outputSchema", "description", "title", "annotations", "other"] } },
|
|
818
|
+
impact: { enum: ["breaking", "compatible", "unclassified"] },
|
|
819
|
+
reasons: { type: "array", maxItems: TOOL_CHANGE_REASONS.length, uniqueItems: true, items: { enum: [...TOOL_CHANGE_REASONS] } }
|
|
815
820
|
}, additionalProperties: false
|
|
816
821
|
}
|
|
817
822
|
}
|
|
@@ -11,12 +11,15 @@ export function renderMcpToolDiffReport(report) {
|
|
|
11
11
|
`Removed: ${comparison.removed ?? "not compared"}`,
|
|
12
12
|
`Changed: ${comparison.changed ?? "not compared"}`,
|
|
13
13
|
`Unchanged: ${comparison.unchanged ?? "not compared"}`,
|
|
14
|
+
`Breaking: ${comparison.breaking ?? "not compared"}`,
|
|
15
|
+
`Unclassified: ${comparison.unclassified ?? "not compared"}`,
|
|
14
16
|
`Comparison coverage: ${report.coverage.comparison}`,
|
|
15
17
|
`Schema coverage: ${report.coverage.schema}`,
|
|
16
18
|
`Compatibility: ${report.coverage.compatibility}`,
|
|
17
19
|
`Server catalog: ${report.coverage.serverCatalog}`,
|
|
18
20
|
`Tool execution: ${report.coverage.toolExecution}`,
|
|
19
|
-
|
|
21
|
+
`Impact classification: ${report.coverage.impactClassification}`,
|
|
22
|
+
"Only the supplied responses are compared. Impact labels are root-level schema heuristics: a compatible label or an unchanged result does not establish compatibility.",
|
|
20
23
|
...["before", "after"].flatMap((side) => {
|
|
21
24
|
const source = report[side];
|
|
22
25
|
const label = side === "before" ? "Before" : "After";
|
|
@@ -35,7 +38,8 @@ export function renderMcpToolDiffReport(report) {
|
|
|
35
38
|
...(change.beforeToolIndex === null ? [] : [`before tool ${change.beforeToolIndex}`]),
|
|
36
39
|
...(change.afterToolIndex === null ? [] : [`after tool ${change.afterToolIndex}`])
|
|
37
40
|
];
|
|
38
|
-
|
|
41
|
+
const impact = `${change.impact}${change.reasons.length ? ` (${change.reasons.join(", ")})` : ""}`;
|
|
42
|
+
return `${change.kind.toUpperCase()} [${locations.join(" -> ")}]${change.fields.length ? `: ${change.fields.join(", ")}` : ""}\n Impact: ${impact}`;
|
|
39
43
|
})
|
|
40
44
|
].join("\n");
|
|
41
45
|
}
|
package/dist/run-cli.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { discoverMcpServer } from "./core/mcp-discovery.js";
|
|
2
|
-
import { inspectMcpToolCatalog } from "./core/mcp-tool-catalog.js";
|
|
2
|
+
import { captureMcpToolCatalog, inspectMcpToolCatalog } from "./core/mcp-tool-catalog.js";
|
|
3
3
|
import { inspectMcpToolFile } from "./core/mcp-tool-file.js";
|
|
4
4
|
import { compareMcpToolFiles } from "./core/mcp-tool-diff.js";
|
|
5
5
|
import { buildCorpusQualityMetricsDiffReport, buildCorpusQualityMetricsReport } from "./core/corpus-quality-metrics.js";
|
|
@@ -20,6 +20,7 @@ export interface RunCliOptions {
|
|
|
20
20
|
compareMcpToolFilesImpl?: typeof compareMcpToolFiles;
|
|
21
21
|
inspectMcpToolFileImpl?: typeof inspectMcpToolFile;
|
|
22
22
|
inspectMcpToolCatalogImpl?: typeof inspectMcpToolCatalog;
|
|
23
|
+
captureMcpToolCatalogImpl?: typeof captureMcpToolCatalog;
|
|
23
24
|
discoverMcpServerImpl?: typeof discoverMcpServer;
|
|
24
25
|
terminalContext?: CliTerminalContext;
|
|
25
26
|
runCheckImpl?: typeof runCheck;
|
package/dist/run-cli.js
CHANGED
|
@@ -22,8 +22,9 @@ import { buildDoctorOutputContract, renderDoctorOutputContract, renderDoctorOutp
|
|
|
22
22
|
import { buildSubmissionArchivePreflight, submissionArchiveExitCode } from "./core/submission-archive-preflight.js";
|
|
23
23
|
import { buildSubmissionPreflight } from "./core/submission-preflight.js";
|
|
24
24
|
import { discoverMcpServer } from "./core/mcp-discovery.js";
|
|
25
|
-
import { inspectMcpToolCatalog } from "./core/mcp-tool-catalog.js";
|
|
26
|
-
import { inspectMcpToolFile } from "./core/mcp-tool-file.js";
|
|
25
|
+
import { captureMcpToolCatalog, inspectMcpToolCatalog } from "./core/mcp-tool-catalog.js";
|
|
26
|
+
import { inspectMcpToolFile, isBlockedOfflinePath } from "./core/mcp-tool-file.js";
|
|
27
|
+
import { saveMcpToolsListResponse } from "./core/mcp-tool-response-file.js";
|
|
27
28
|
import { compareMcpToolFiles } from "./core/mcp-tool-diff.js";
|
|
28
29
|
import { renderMcpToolDiffReport } from "./reporting/render-mcp-tool-diff-report.js";
|
|
29
30
|
import { renderMcpToolFileReport } from "./reporting/render-mcp-tool-file-report.js";
|
|
@@ -107,9 +108,9 @@ function writeExactStdout(io, message) {
|
|
|
107
108
|
}
|
|
108
109
|
class CliUsageError extends Error {
|
|
109
110
|
}
|
|
110
|
-
const toolsDiffUsage = "Usage: codex-plugin-doctor doctor tools-diff --before <path> --after <path> [--json]";
|
|
111
|
+
const toolsDiffUsage = "Usage: codex-plugin-doctor doctor tools-diff --before <path> --after <path> [--json] [--fail-on any|breaking]";
|
|
111
112
|
const toolsFileUsage = "Usage: codex-plugin-doctor doctor tools-file <path> [--json]";
|
|
112
|
-
const toolsUsage = "Usage: codex-plugin-doctor doctor tools <url> --allow-network [--allow-local-network] [--json]";
|
|
113
|
+
const toolsUsage = "Usage: codex-plugin-doctor doctor tools <url> --allow-network [--allow-local-network] [--json] [--save-response <path>]";
|
|
113
114
|
const discoveryUsage = "Usage: codex-plugin-doctor doctor discover <url> --allow-network [--allow-local-network] [--json]";
|
|
114
115
|
function parseRuntimeSandbox(flags, options = { requireRuntime: true }) {
|
|
115
116
|
const index = flags.indexOf("--sandbox");
|
|
@@ -1209,18 +1210,28 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
1209
1210
|
if (command === "doctor") {
|
|
1210
1211
|
if (maybePath === "tools-diff") {
|
|
1211
1212
|
if (remainingArgs.length === 1 && remainingArgs[0] === "--help") {
|
|
1212
|
-
io.writeStdout(`${toolsDiffUsage}\nCompares two saved MCP tools/list responses offline.
|
|
1213
|
+
io.writeStdout(`${toolsDiffUsage}\nCompares two saved MCP tools/list responses offline. Changes are classified heuristically as breaking, compatible, or unclassified; compatibility is not tested.\n--fail-on breaking exits 1 only for breaking or unclassified changes (default: any change or input warning).`);
|
|
1213
1214
|
return 0;
|
|
1214
1215
|
}
|
|
1215
1216
|
let beforePath;
|
|
1216
1217
|
let afterPath;
|
|
1217
1218
|
let json = false;
|
|
1219
|
+
let failOn;
|
|
1218
1220
|
let invalid = false;
|
|
1219
1221
|
for (let index = 0; index < remainingArgs.length; index += 1) {
|
|
1220
1222
|
const flag = remainingArgs[index];
|
|
1221
1223
|
if (flag === "--json" && !json) {
|
|
1222
1224
|
json = true;
|
|
1223
1225
|
}
|
|
1226
|
+
else if (flag === "--fail-on" && failOn === undefined) {
|
|
1227
|
+
const value = remainingArgs[index + 1];
|
|
1228
|
+
if (value !== "any" && value !== "breaking") {
|
|
1229
|
+
invalid = true;
|
|
1230
|
+
break;
|
|
1231
|
+
}
|
|
1232
|
+
failOn = value;
|
|
1233
|
+
index += 1;
|
|
1234
|
+
}
|
|
1224
1235
|
else if ((flag === "--before" && beforePath === undefined) || (flag === "--after" && afterPath === undefined)) {
|
|
1225
1236
|
const value = remainingArgs[index + 1];
|
|
1226
1237
|
if (!value || value.startsWith("-")) {
|
|
@@ -1244,7 +1255,12 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
1244
1255
|
}
|
|
1245
1256
|
const report = await (options.compareMcpToolFilesImpl ?? compareMcpToolFiles)(beforePath, afterPath);
|
|
1246
1257
|
io.writeStdout(json ? JSON.stringify(report, null, 2) : renderMcpToolDiffReport(report));
|
|
1247
|
-
|
|
1258
|
+
if (report.status !== "pass" && report.status !== "warn")
|
|
1259
|
+
return 2;
|
|
1260
|
+
if (failOn === "breaking") {
|
|
1261
|
+
return (report.comparison.breaking ?? 0) + (report.comparison.unclassified ?? 0) > 0 ? 1 : 0;
|
|
1262
|
+
}
|
|
1263
|
+
return report.status === "pass" ? 0 : 1;
|
|
1248
1264
|
}
|
|
1249
1265
|
if (maybePath === "tools-file") {
|
|
1250
1266
|
if (remainingArgs.length === 1 && remainingArgs[0] === "--help") {
|
|
@@ -1265,7 +1281,7 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
1265
1281
|
}
|
|
1266
1282
|
if (maybePath === "tools") {
|
|
1267
1283
|
if (remainingArgs.length === 1 && remainingArgs[0] === "--help") {
|
|
1268
|
-
io.writeStdout(`${toolsUsage}\nHTTP tool catalog structure only; tools are not executed.`);
|
|
1284
|
+
io.writeStdout(`${toolsUsage}\nHTTP tool catalog structure only; tools are not executed.\n--save-response writes a complete enumeration as one tools/list response for doctor tools-file and tools-diff; nothing is written when enumeration is incomplete.`);
|
|
1269
1285
|
return 0;
|
|
1270
1286
|
}
|
|
1271
1287
|
const [url, ...flags] = remainingArgs;
|
|
@@ -1274,23 +1290,68 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
1274
1290
|
return 2;
|
|
1275
1291
|
}
|
|
1276
1292
|
const allowedFlags = new Set(["--allow-network", "--allow-local-network", "--json"]);
|
|
1277
|
-
|
|
1293
|
+
const switches = [];
|
|
1294
|
+
let savePath;
|
|
1295
|
+
let invalid = false;
|
|
1296
|
+
for (let index = 0; index < flags.length; index += 1) {
|
|
1297
|
+
const flag = flags[index];
|
|
1298
|
+
if (flag === "--save-response" && savePath === undefined) {
|
|
1299
|
+
const value = flags[index + 1];
|
|
1300
|
+
if (!value || value.startsWith("-")) {
|
|
1301
|
+
invalid = true;
|
|
1302
|
+
break;
|
|
1303
|
+
}
|
|
1304
|
+
savePath = value;
|
|
1305
|
+
index += 1;
|
|
1306
|
+
}
|
|
1307
|
+
else if (allowedFlags.has(flag) && !switches.includes(flag)) {
|
|
1308
|
+
switches.push(flag);
|
|
1309
|
+
}
|
|
1310
|
+
else {
|
|
1311
|
+
invalid = true;
|
|
1312
|
+
break;
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
if (invalid) {
|
|
1278
1316
|
io.writeStderr(`Invalid or duplicate tool catalog arguments. ${toolsUsage}`);
|
|
1279
1317
|
return 2;
|
|
1280
1318
|
}
|
|
1281
|
-
if (!
|
|
1319
|
+
if (!switches.includes("--allow-network")) {
|
|
1282
1320
|
io.writeStderr("doctor tools requires explicit --allow-network consent.");
|
|
1283
1321
|
return 2;
|
|
1284
1322
|
}
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
}
|
|
1289
|
-
|
|
1323
|
+
if (savePath !== undefined && isBlockedOfflinePath(savePath)) {
|
|
1324
|
+
io.writeStderr("doctor tools --save-response requires a local file path.");
|
|
1325
|
+
return 2;
|
|
1326
|
+
}
|
|
1327
|
+
const catalogOptions = { allowNetwork: true, allowLocalNetwork: switches.includes("--allow-local-network") };
|
|
1328
|
+
const snapshot = savePath === undefined
|
|
1329
|
+
? { report: await (options.inspectMcpToolCatalogImpl ?? inspectMcpToolCatalog)(url, catalogOptions), tools: null, cache: null }
|
|
1330
|
+
: await (options.captureMcpToolCatalogImpl ?? captureMcpToolCatalog)(url, catalogOptions);
|
|
1331
|
+
const report = snapshot.report;
|
|
1332
|
+
io.writeStdout(switches.includes("--json")
|
|
1290
1333
|
? JSON.stringify(report, null, 2)
|
|
1291
1334
|
: renderMcpToolCatalogReport(report));
|
|
1292
|
-
|
|
1335
|
+
const exitCode = report.status === "pass" ? 0
|
|
1293
1336
|
: report.status === "incomplete" || report.status === "blocked" ? 2 : 1;
|
|
1337
|
+
if (savePath === undefined)
|
|
1338
|
+
return exitCode;
|
|
1339
|
+
if (snapshot.tools === null || snapshot.cache === null) {
|
|
1340
|
+
io.writeStderr("Tool catalog response was not saved because enumeration did not complete.");
|
|
1341
|
+
return 2;
|
|
1342
|
+
}
|
|
1343
|
+
const saved = await saveMcpToolsListResponse(savePath, snapshot.tools, snapshot.cache);
|
|
1344
|
+
if (saved.kind !== "saved") {
|
|
1345
|
+
io.writeStderr({
|
|
1346
|
+
"path-blocked": "Tool catalog response was not saved because the path is not a local file path.",
|
|
1347
|
+
"not-regular": "Tool catalog response was not saved because the existing target is not a regular file.",
|
|
1348
|
+
"too-large": "Tool catalog response was not saved because it exceeds the one MiB tools-file limit.",
|
|
1349
|
+
"write-failed": "Tool catalog response could not be written."
|
|
1350
|
+
}[saved.kind]);
|
|
1351
|
+
return 2;
|
|
1352
|
+
}
|
|
1353
|
+
io.writeStderr(`Saved a complete tools/list response with ${saved.tools} tools (${saved.bytes} bytes).`);
|
|
1354
|
+
return exitCode;
|
|
1294
1355
|
}
|
|
1295
1356
|
if (maybePath === "discover") {
|
|
1296
1357
|
if (remainingArgs.length === 1 && remainingArgs[0] === "--help") {
|
package/examples/README.md
CHANGED
|
@@ -1,94 +1,94 @@
|
|
|
1
|
-
# Examples
|
|
2
|
-
|
|
3
|
-
This folder contains manual example packs for local testing. Unlike `tests/fixtures`, these examples are meant for humans to run directly against the CLI.
|
|
4
|
-
|
|
5
|
-
## Example Packs
|
|
6
|
-
|
|
7
|
-
Use the smallest example that matches what you need to prove:
|
|
8
|
-
|
|
9
|
-
- `codex-doctor-starter` for static manifest and skill validation.
|
|
10
|
-
- `codex-doctor-runtime` for full MCP runtime probing and compatibility scoring.
|
|
11
|
-
- `codex-doctor-risky` for security finding output.
|
|
12
|
-
- `codex-doctor-generic-mcp` for MCP-only packages without a Codex plugin manifest.
|
|
13
|
-
|
|
14
|
-
### `codex-doctor-starter`
|
|
15
|
-
|
|
16
|
-
Minimal valid Codex plugin package with one skill and no runtime MCP server.
|
|
17
|
-
|
|
18
|
-
Expected result:
|
|
19
|
-
|
|
20
|
-
- static validation passes
|
|
21
|
-
- no runtime probing needed
|
|
22
|
-
|
|
23
|
-
Command:
|
|
24
|
-
|
|
25
|
-
```bash
|
|
26
|
-
codex-plugin-doctor check examples/codex-doctor-starter
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
### `codex-doctor-runtime`
|
|
30
|
-
|
|
31
|
-
Valid Codex plugin package with:
|
|
32
|
-
|
|
33
|
-
- skill metadata
|
|
34
|
-
- `.mcp.json`
|
|
35
|
-
- mock MCP stdio server
|
|
36
|
-
- `tools/list`
|
|
37
|
-
- `tools/call`
|
|
38
|
-
- `resources/list`
|
|
39
|
-
- `resources/read`
|
|
40
|
-
- `resources/templates/list`
|
|
41
|
-
- `prompts/list`
|
|
42
|
-
- `prompts/get`
|
|
43
|
-
|
|
44
|
-
Expected result:
|
|
45
|
-
|
|
46
|
-
- static validation passes
|
|
47
|
-
- runtime validation passes
|
|
48
|
-
- runtime scorecard shows all supported runtime capabilities as `pass`
|
|
49
|
-
|
|
50
|
-
Command:
|
|
51
|
-
|
|
52
|
-
```bash
|
|
53
|
-
codex-plugin-doctor check examples/codex-doctor-runtime --json --runtime --verbose-runtime
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
### `codex-doctor-risky`
|
|
57
|
-
|
|
58
|
-
Intentionally flawed package for showing failure output.
|
|
59
|
-
|
|
60
|
-
Expected result:
|
|
61
|
-
|
|
62
|
-
- security finding for hard-coded secret
|
|
63
|
-
|
|
64
|
-
Command:
|
|
65
|
-
|
|
66
|
-
```bash
|
|
67
|
-
codex-plugin-doctor check examples/codex-doctor-risky --ascii
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
### `codex-doctor-generic-mcp`
|
|
71
|
-
|
|
72
|
-
Generic MCP package with a `.mcp.json` file and no Codex plugin manifest.
|
|
73
|
-
|
|
74
|
-
Expected result:
|
|
75
|
-
|
|
76
|
-
- `codex-plugin-doctor mcp` passes generic MCP static health checks
|
|
77
|
-
- `codex-plugin-doctor check` still reports the missing Codex plugin manifest
|
|
78
|
-
|
|
79
|
-
Command:
|
|
80
|
-
|
|
81
|
-
```bash
|
|
82
|
-
codex-plugin-doctor mcp examples/codex-doctor-generic-mcp
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
## Suggested Local Flow
|
|
86
|
-
|
|
87
|
-
```bash
|
|
88
|
-
npm install
|
|
89
|
-
npm run build
|
|
90
|
-
npm link
|
|
91
|
-
codex-plugin-doctor check examples/codex-doctor-starter
|
|
92
|
-
codex-plugin-doctor check examples/codex-doctor-runtime --json --runtime --verbose-runtime
|
|
93
|
-
codex-plugin-doctor check examples/codex-doctor-risky --ascii
|
|
94
|
-
```
|
|
1
|
+
# Examples
|
|
2
|
+
|
|
3
|
+
This folder contains manual example packs for local testing. Unlike `tests/fixtures`, these examples are meant for humans to run directly against the CLI.
|
|
4
|
+
|
|
5
|
+
## Example Packs
|
|
6
|
+
|
|
7
|
+
Use the smallest example that matches what you need to prove:
|
|
8
|
+
|
|
9
|
+
- `codex-doctor-starter` for static manifest and skill validation.
|
|
10
|
+
- `codex-doctor-runtime` for full MCP runtime probing and compatibility scoring.
|
|
11
|
+
- `codex-doctor-risky` for security finding output.
|
|
12
|
+
- `codex-doctor-generic-mcp` for MCP-only packages without a Codex plugin manifest.
|
|
13
|
+
|
|
14
|
+
### `codex-doctor-starter`
|
|
15
|
+
|
|
16
|
+
Minimal valid Codex plugin package with one skill and no runtime MCP server.
|
|
17
|
+
|
|
18
|
+
Expected result:
|
|
19
|
+
|
|
20
|
+
- static validation passes
|
|
21
|
+
- no runtime probing needed
|
|
22
|
+
|
|
23
|
+
Command:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
codex-plugin-doctor check examples/codex-doctor-starter
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### `codex-doctor-runtime`
|
|
30
|
+
|
|
31
|
+
Valid Codex plugin package with:
|
|
32
|
+
|
|
33
|
+
- skill metadata
|
|
34
|
+
- `.mcp.json`
|
|
35
|
+
- mock MCP stdio server
|
|
36
|
+
- `tools/list`
|
|
37
|
+
- `tools/call`
|
|
38
|
+
- `resources/list`
|
|
39
|
+
- `resources/read`
|
|
40
|
+
- `resources/templates/list`
|
|
41
|
+
- `prompts/list`
|
|
42
|
+
- `prompts/get`
|
|
43
|
+
|
|
44
|
+
Expected result:
|
|
45
|
+
|
|
46
|
+
- static validation passes
|
|
47
|
+
- runtime validation passes
|
|
48
|
+
- runtime scorecard shows all supported runtime capabilities as `pass`
|
|
49
|
+
|
|
50
|
+
Command:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
codex-plugin-doctor check examples/codex-doctor-runtime --json --runtime --verbose-runtime
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### `codex-doctor-risky`
|
|
57
|
+
|
|
58
|
+
Intentionally flawed package for showing failure output.
|
|
59
|
+
|
|
60
|
+
Expected result:
|
|
61
|
+
|
|
62
|
+
- security finding for hard-coded secret
|
|
63
|
+
|
|
64
|
+
Command:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
codex-plugin-doctor check examples/codex-doctor-risky --ascii
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### `codex-doctor-generic-mcp`
|
|
71
|
+
|
|
72
|
+
Generic MCP package with a `.mcp.json` file and no Codex plugin manifest.
|
|
73
|
+
|
|
74
|
+
Expected result:
|
|
75
|
+
|
|
76
|
+
- `codex-plugin-doctor mcp` passes generic MCP static health checks
|
|
77
|
+
- `codex-plugin-doctor check` still reports the missing Codex plugin manifest
|
|
78
|
+
|
|
79
|
+
Command:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
codex-plugin-doctor mcp examples/codex-doctor-generic-mcp
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Suggested Local Flow
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
npm install
|
|
89
|
+
npm run build
|
|
90
|
+
npm link
|
|
91
|
+
codex-plugin-doctor check examples/codex-doctor-starter
|
|
92
|
+
codex-plugin-doctor check examples/codex-doctor-runtime --json --runtime --verbose-runtime
|
|
93
|
+
codex-plugin-doctor check examples/codex-doctor-risky --ascii
|
|
94
|
+
```
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
{
|
|
2
|
-
"mcpServers": {
|
|
3
|
-
"genericRuntime": {
|
|
4
|
-
"command": "node",
|
|
5
|
-
"args": [
|
|
6
|
-
"server.js"
|
|
7
|
-
]
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"mcpServers": {
|
|
3
|
+
"genericRuntime": {
|
|
4
|
+
"command": "node",
|
|
5
|
+
"args": [
|
|
6
|
+
"server.js"
|
|
7
|
+
]
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
process.stdin.resume();
|
|
1
|
+
process.stdin.resume();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "codex-doctor-risky",
|
|
3
|
-
"version": "1.0.0",
|
|
4
|
-
"description": "Intentionally risky sample package for showing Codex Doctor failure output.",
|
|
5
|
-
"mcpServers": "./.mcp.json"
|
|
6
|
-
}
|
|
7
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "codex-doctor-risky",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Intentionally risky sample package for showing Codex Doctor failure output.",
|
|
5
|
+
"mcpServers": "./.mcp.json"
|
|
6
|
+
}
|
|
7
|
+
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
{
|
|
2
|
-
"mcpServers": {
|
|
3
|
-
"dangerServer": {
|
|
4
|
-
"command": "node",
|
|
5
|
-
"args": ["./mock-server.js"],
|
|
6
|
-
"env": {
|
|
7
|
-
"OPENAI_API_KEY": "sk-live-example-secret-value"
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
|
|
1
|
+
{
|
|
2
|
+
"mcpServers": {
|
|
3
|
+
"dangerServer": {
|
|
4
|
+
"command": "node",
|
|
5
|
+
"args": ["./mock-server.js"],
|
|
6
|
+
"env": {
|
|
7
|
+
"OPENAI_API_KEY": "sk-live-example-secret-value"
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
@@ -1 +1 @@
|
|
|
1
|
-
process.stdin.resume();
|
|
1
|
+
process.stdin.resume();
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "codex-doctor-runtime",
|
|
3
|
-
"version": "1.0.0",
|
|
4
|
-
"description": "Runtime-complete Codex Doctor sample plugin with MCP validation coverage.",
|
|
5
|
-
"skills": "./skills/",
|
|
6
|
-
"mcpServers": "./.mcp.json"
|
|
7
|
-
}
|
|
8
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "codex-doctor-runtime",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Runtime-complete Codex Doctor sample plugin with MCP validation coverage.",
|
|
5
|
+
"skills": "./skills/",
|
|
6
|
+
"mcpServers": "./.mcp.json"
|
|
7
|
+
}
|
|
8
|
+
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
{
|
|
2
|
-
"mcpServers": {
|
|
3
|
-
"doctorRuntime": {
|
|
4
|
-
"command": "node",
|
|
5
|
-
"args": ["./mock-server.js"]
|
|
6
|
-
}
|
|
7
|
-
}
|
|
8
|
-
}
|
|
9
|
-
|
|
1
|
+
{
|
|
2
|
+
"mcpServers": {
|
|
3
|
+
"doctorRuntime": {
|
|
4
|
+
"command": "node",
|
|
5
|
+
"args": ["./mock-server.js"]
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|