codex-plugin-doctor 1.76.0 → 1.77.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 +18 -2
- package/dist/core/mcp-prompt-catalog.d.ts +36 -0
- package/dist/core/mcp-prompt-catalog.js +53 -0
- package/dist/core/mcp-prompt-definition.d.ts +24 -0
- package/dist/core/mcp-prompt-definition.js +107 -0
- package/dist/core/mcp-prompt-diff.d.ts +43 -0
- package/dist/core/mcp-prompt-diff.js +132 -0
- package/dist/core/mcp-prompt-file.d.ts +31 -0
- package/dist/core/mcp-prompt-file.js +118 -0
- package/dist/core/mcp-prompt-impact.d.ts +21 -0
- package/dist/core/mcp-prompt-impact.js +93 -0
- package/dist/core/mcp-stdio-tool-catalog.d.ts +9 -3
- package/dist/core/mcp-stdio-tool-catalog.js +74 -55
- package/dist/core/mcp-tool-catalog.d.ts +44 -5
- package/dist/core/mcp-tool-catalog.js +76 -31
- package/dist/core/mcp-tool-diff.d.ts +1 -0
- package/dist/core/mcp-tool-diff.js +1 -1
- package/dist/core/mcp-tool-file.d.ts +20 -0
- package/dist/core/mcp-tool-file.js +1 -1
- package/dist/core/mcp-tool-response-file.d.ts +6 -4
- package/dist/core/mcp-tool-response-file.js +8 -8
- package/dist/core/output-contract.js +177 -0
- package/dist/reporting/quote-server-text.d.ts +1 -0
- package/dist/reporting/quote-server-text.js +13 -0
- package/dist/reporting/render-mcp-prompt-catalog-report.d.ts +2 -0
- package/dist/reporting/render-mcp-prompt-catalog-report.js +31 -0
- package/dist/reporting/render-mcp-prompt-diff-markdown.d.ts +2 -0
- package/dist/reporting/render-mcp-prompt-diff-markdown.js +39 -0
- package/dist/reporting/render-mcp-prompt-diff-report.d.ts +2 -0
- package/dist/reporting/render-mcp-prompt-diff-report.js +48 -0
- package/dist/reporting/render-mcp-prompt-file-report.d.ts +5 -0
- package/dist/reporting/render-mcp-prompt-file-report.js +33 -0
- package/dist/reporting/render-mcp-tool-diff-markdown.js +3 -16
- package/dist/run-cli.d.ts +7 -0
- package/dist/run-cli.js +203 -140
- package/package.json +1 -1
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { describePromptLocation } from "./render-mcp-prompt-file-report.js";
|
|
2
|
+
import { quoteServerText } from "./quote-server-text.js";
|
|
3
|
+
export function renderMcpPromptDiffReport(report) {
|
|
4
|
+
const comparison = report.comparison;
|
|
5
|
+
return [
|
|
6
|
+
"Offline MCP Prompt Diff",
|
|
7
|
+
"=======================",
|
|
8
|
+
`Status: ${report.status}`,
|
|
9
|
+
`Scope: ${report.scope}`,
|
|
10
|
+
`Comparison complete: ${comparison.complete}`,
|
|
11
|
+
...(comparison.reason === null ? [] : [`Reason: ${comparison.reason}`]),
|
|
12
|
+
`Added: ${comparison.added ?? "not compared"}`,
|
|
13
|
+
`Removed: ${comparison.removed ?? "not compared"}`,
|
|
14
|
+
`Changed: ${comparison.changed ?? "not compared"}`,
|
|
15
|
+
`Unchanged: ${comparison.unchanged ?? "not compared"}`,
|
|
16
|
+
`Breaking: ${comparison.breaking ?? "not compared"}`,
|
|
17
|
+
`Unclassified: ${comparison.unclassified ?? "not compared"}`,
|
|
18
|
+
`Comparison coverage: ${report.coverage.comparison}`,
|
|
19
|
+
`Definition coverage: ${report.coverage.definitions}`,
|
|
20
|
+
`Compatibility: ${report.coverage.compatibility}`,
|
|
21
|
+
`Server catalog: ${report.coverage.serverCatalog}`,
|
|
22
|
+
`Prompt retrieval: ${report.coverage.promptRetrieval}`,
|
|
23
|
+
`Impact classification: ${report.coverage.impactClassification}`,
|
|
24
|
+
"Only the supplied responses are compared. Impact labels are argument heuristics: a compatible label or an unchanged result does not establish compatibility.",
|
|
25
|
+
...["before", "after"].flatMap((side) => {
|
|
26
|
+
const source = report[side];
|
|
27
|
+
const label = side === "before" ? "Before" : "After";
|
|
28
|
+
return [
|
|
29
|
+
`${label} input: ${source.status}; prompts checked: ${source.inspection.promptsChecked}; bytes read: ${source.source.bytesRead}`,
|
|
30
|
+
...(source.inspection.reason === null ? [] : [`${label} reason: ${source.inspection.reason}`]),
|
|
31
|
+
...source.findings.map((finding) => {
|
|
32
|
+
const locator = finding.location ? ` [${side} ${describePromptLocation(finding.location)}]` : "";
|
|
33
|
+
return `${label} ${finding.severity.toUpperCase()} ${finding.id}${locator}: ${finding.message}\n ${finding.suggestedFix}`;
|
|
34
|
+
})
|
|
35
|
+
];
|
|
36
|
+
}),
|
|
37
|
+
...report.changes.map((change) => {
|
|
38
|
+
const locations = [
|
|
39
|
+
...(change.beforePromptIndex === null ? [] : [`before prompt ${change.beforePromptIndex}`]),
|
|
40
|
+
...(change.afterPromptIndex === null ? [] : [`after prompt ${change.afterPromptIndex}`])
|
|
41
|
+
];
|
|
42
|
+
const impact = `${change.impact}${change.reasons.length ? ` (${change.reasons.join(", ")})` : ""}`;
|
|
43
|
+
const name = change.name === undefined ? "" : ` ${quoteServerText(change.name)}`;
|
|
44
|
+
const at = (change.locations ?? []).map((location) => `\n At ${location.argument === null ? "(prompt)" : `argument ${quoteServerText(location.argument)}`}: ${location.reason}`).join("");
|
|
45
|
+
return `${change.kind.toUpperCase()}${name} [${locations.join(" -> ")}]${change.fields.length ? `: ${change.fields.join(", ")}` : ""}\n Impact: ${impact}${at}`;
|
|
46
|
+
})
|
|
47
|
+
].join("\n");
|
|
48
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { PromptCatalogLocation } from "../core/mcp-prompt-definition.js";
|
|
2
|
+
import type { McpPromptFileReport } from "../core/mcp-prompt-file.js";
|
|
3
|
+
/** Describes a finding location by one-based positions only, such as "prompt 2, argument 1, related argument 3". */
|
|
4
|
+
export declare function describePromptLocation(location: PromptCatalogLocation): string;
|
|
5
|
+
export declare function renderMcpPromptFileReport(report: McpPromptFileReport): string;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** Describes a finding location by one-based positions only, such as "prompt 2, argument 1, related argument 3". */
|
|
2
|
+
export function describePromptLocation(location) {
|
|
3
|
+
return [
|
|
4
|
+
`prompt ${location.promptIndex}`,
|
|
5
|
+
...(location.argumentIndex === undefined ? [] : [`argument ${location.argumentIndex}`]),
|
|
6
|
+
...(location.relatedPromptIndex === undefined ? [] : [`related prompt ${location.relatedPromptIndex}`]),
|
|
7
|
+
...(location.relatedArgumentIndex === undefined ? [] : [`related argument ${location.relatedArgumentIndex}`])
|
|
8
|
+
].join(", ");
|
|
9
|
+
}
|
|
10
|
+
export function renderMcpPromptFileReport(report) {
|
|
11
|
+
return [
|
|
12
|
+
"Offline MCP Prompt Definitions",
|
|
13
|
+
"==============================",
|
|
14
|
+
`Status: ${report.status}`,
|
|
15
|
+
`Scope: ${report.scope}`,
|
|
16
|
+
`Source: ${report.source.kind} (${report.source.format})`,
|
|
17
|
+
`Bytes read: ${report.source.bytesRead}`,
|
|
18
|
+
`Inspection complete: ${report.inspection.complete}`,
|
|
19
|
+
`Prompts checked: ${report.inspection.promptsChecked}`,
|
|
20
|
+
`More pages advertised: ${report.inspection.hasNextCursor ?? "unknown"}`,
|
|
21
|
+
...(report.inspection.reason === null ? [] : [`Reason: ${report.inspection.reason}`]),
|
|
22
|
+
`Definition coverage: ${report.coverage.definitions}`,
|
|
23
|
+
`Discovery: ${report.coverage.discovery}`,
|
|
24
|
+
`Server catalog: ${report.coverage.serverCatalog}`,
|
|
25
|
+
`Prompt retrieval: ${report.coverage.promptRetrieval}`,
|
|
26
|
+
`Full conformance: ${report.coverage.fullConformance}`,
|
|
27
|
+
"Only the supplied file was inspected. Its provenance and server completeness are not established.",
|
|
28
|
+
...report.findings.map((finding) => {
|
|
29
|
+
const locator = finding.location ? ` [${describePromptLocation(finding.location)}]` : "";
|
|
30
|
+
return `${finding.severity.toUpperCase()} ${finding.id}${locator}: ${finding.message}\n ${finding.suggestedFix}`;
|
|
31
|
+
})
|
|
32
|
+
].join("\n");
|
|
33
|
+
}
|
|
@@ -1,23 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { markdownCodeSpan } from "./quote-server-text.js";
|
|
2
2
|
function count(value) {
|
|
3
3
|
return value === null || value === undefined ? "not compared" : String(value);
|
|
4
4
|
}
|
|
5
5
|
function toolIndex(value) {
|
|
6
6
|
return value === null ? "-" : String(value);
|
|
7
7
|
}
|
|
8
|
-
// Names and pointers come from the server, so they are quoted and put in a code span, where Markdown,
|
|
9
|
-
// HTML, mentions, and links stay inert; the fence outgrows any backtick run, and pipes are escaped for the table.
|
|
10
|
-
function code(text) {
|
|
11
|
-
const quoted = quoteServerText(text);
|
|
12
|
-
let run = 0;
|
|
13
|
-
let longest = 0;
|
|
14
|
-
for (const character of quoted) {
|
|
15
|
-
run = character === "`" ? run + 1 : 0;
|
|
16
|
-
longest = Math.max(longest, run);
|
|
17
|
-
}
|
|
18
|
-
const fence = "`".repeat(longest + 1);
|
|
19
|
-
return `${fence}${quoted.replace(/\|/gu, "\\|")}${fence}`;
|
|
20
|
-
}
|
|
21
8
|
export function renderMcpToolDiffMarkdown(report) {
|
|
22
9
|
const comparison = report.comparison;
|
|
23
10
|
const named = report.changes.some((change) => change.name !== undefined);
|
|
@@ -31,9 +18,9 @@ export function renderMcpToolDiffMarkdown(report) {
|
|
|
31
18
|
`| ${count(comparison.added)} | ${count(comparison.removed)} | ${count(comparison.changed)} | ${count(comparison.unchanged)} | ${count(comparison.breaking)} | ${count(comparison.unclassified)} |`
|
|
32
19
|
];
|
|
33
20
|
if (report.changes.length > 0) {
|
|
34
|
-
lines.push("", "### Changes", "", named ? "| Change | Tool | Before tool | After tool | Fields | Impact | Reasons |" : "| Change | Before tool | After tool | Fields | Impact | Reasons |", named ? "| --- | --- | --- | --- | --- | --- | --- |" : "| --- | --- | --- | --- | --- | --- |", ...report.changes.map((change) => `| ${change.kind}${named ? ` | ${change.name === undefined ? "-" :
|
|
21
|
+
lines.push("", "### Changes", "", named ? "| Change | Tool | Before tool | After tool | Fields | Impact | Reasons |" : "| Change | Before tool | After tool | Fields | Impact | Reasons |", named ? "| --- | --- | --- | --- | --- | --- | --- |" : "| --- | --- | --- | --- | --- | --- |", ...report.changes.map((change) => `| ${change.kind}${named ? ` | ${change.name === undefined ? "-" : markdownCodeSpan(change.name)}` : ""} | ${toolIndex(change.beforeToolIndex)} | ${toolIndex(change.afterToolIndex)} | ${change.fields.map((field) => `\`${field}\``).join(", ") || "-"} | **${change.impact}** | ${change.reasons.map((reason) => `\`${reason}\``).join(", ") || "-"} |`));
|
|
35
22
|
}
|
|
36
|
-
const located = report.changes.flatMap((change) => (change.locations ?? []).map((location) => `| ${change.name === undefined ? "-" :
|
|
23
|
+
const located = report.changes.flatMap((change) => (change.locations ?? []).map((location) => `| ${change.name === undefined ? "-" : markdownCodeSpan(change.name)} | \`${location.reason}\` | ${location.pointer === "" ? "(tool)" : markdownCodeSpan(location.pointer)} |`));
|
|
37
24
|
if (located.length > 0) {
|
|
38
25
|
lines.push("", "### Locations", "", "| Tool | Reason | Location |", "| --- | --- | --- |", ...located);
|
|
39
26
|
}
|
package/dist/run-cli.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { discoverMcpServer } from "./core/mcp-discovery.js";
|
|
2
2
|
import { captureMcpToolCatalog, inspectMcpToolCatalog } from "./core/mcp-tool-catalog.js";
|
|
3
3
|
import { captureStdioMcpToolCatalog } from "./core/mcp-stdio-tool-catalog.js";
|
|
4
|
+
import { captureMcpPromptCatalog, captureStdioMcpPromptCatalog } from "./core/mcp-prompt-catalog.js";
|
|
5
|
+
import { compareMcpPromptFiles } from "./core/mcp-prompt-diff.js";
|
|
6
|
+
import { inspectMcpPromptFile } from "./core/mcp-prompt-file.js";
|
|
4
7
|
import { inspectMcpToolFile } from "./core/mcp-tool-file.js";
|
|
5
8
|
import { compareMcpToolFiles } from "./core/mcp-tool-diff.js";
|
|
6
9
|
import { buildCorpusQualityMetricsDiffReport, buildCorpusQualityMetricsReport } from "./core/corpus-quality-metrics.js";
|
|
@@ -23,6 +26,10 @@ export interface RunCliOptions {
|
|
|
23
26
|
inspectMcpToolCatalogImpl?: typeof inspectMcpToolCatalog;
|
|
24
27
|
captureMcpToolCatalogImpl?: typeof captureMcpToolCatalog;
|
|
25
28
|
captureStdioMcpToolCatalogImpl?: typeof captureStdioMcpToolCatalog;
|
|
29
|
+
compareMcpPromptFilesImpl?: typeof compareMcpPromptFiles;
|
|
30
|
+
inspectMcpPromptFileImpl?: typeof inspectMcpPromptFile;
|
|
31
|
+
captureMcpPromptCatalogImpl?: typeof captureMcpPromptCatalog;
|
|
32
|
+
captureStdioMcpPromptCatalogImpl?: typeof captureStdioMcpPromptCatalog;
|
|
26
33
|
discoverMcpServerImpl?: typeof discoverMcpServer;
|
|
27
34
|
terminalContext?: CliTerminalContext;
|
|
28
35
|
runCheckImpl?: typeof runCheck;
|
package/dist/run-cli.js
CHANGED
|
@@ -25,13 +25,20 @@ import { isValidBearerToken } from "./core/bounded-http-client.js";
|
|
|
25
25
|
import { discoverMcpServer } from "./core/mcp-discovery.js";
|
|
26
26
|
import { captureMcpToolCatalog, inspectMcpToolCatalog } from "./core/mcp-tool-catalog.js";
|
|
27
27
|
import { captureStdioMcpToolCatalog, resolveMcpStdioServer } from "./core/mcp-stdio-tool-catalog.js";
|
|
28
|
+
import { captureMcpPromptCatalog, captureStdioMcpPromptCatalog } from "./core/mcp-prompt-catalog.js";
|
|
29
|
+
import { compareMcpPromptFiles } from "./core/mcp-prompt-diff.js";
|
|
30
|
+
import { inspectMcpPromptFile } from "./core/mcp-prompt-file.js";
|
|
28
31
|
import { inspectMcpToolFile, isBlockedOfflinePath } from "./core/mcp-tool-file.js";
|
|
29
|
-
import {
|
|
32
|
+
import { saveMcpListResponse } from "./core/mcp-tool-response-file.js";
|
|
30
33
|
import { compareMcpToolFiles } from "./core/mcp-tool-diff.js";
|
|
31
34
|
import { renderMcpToolDiffMarkdown } from "./reporting/render-mcp-tool-diff-markdown.js";
|
|
32
35
|
import { renderMcpToolDiffReport } from "./reporting/render-mcp-tool-diff-report.js";
|
|
33
36
|
import { renderMcpToolFileReport } from "./reporting/render-mcp-tool-file-report.js";
|
|
34
37
|
import { renderMcpToolCatalogReport } from "./reporting/render-mcp-tool-catalog-report.js";
|
|
38
|
+
import { renderMcpPromptCatalogReport } from "./reporting/render-mcp-prompt-catalog-report.js";
|
|
39
|
+
import { renderMcpPromptDiffMarkdown } from "./reporting/render-mcp-prompt-diff-markdown.js";
|
|
40
|
+
import { renderMcpPromptDiffReport } from "./reporting/render-mcp-prompt-diff-report.js";
|
|
41
|
+
import { renderMcpPromptFileReport } from "./reporting/render-mcp-prompt-file-report.js";
|
|
35
42
|
import { renderMcpDiscoveryReport } from "./reporting/render-mcp-discovery-report.js";
|
|
36
43
|
import { buildDoctorValidationCorpusReport, renderDoctorValidationCorpusJson, renderDoctorValidationCorpusReport } from "./core/validation-corpus.js";
|
|
37
44
|
import { buildExternalValidationCorpusReport, ExternalCorpusManifestError } from "./core/external-validation-corpus.js";
|
|
@@ -113,7 +120,12 @@ class CliUsageError extends Error {
|
|
|
113
120
|
}
|
|
114
121
|
const toolsDiffUsage = "Usage: codex-plugin-doctor doctor tools-diff --before <path> --after <path> [--json|--markdown] [--fail-on any|breaking] [--show-names]";
|
|
115
122
|
const toolsFileUsage = "Usage: codex-plugin-doctor doctor tools-file <path> [--json]";
|
|
123
|
+
const promptsDiffUsage = "Usage: codex-plugin-doctor doctor prompts-diff --before <path> --after <path> [--json|--markdown] [--fail-on any|breaking] [--show-names]";
|
|
124
|
+
const promptsFileUsage = "Usage: codex-plugin-doctor doctor prompts-file <path> [--json]";
|
|
116
125
|
const toolsUsage = "Usage: codex-plugin-doctor doctor tools <url> --allow-network [--allow-local-network] [--json] [--save-response <path>] [--bearer-token-env <NAME>]\n codex-plugin-doctor doctor tools <path> --server <name> --runtime [--sandbox docker [--env <NAME>]...] [--require-runtime-approval [--runtime-approval-digest <digest>]] [--json] [--save-response <path>]";
|
|
126
|
+
const promptsUsage = "Usage: codex-plugin-doctor doctor prompts <url> --allow-network [--allow-local-network] [--json] [--save-response <path>] [--bearer-token-env <NAME>]\n codex-plugin-doctor doctor prompts <path> --server <name> --runtime [--sandbox docker [--env <NAME>]...] [--require-runtime-approval [--runtime-approval-digest <digest>]] [--json] [--save-response <path>]";
|
|
127
|
+
const TOOL_CATALOG_COMMAND = { name: "tools", noun: "Tool", method: "tools/list", usage: toolsUsage };
|
|
128
|
+
const PROMPT_CATALOG_COMMAND = { name: "prompts", noun: "Prompt", method: "prompts/list", usage: promptsUsage };
|
|
117
129
|
const discoveryUsage = "Usage: codex-plugin-doctor doctor discover <url> --allow-network [--allow-local-network] [--json] [--bearer-token-env <NAME>]";
|
|
118
130
|
const bearerTokenHelp = "--bearer-token-env reads a bearer token from the named environment variable and sends it only over HTTPS or to an approved loopback target; the token is never printed or saved.";
|
|
119
131
|
const ENVIRONMENT_VARIABLE_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/u;
|
|
@@ -165,33 +177,90 @@ function resolveBearerToken(name, env) {
|
|
|
165
177
|
return { error: `Bearer token environment variable does not hold a valid bearer token: ${name}` };
|
|
166
178
|
return { token };
|
|
167
179
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
io.writeStdout(
|
|
171
|
-
|
|
172
|
-
:
|
|
173
|
-
const exitCode = report.status === "pass" ? 0
|
|
174
|
-
: report.status === "incomplete" || report.status === "blocked" ? 2 : 1;
|
|
180
|
+
// Writes the catalog report and, when asked, saves a complete enumeration as one list response.
|
|
181
|
+
async function finishCatalogCommand(io, kind, status, output, items, cache, savePath) {
|
|
182
|
+
io.writeStdout(output);
|
|
183
|
+
const exitCode = status === "pass" ? 0
|
|
184
|
+
: status === "incomplete" || status === "blocked" ? 2 : 1;
|
|
175
185
|
if (savePath === undefined)
|
|
176
186
|
return exitCode;
|
|
177
|
-
if (
|
|
178
|
-
io.writeStderr(
|
|
187
|
+
if (items === null || cache === null) {
|
|
188
|
+
io.writeStderr(`${kind.noun} catalog response was not saved because enumeration did not complete.`);
|
|
179
189
|
return 2;
|
|
180
190
|
}
|
|
181
|
-
const saved = await
|
|
191
|
+
const saved = await saveMcpListResponse(savePath, kind.name, items, cache);
|
|
182
192
|
if (saved.kind !== "saved") {
|
|
183
193
|
io.writeStderr({
|
|
184
|
-
"path-blocked":
|
|
185
|
-
"not-regular":
|
|
186
|
-
"too-large":
|
|
187
|
-
"write-failed":
|
|
194
|
+
"path-blocked": `${kind.noun} catalog response was not saved because the path is not a local file path.`,
|
|
195
|
+
"not-regular": `${kind.noun} catalog response was not saved because the existing target is not a regular file.`,
|
|
196
|
+
"too-large": `${kind.noun} catalog response was not saved because it exceeds the one MiB ${kind.name}-file limit.`,
|
|
197
|
+
"write-failed": `${kind.noun} catalog response could not be written.`
|
|
188
198
|
}[saved.kind]);
|
|
189
199
|
return 2;
|
|
190
200
|
}
|
|
191
|
-
io.writeStderr(`Saved a complete
|
|
201
|
+
io.writeStderr(`Saved a complete ${kind.method} response with ${saved.count} ${kind.name} (${saved.bytes} bytes).`);
|
|
192
202
|
return exitCode;
|
|
193
203
|
}
|
|
194
|
-
|
|
204
|
+
function finishToolCatalogCommand(io, snapshot, json, savePath) {
|
|
205
|
+
const report = snapshot.report;
|
|
206
|
+
return finishCatalogCommand(io, TOOL_CATALOG_COMMAND, report.status, json ? JSON.stringify(report, null, 2) : renderMcpToolCatalogReport(report), snapshot.tools, snapshot.cache, savePath);
|
|
207
|
+
}
|
|
208
|
+
function finishPromptCatalogCommand(io, snapshot, json, savePath) {
|
|
209
|
+
const report = snapshot.report;
|
|
210
|
+
return finishCatalogCommand(io, PROMPT_CATALOG_COMMAND, report.status, json ? JSON.stringify(report, null, 2) : renderMcpPromptCatalogReport(report), snapshot.prompts, snapshot.cache, savePath);
|
|
211
|
+
}
|
|
212
|
+
async function runHttpCatalogCommand(url, flags, io, env, kind, run) {
|
|
213
|
+
const allowedFlags = new Set(["--allow-network", "--allow-local-network", "--json"]);
|
|
214
|
+
const switches = [];
|
|
215
|
+
let savePath;
|
|
216
|
+
let tokenEnv;
|
|
217
|
+
let invalid = false;
|
|
218
|
+
for (let index = 0; index < flags.length; index += 1) {
|
|
219
|
+
const flag = flags[index];
|
|
220
|
+
if ((flag === "--save-response" && savePath === undefined) || (flag === "--bearer-token-env" && tokenEnv === undefined)) {
|
|
221
|
+
const value = flags[index + 1];
|
|
222
|
+
if (!value || value.startsWith("-")) {
|
|
223
|
+
invalid = true;
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
if (flag === "--save-response")
|
|
227
|
+
savePath = value;
|
|
228
|
+
else
|
|
229
|
+
tokenEnv = value;
|
|
230
|
+
index += 1;
|
|
231
|
+
}
|
|
232
|
+
else if (allowedFlags.has(flag) && !switches.includes(flag)) {
|
|
233
|
+
switches.push(flag);
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
invalid = true;
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (invalid) {
|
|
241
|
+
io.writeStderr(`Invalid or duplicate ${kind.noun.toLowerCase()} catalog arguments. ${kind.usage}`);
|
|
242
|
+
return 2;
|
|
243
|
+
}
|
|
244
|
+
if (!switches.includes("--allow-network")) {
|
|
245
|
+
io.writeStderr(`doctor ${kind.name} requires explicit --allow-network consent.`);
|
|
246
|
+
return 2;
|
|
247
|
+
}
|
|
248
|
+
if (savePath !== undefined && isBlockedOfflinePath(savePath)) {
|
|
249
|
+
io.writeStderr(`doctor ${kind.name} --save-response requires a local file path.`);
|
|
250
|
+
return 2;
|
|
251
|
+
}
|
|
252
|
+
const bearer = resolveBearerToken(tokenEnv, env);
|
|
253
|
+
if ("error" in bearer) {
|
|
254
|
+
io.writeStderr(bearer.error);
|
|
255
|
+
return 2;
|
|
256
|
+
}
|
|
257
|
+
return run(url, {
|
|
258
|
+
allowNetwork: true,
|
|
259
|
+
allowLocalNetwork: switches.includes("--allow-local-network"),
|
|
260
|
+
...(bearer.token === undefined ? {} : { bearerToken: bearer.token })
|
|
261
|
+
}, switches.includes("--json"), savePath);
|
|
262
|
+
}
|
|
263
|
+
async function runStdioCatalogCommand(targetPath, flags, io, env, kind, run) {
|
|
195
264
|
const allowedSwitches = new Set(["--runtime", "--json", "--require-runtime-approval"]);
|
|
196
265
|
const switches = [];
|
|
197
266
|
const values = new Map();
|
|
@@ -231,11 +300,11 @@ async function runStdioToolCatalogCommand(targetPath, flags, io, options, env) {
|
|
|
231
300
|
const approvalDigest = values.get("--runtime-approval-digest");
|
|
232
301
|
const requireApproval = switches.includes("--require-runtime-approval");
|
|
233
302
|
if (invalid || serverName === undefined || (sandbox !== undefined && sandbox !== "docker")) {
|
|
234
|
-
io.writeStderr(`Invalid or duplicate
|
|
303
|
+
io.writeStderr(`Invalid or duplicate ${kind.noun.toLowerCase()} catalog arguments. ${kind.usage}`);
|
|
235
304
|
return 2;
|
|
236
305
|
}
|
|
237
306
|
if (!switches.includes("--runtime")) {
|
|
238
|
-
io.writeStderr(
|
|
307
|
+
io.writeStderr(`doctor ${kind.name} --server starts a local MCP server and requires explicit --runtime consent.`);
|
|
239
308
|
return 2;
|
|
240
309
|
}
|
|
241
310
|
if (approvalDigest !== undefined && !requireApproval) {
|
|
@@ -262,14 +331,14 @@ async function runStdioToolCatalogCommand(targetPath, flags, io, options, env) {
|
|
|
262
331
|
}
|
|
263
332
|
}
|
|
264
333
|
if (isBlockedOfflinePath(targetPath)) {
|
|
265
|
-
io.writeStderr(
|
|
334
|
+
io.writeStderr(`doctor ${kind.name} --server requires a local package path.`);
|
|
266
335
|
return 2;
|
|
267
336
|
}
|
|
268
337
|
if (savePath !== undefined && isBlockedOfflinePath(savePath)) {
|
|
269
|
-
io.writeStderr(
|
|
338
|
+
io.writeStderr(`doctor ${kind.name} --save-response requires a local file path.`);
|
|
270
339
|
return 2;
|
|
271
340
|
}
|
|
272
|
-
const resolved = await resolveMcpStdioServer(targetPath, serverName);
|
|
341
|
+
const resolved = await resolveMcpStdioServer(targetPath, serverName, kind.name);
|
|
273
342
|
if (!resolved.ok) {
|
|
274
343
|
io.writeStderr(resolved.message);
|
|
275
344
|
return 2;
|
|
@@ -290,8 +359,74 @@ async function runStdioToolCatalogCommand(targetPath, flags, io, options, env) {
|
|
|
290
359
|
return 1;
|
|
291
360
|
}
|
|
292
361
|
}
|
|
293
|
-
|
|
294
|
-
|
|
362
|
+
return run(resolved.server, sandbox !== "docker" ? {} : environment.length > 0 ? { sandbox: "docker", environment } : { sandbox: "docker" }, switches.includes("--json"), savePath);
|
|
363
|
+
}
|
|
364
|
+
// Runs tools-diff or prompts-diff, which share their options and exit codes.
|
|
365
|
+
async function runOfflineDiffCommand(args, io, usage, compare, render) {
|
|
366
|
+
let beforePath;
|
|
367
|
+
let afterPath;
|
|
368
|
+
let format;
|
|
369
|
+
let failOn;
|
|
370
|
+
let showNames = false;
|
|
371
|
+
let invalid = false;
|
|
372
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
373
|
+
const flag = args[index];
|
|
374
|
+
if (flag === "--show-names" && !showNames) {
|
|
375
|
+
showNames = true;
|
|
376
|
+
}
|
|
377
|
+
else if ((flag === "--json" || flag === "--markdown") && format === undefined) {
|
|
378
|
+
format = flag === "--json" ? "json" : "markdown";
|
|
379
|
+
}
|
|
380
|
+
else if (flag === "--fail-on" && failOn === undefined) {
|
|
381
|
+
const value = args[index + 1];
|
|
382
|
+
if (value !== "any" && value !== "breaking") {
|
|
383
|
+
invalid = true;
|
|
384
|
+
break;
|
|
385
|
+
}
|
|
386
|
+
failOn = value;
|
|
387
|
+
index += 1;
|
|
388
|
+
}
|
|
389
|
+
else if ((flag === "--before" && beforePath === undefined) || (flag === "--after" && afterPath === undefined)) {
|
|
390
|
+
const value = args[index + 1];
|
|
391
|
+
if (!value || value.startsWith("-")) {
|
|
392
|
+
invalid = true;
|
|
393
|
+
break;
|
|
394
|
+
}
|
|
395
|
+
if (flag === "--before")
|
|
396
|
+
beforePath = value;
|
|
397
|
+
else
|
|
398
|
+
afterPath = value;
|
|
399
|
+
index += 1;
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
invalid = true;
|
|
403
|
+
break;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
if (invalid || !beforePath || !afterPath) {
|
|
407
|
+
io.writeStderr(`Invalid comparison arguments. ${usage}`);
|
|
408
|
+
return 2;
|
|
409
|
+
}
|
|
410
|
+
const report = await compare(beforePath, afterPath, showNames);
|
|
411
|
+
io.writeStdout(format === "json" ? JSON.stringify(report, null, 2) : render(report, format === "markdown"));
|
|
412
|
+
if (report.status !== "pass" && report.status !== "warn")
|
|
413
|
+
return 2;
|
|
414
|
+
if (failOn === "breaking") {
|
|
415
|
+
return (report.comparison.breaking ?? 0) + (report.comparison.unclassified ?? 0) > 0 ? 1 : 0;
|
|
416
|
+
}
|
|
417
|
+
return report.status === "pass" ? 0 : 1;
|
|
418
|
+
}
|
|
419
|
+
// Runs tools-file or prompts-file, which share their options and exit codes.
|
|
420
|
+
async function runOfflineFileCommand(args, io, usage, inspect, render) {
|
|
421
|
+
const [filePath, ...flags] = args;
|
|
422
|
+
if (!filePath || filePath.startsWith("-") || flags.some((flag) => flag !== "--json") || flags.length > 1) {
|
|
423
|
+
io.writeStderr(`Invalid file inspection arguments. ${usage}`);
|
|
424
|
+
return 2;
|
|
425
|
+
}
|
|
426
|
+
const report = await inspect(filePath);
|
|
427
|
+
io.writeStdout(flags.includes("--json") ? JSON.stringify(report, null, 2) : render(report));
|
|
428
|
+
return report.status === "pass" ? 0
|
|
429
|
+
: report.status === "warn" || report.status === "fail" ? 1 : 2;
|
|
295
430
|
}
|
|
296
431
|
function parseRuntimeSandbox(flags, options = { requireRuntime: true }) {
|
|
297
432
|
const index = flags.indexOf("--sandbox");
|
|
@@ -377,8 +512,11 @@ function printUsage(io) {
|
|
|
377
512
|
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] [--tools-diff-server <name> [--baseline <path>] [--sandbox docker]]\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");
|
|
378
513
|
io.writeStderr(discoveryUsage);
|
|
379
514
|
io.writeStderr(toolsUsage);
|
|
515
|
+
io.writeStderr(promptsUsage);
|
|
380
516
|
io.writeStderr(toolsFileUsage);
|
|
381
517
|
io.writeStderr(toolsDiffUsage);
|
|
518
|
+
io.writeStderr(promptsFileUsage);
|
|
519
|
+
io.writeStderr(promptsDiffUsage);
|
|
382
520
|
io.writeStderr("Registry readiness: codex-plugin-doctor registry check <server.json|directory> [--json] [--output <path>] [--require-registry-readiness]\n"
|
|
383
521
|
+ " codex-plugin-doctor registry inspect <server-name> --allow-network [--json] [--output <path>] [--require-registry-readiness]\n"
|
|
384
522
|
+ "Registry publication preflight: codex-plugin-doctor registry preflight <server.json|directory> [--allow-network] [--json] [--output <path>] [--require-publish-ready]");
|
|
@@ -1342,10 +1480,10 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
1342
1480
|
(command === "doctor" &&
|
|
1343
1481
|
(["runtime-plan", "runtime-policy"].includes(maybePath ?? "") ||
|
|
1344
1482
|
(maybePath === "release-evidence" && remainingArgs[0] !== "verify") ||
|
|
1345
|
-
(maybePath === "tools" && remainingArgs.includes("--server")))) ||
|
|
1483
|
+
((maybePath === "tools" || maybePath === "prompts") && remainingArgs.includes("--server")))) ||
|
|
1346
1484
|
(command === "init-ci" && args.includes("--tools-diff-server"));
|
|
1347
1485
|
if (args.includes("--sandbox") && !runtimeSandboxSupported) {
|
|
1348
|
-
io.writeStderr("--sandbox is supported only by single-package check, release check, runtime-plan, runtime-policy, release-evidence, doctor tools --server, and init-ci --tools-diff-server.");
|
|
1486
|
+
io.writeStderr("--sandbox is supported only by single-package check, release check, runtime-plan, runtime-policy, release-evidence, doctor tools --server, doctor prompts --server, and init-ci --tools-diff-server.");
|
|
1349
1487
|
return 2;
|
|
1350
1488
|
}
|
|
1351
1489
|
if (command === "--version" || command === "-v" || command === "version") {
|
|
@@ -1397,78 +1535,35 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
1397
1535
|
--show-names adds tool names and the JSON Pointer of each reason; reports omit both by default, so keep it out of public output such as pull request comments.`);
|
|
1398
1536
|
return 0;
|
|
1399
1537
|
}
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
}
|
|
1411
|
-
else if ((flag === "--json" || flag === "--markdown") && format === undefined) {
|
|
1412
|
-
format = flag === "--json" ? "json" : "markdown";
|
|
1413
|
-
}
|
|
1414
|
-
else if (flag === "--fail-on" && failOn === undefined) {
|
|
1415
|
-
const value = remainingArgs[index + 1];
|
|
1416
|
-
if (value !== "any" && value !== "breaking") {
|
|
1417
|
-
invalid = true;
|
|
1418
|
-
break;
|
|
1419
|
-
}
|
|
1420
|
-
failOn = value;
|
|
1421
|
-
index += 1;
|
|
1422
|
-
}
|
|
1423
|
-
else if ((flag === "--before" && beforePath === undefined) || (flag === "--after" && afterPath === undefined)) {
|
|
1424
|
-
const value = remainingArgs[index + 1];
|
|
1425
|
-
if (!value || value.startsWith("-")) {
|
|
1426
|
-
invalid = true;
|
|
1427
|
-
break;
|
|
1428
|
-
}
|
|
1429
|
-
if (flag === "--before")
|
|
1430
|
-
beforePath = value;
|
|
1431
|
-
else
|
|
1432
|
-
afterPath = value;
|
|
1433
|
-
index += 1;
|
|
1434
|
-
}
|
|
1435
|
-
else {
|
|
1436
|
-
invalid = true;
|
|
1437
|
-
break;
|
|
1438
|
-
}
|
|
1439
|
-
}
|
|
1440
|
-
if (invalid || !beforePath || !afterPath) {
|
|
1441
|
-
io.writeStderr(`Invalid comparison arguments. ${toolsDiffUsage}`);
|
|
1442
|
-
return 2;
|
|
1443
|
-
}
|
|
1444
|
-
const compare = options.compareMcpToolFilesImpl ?? compareMcpToolFiles;
|
|
1445
|
-
const report = await (showNames ? compare(beforePath, afterPath, { showNames: true }) : compare(beforePath, afterPath));
|
|
1446
|
-
io.writeStdout(format === "json" ? JSON.stringify(report, null, 2)
|
|
1447
|
-
: format === "markdown" ? renderMcpToolDiffMarkdown(report)
|
|
1448
|
-
: renderMcpToolDiffReport(report));
|
|
1449
|
-
if (report.status !== "pass" && report.status !== "warn")
|
|
1450
|
-
return 2;
|
|
1451
|
-
if (failOn === "breaking") {
|
|
1452
|
-
return (report.comparison.breaking ?? 0) + (report.comparison.unclassified ?? 0) > 0 ? 1 : 0;
|
|
1538
|
+
return runOfflineDiffCommand(remainingArgs, io, toolsDiffUsage, (beforePath, afterPath, showNames) => {
|
|
1539
|
+
const compare = options.compareMcpToolFilesImpl ?? compareMcpToolFiles;
|
|
1540
|
+
return showNames ? compare(beforePath, afterPath, { showNames: true }) : compare(beforePath, afterPath);
|
|
1541
|
+
}, (report, markdown) => markdown ? renderMcpToolDiffMarkdown(report) : renderMcpToolDiffReport(report));
|
|
1542
|
+
}
|
|
1543
|
+
if (maybePath === "prompts-diff") {
|
|
1544
|
+
if (remainingArgs.length === 1 && remainingArgs[0] === "--help") {
|
|
1545
|
+
io.writeStdout(`${promptsDiffUsage}\nCompares two saved MCP prompts/list responses offline. Changes are classified heuristically as breaking, compatible, or unclassified; prompts are not retrieved and compatibility is not tested.\n--fail-on breaking exits 1 only for breaking or unclassified changes (default: any change or input warning).
|
|
1546
|
+
--show-names adds prompt names and the argument behind each reason; reports omit both by default, so keep it out of public output such as pull request comments.`);
|
|
1547
|
+
return 0;
|
|
1453
1548
|
}
|
|
1454
|
-
return
|
|
1549
|
+
return runOfflineDiffCommand(remainingArgs, io, promptsDiffUsage, (beforePath, afterPath, showNames) => {
|
|
1550
|
+
const compare = options.compareMcpPromptFilesImpl ?? compareMcpPromptFiles;
|
|
1551
|
+
return showNames ? compare(beforePath, afterPath, { showNames: true }) : compare(beforePath, afterPath);
|
|
1552
|
+
}, (report, markdown) => markdown ? renderMcpPromptDiffMarkdown(report) : renderMcpPromptDiffReport(report));
|
|
1455
1553
|
}
|
|
1456
1554
|
if (maybePath === "tools-file") {
|
|
1457
1555
|
if (remainingArgs.length === 1 && remainingArgs[0] === "--help") {
|
|
1458
1556
|
io.writeStdout(`${toolsFileUsage}\nInspects one saved UTF-8 MCP tools/list response. No server connection or tool execution.`);
|
|
1459
1557
|
return 0;
|
|
1460
1558
|
}
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1559
|
+
return runOfflineFileCommand(remainingArgs, io, toolsFileUsage, options.inspectMcpToolFileImpl ?? inspectMcpToolFile, renderMcpToolFileReport);
|
|
1560
|
+
}
|
|
1561
|
+
if (maybePath === "prompts-file") {
|
|
1562
|
+
if (remainingArgs.length === 1 && remainingArgs[0] === "--help") {
|
|
1563
|
+
io.writeStdout(`${promptsFileUsage}\nInspects one saved UTF-8 MCP prompts/list response. No server connection or prompt retrieval.`);
|
|
1564
|
+
return 0;
|
|
1465
1565
|
}
|
|
1466
|
-
|
|
1467
|
-
io.writeStdout(flags.includes("--json")
|
|
1468
|
-
? JSON.stringify(report, null, 2)
|
|
1469
|
-
: renderMcpToolFileReport(report));
|
|
1470
|
-
return report.status === "pass" ? 0
|
|
1471
|
-
: report.status === "warn" || report.status === "fail" ? 1 : 2;
|
|
1566
|
+
return runOfflineFileCommand(remainingArgs, io, promptsFileUsage, options.inspectMcpPromptFileImpl ?? inspectMcpPromptFile, renderMcpPromptFileReport);
|
|
1472
1567
|
}
|
|
1473
1568
|
if (maybePath === "tools") {
|
|
1474
1569
|
if (remainingArgs.length === 1 && remainingArgs[0] === "--help") {
|
|
@@ -1481,61 +1576,29 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
1481
1576
|
return 2;
|
|
1482
1577
|
}
|
|
1483
1578
|
if (flags.includes("--server")) {
|
|
1484
|
-
return
|
|
1579
|
+
return runStdioCatalogCommand(url, flags, io, terminalContext.env, TOOL_CATALOG_COMMAND, async (server, captureOptions, json, savePath) => finishToolCatalogCommand(io, await (options.captureStdioMcpToolCatalogImpl ?? captureStdioMcpToolCatalog)(server, captureOptions), json, savePath));
|
|
1485
1580
|
}
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
break;
|
|
1498
|
-
}
|
|
1499
|
-
if (flag === "--save-response")
|
|
1500
|
-
savePath = value;
|
|
1501
|
-
else
|
|
1502
|
-
tokenEnv = value;
|
|
1503
|
-
index += 1;
|
|
1504
|
-
}
|
|
1505
|
-
else if (allowedFlags.has(flag) && !switches.includes(flag)) {
|
|
1506
|
-
switches.push(flag);
|
|
1507
|
-
}
|
|
1508
|
-
else {
|
|
1509
|
-
invalid = true;
|
|
1510
|
-
break;
|
|
1511
|
-
}
|
|
1512
|
-
}
|
|
1513
|
-
if (invalid) {
|
|
1514
|
-
io.writeStderr(`Invalid or duplicate tool catalog arguments. ${toolsUsage}`);
|
|
1515
|
-
return 2;
|
|
1516
|
-
}
|
|
1517
|
-
if (!switches.includes("--allow-network")) {
|
|
1518
|
-
io.writeStderr("doctor tools requires explicit --allow-network consent.");
|
|
1519
|
-
return 2;
|
|
1581
|
+
return runHttpCatalogCommand(url, flags, io, terminalContext.env, TOOL_CATALOG_COMMAND, async (target, catalogOptions, json, savePath) => {
|
|
1582
|
+
const snapshot = savePath === undefined
|
|
1583
|
+
? { report: await (options.inspectMcpToolCatalogImpl ?? inspectMcpToolCatalog)(target, catalogOptions), tools: null, cache: null }
|
|
1584
|
+
: await (options.captureMcpToolCatalogImpl ?? captureMcpToolCatalog)(target, catalogOptions);
|
|
1585
|
+
return finishToolCatalogCommand(io, snapshot, json, savePath);
|
|
1586
|
+
});
|
|
1587
|
+
}
|
|
1588
|
+
if (maybePath === "prompts") {
|
|
1589
|
+
if (remainingArgs.length === 1 && remainingArgs[0] === "--help") {
|
|
1590
|
+
io.writeStdout(`${promptsUsage}\nPrompt catalog structure only; prompts are not retrieved.\n--server starts the named stdio server from the package MCP configuration after explicit --runtime consent and stops it after reading the prompt list.\n${forwardedEnvironmentHelp}\n--save-response writes a complete enumeration as one prompts/list response for doctor prompts-file and prompts-diff; nothing is written when enumeration is incomplete.\n${bearerTokenHelp}`);
|
|
1591
|
+
return 0;
|
|
1520
1592
|
}
|
|
1521
|
-
|
|
1522
|
-
|
|
1593
|
+
const [target, ...flags] = remainingArgs;
|
|
1594
|
+
if (!target || target.startsWith("-")) {
|
|
1595
|
+
io.writeStderr(promptsUsage);
|
|
1523
1596
|
return 2;
|
|
1524
1597
|
}
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
io.writeStderr(bearer.error);
|
|
1528
|
-
return 2;
|
|
1598
|
+
if (flags.includes("--server")) {
|
|
1599
|
+
return runStdioCatalogCommand(target, flags, io, terminalContext.env, PROMPT_CATALOG_COMMAND, async (server, captureOptions, json, savePath) => finishPromptCatalogCommand(io, await (options.captureStdioMcpPromptCatalogImpl ?? captureStdioMcpPromptCatalog)(server, captureOptions), json, savePath));
|
|
1529
1600
|
}
|
|
1530
|
-
|
|
1531
|
-
allowNetwork: true,
|
|
1532
|
-
allowLocalNetwork: switches.includes("--allow-local-network"),
|
|
1533
|
-
...(bearer.token === undefined ? {} : { bearerToken: bearer.token })
|
|
1534
|
-
};
|
|
1535
|
-
const snapshot = savePath === undefined
|
|
1536
|
-
? { report: await (options.inspectMcpToolCatalogImpl ?? inspectMcpToolCatalog)(url, catalogOptions), tools: null, cache: null }
|
|
1537
|
-
: await (options.captureMcpToolCatalogImpl ?? captureMcpToolCatalog)(url, catalogOptions);
|
|
1538
|
-
return finishToolCatalogCommand(io, snapshot, switches.includes("--json"), savePath);
|
|
1601
|
+
return runHttpCatalogCommand(target, flags, io, terminalContext.env, PROMPT_CATALOG_COMMAND, async (url, catalogOptions, json, savePath) => finishPromptCatalogCommand(io, await (options.captureMcpPromptCatalogImpl ?? captureMcpPromptCatalog)(url, catalogOptions), json, savePath));
|
|
1539
1602
|
}
|
|
1540
1603
|
if (maybePath === "discover") {
|
|
1541
1604
|
if (remainingArgs.length === 1 && remainingArgs[0] === "--help") {
|
package/package.json
CHANGED