codex-plugin-doctor 0.9.0 → 0.10.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 +20 -9
- package/dist/core/environment-doctor.d.ts +10 -0
- package/dist/core/environment-doctor.js +111 -0
- package/dist/core/fix-plan.d.ts +4 -1
- package/dist/core/fix-plan.js +11 -3
- package/dist/core/init-plugin.d.ts +10 -1
- package/dist/core/init-plugin.js +162 -15
- package/dist/reporting/render-installed-summary.d.ts +2 -0
- package/dist/reporting/render-installed-summary.js +34 -0
- package/dist/run-cli.d.ts +1 -0
- package/dist/run-cli.js +98 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -87,9 +87,10 @@ If you already have Codex installed locally and do not know plugin paths, discov
|
|
|
87
87
|
|
|
88
88
|
```bash
|
|
89
89
|
codex-plugin-doctor list --installed
|
|
90
|
-
codex-plugin-doctor check --installed
|
|
91
|
-
codex-plugin-doctor check --installed --all-summary
|
|
92
|
-
codex-plugin-doctor check --installed
|
|
90
|
+
codex-plugin-doctor check --installed
|
|
91
|
+
codex-plugin-doctor check --installed --all-summary
|
|
92
|
+
codex-plugin-doctor check --installed --compat --all-summary
|
|
93
|
+
codex-plugin-doctor check --installed github
|
|
93
94
|
codex-plugin-doctor explain plugin.manifest.missing
|
|
94
95
|
```
|
|
95
96
|
|
|
@@ -161,10 +162,15 @@ x plugin.security.hard_coded_secret
|
|
|
161
162
|
Run these from a Codex plugin package root:
|
|
162
163
|
|
|
163
164
|
```bash
|
|
164
|
-
codex-plugin-doctor --version
|
|
165
|
+
codex-plugin-doctor --version
|
|
165
166
|
codex-plugin-doctor self-test
|
|
166
167
|
codex-plugin-doctor doctor
|
|
168
|
+
codex-plugin-doctor doctor clients
|
|
169
|
+
codex-plugin-doctor doctor --update-check
|
|
167
170
|
codex-plugin-doctor init my-plugin
|
|
171
|
+
codex-plugin-doctor init my-mcp --template mcp-stdio
|
|
172
|
+
codex-plugin-doctor init remote-mcp --template mcp-http
|
|
173
|
+
codex-plugin-doctor init runtime-demo --template full-runtime
|
|
168
174
|
codex-plugin-doctor compat .
|
|
169
175
|
codex-plugin-doctor compat . --all --scorecard
|
|
170
176
|
codex-plugin-doctor compat . --client codex
|
|
@@ -207,7 +213,9 @@ codex-plugin-doctor check . --json --runtime --verbose-runtime
|
|
|
207
213
|
|
|
208
214
|
`self-test` runs the bundled runtime-complete sample through static validation, runtime MCP probes, and the compatibility scorecard. It is the fastest post-install check after `npm install -g codex-plugin-doctor`.
|
|
209
215
|
|
|
210
|
-
`doctor` checks the local environment, including package version, platform, Node version, npm global prefix, Codex home, and Codex plugin cache visibility. The text output also includes recommended next commands for self-test, installed plugin discovery, runtime checks, compatibility scoring, and CI setup.
|
|
216
|
+
`doctor` checks the local environment, including package version, platform, Node version, npm global prefix, Codex home, and Codex plugin cache visibility. The text output also includes recommended next commands for self-test, installed plugin discovery, runtime checks, compatibility scoring, and CI setup. `doctor clients` reports local Codex, Claude Desktop, Cursor, Cline, and Windsurf config readiness. `doctor --update-check` compares the installed CLI version with the latest npm version and prints the upgrade command when a newer release is available.
|
|
217
|
+
|
|
218
|
+
`init [path] --template ...` creates targeted starter packages. `skill-only` is the default minimal skill package, `mcp-stdio` adds a local stdio MCP config and mock server, `mcp-http` scaffolds a streamable HTTP MCP config, and `full-runtime` generates a stdio sample that passes the runtime protocol probes.
|
|
211
219
|
|
|
212
220
|
`compat --client claude-desktop` checks whether the MCP package can be added to the local Claude Desktop setup. On Windows it looks for `%APPDATA%\Claude\claude_desktop_config.json`; on macOS it looks for `~/Library/Application Support/Claude/claude_desktop_config.json`. A valid existing config returns `PASS`, a missing Claude Desktop install returns `WARN`, and a malformed local config returns `FAIL` so you do not add new servers into a broken config file. If the package server name already exists in Claude Desktop, the command returns `WARN` with the duplicate server name. Add `--install-preview` to print the JSON snippet that should be merged into `claude_desktop_config.json`; it does not modify files. Use `--apply --backup` only when you want the CLI to create a timestamped backup and merge the server config. Apply mode refuses to overwrite duplicate server names.
|
|
213
221
|
|
|
@@ -217,6 +225,8 @@ codex-plugin-doctor check . --json --runtime --verbose-runtime
|
|
|
217
225
|
|
|
218
226
|
`compat --all` makes the all-client matrix explicit when you want Codex, Generic MCP, Claude Desktop, Cursor, Cline, and Windsurf in one run. `compat --scorecard` turns the compatibility matrix into a compact score summary. `PASS` maps to `100`, `WARN` maps to `70`, and `FAIL` or `SKIPPED` maps to `0`.
|
|
219
227
|
|
|
228
|
+
`check --installed --compat --all-summary` validates every discovered Codex plugin from the local plugin cache and appends a compact compatibility summary for Codex, Generic MCP, Claude Desktop, Cursor, Cline, and Windsurf. This is the fastest repo-free audit when a user does not know individual plugin paths.
|
|
229
|
+
|
|
220
230
|
`check --profile ci|strict|publish` applies named validation policies. `ci` keeps default behavior, `strict` fails on warnings, and `publish` fails on warnings while enabling runtime probing by default.
|
|
221
231
|
|
|
222
232
|
`check --explain` adds inline rule catalog context to text reports, including why a finding matters, a more detailed fix path, and a compact example.
|
|
@@ -225,7 +235,7 @@ codex-plugin-doctor check . --json --runtime --verbose-runtime
|
|
|
225
235
|
|
|
226
236
|
`check --history <path>` appends a compact JSONL validation snapshot after a single package check. `history <path>` reads the JSONL file and compares the latest run to the previous run, including status, finding-count deltas, and whether the latest run regressed. Add `history --json` for automation output or `history --fail-on-regression` when CI should fail after a worse latest run.
|
|
227
237
|
|
|
228
|
-
`fix --dry-run` renders safe automatic fix plans without changing files. `fix --interactive --backup` shows the same plan, then applies
|
|
238
|
+
`fix --dry-run` renders safe automatic fix plans without changing files. `fix --interactive --backup` shows the same numbered plan, then applies everything after `yes` or only selected action numbers such as `1,3`. `fix --apply --backup` applies supported safe fixes, such as manifest defaults and missing skills directories, after creating backups.
|
|
229
239
|
|
|
230
240
|
Optional local policy file:
|
|
231
241
|
|
|
@@ -240,9 +250,10 @@ Run these when you want Codex Plugin Doctor to find plugins from the local Codex
|
|
|
240
250
|
|
|
241
251
|
```bash
|
|
242
252
|
codex-plugin-doctor list --installed
|
|
243
|
-
codex-plugin-doctor check --installed
|
|
244
|
-
codex-plugin-doctor check --installed --all-summary
|
|
245
|
-
codex-plugin-doctor check --installed
|
|
253
|
+
codex-plugin-doctor check --installed
|
|
254
|
+
codex-plugin-doctor check --installed --all-summary
|
|
255
|
+
codex-plugin-doctor check --installed --compat --all-summary
|
|
256
|
+
codex-plugin-doctor check --installed github
|
|
246
257
|
codex-plugin-doctor check --installed github --runtime --no-animations
|
|
247
258
|
codex-plugin-doctor explain plugin.security.hard_coded_secret
|
|
248
259
|
```
|
|
@@ -14,6 +14,16 @@ export interface EnvironmentDoctorReport {
|
|
|
14
14
|
path: string | null;
|
|
15
15
|
};
|
|
16
16
|
}
|
|
17
|
+
export interface ClientDoctorResult {
|
|
18
|
+
client: string;
|
|
19
|
+
status: "pass" | "warn";
|
|
20
|
+
configPath: string | null;
|
|
21
|
+
configExists: boolean;
|
|
22
|
+
directoryWritable: boolean;
|
|
23
|
+
summary: string;
|
|
24
|
+
}
|
|
17
25
|
export declare function renderEnvironmentDoctor(terminalContext: CliTerminalContext): Promise<string>;
|
|
26
|
+
export declare function buildClientDoctorReport(terminalContext: CliTerminalContext): Promise<ClientDoctorResult[]>;
|
|
27
|
+
export declare function renderClientDoctor(terminalContext: CliTerminalContext): Promise<string>;
|
|
18
28
|
export declare function buildEnvironmentDoctorReport(terminalContext: CliTerminalContext): Promise<EnvironmentDoctorReport>;
|
|
19
29
|
export declare function renderEnvironmentDoctorJson(terminalContext: CliTerminalContext): Promise<string>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { constants } from "node:fs";
|
|
1
2
|
import { access } from "node:fs/promises";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { packageVersion } from "../version.js";
|
|
@@ -10,6 +11,15 @@ async function pathExists(targetPath) {
|
|
|
10
11
|
return false;
|
|
11
12
|
}
|
|
12
13
|
}
|
|
14
|
+
async function pathWritable(targetPath) {
|
|
15
|
+
try {
|
|
16
|
+
await access(targetPath, constants.W_OK);
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
13
23
|
function resolveCodexHome(env) {
|
|
14
24
|
if (env.CODEX_HOME) {
|
|
15
25
|
return path.resolve(env.CODEX_HOME);
|
|
@@ -22,6 +32,89 @@ function resolveCodexHome(env) {
|
|
|
22
32
|
}
|
|
23
33
|
return null;
|
|
24
34
|
}
|
|
35
|
+
function resolveHomeDirectory(env) {
|
|
36
|
+
return env.USERPROFILE ?? env.HOME ?? null;
|
|
37
|
+
}
|
|
38
|
+
function resolveClaudeConfigPath(terminalContext) {
|
|
39
|
+
const platform = terminalContext.platform ?? process.platform;
|
|
40
|
+
const homeDirectory = resolveHomeDirectory(terminalContext.env);
|
|
41
|
+
if (platform === "win32") {
|
|
42
|
+
return terminalContext.env.APPDATA
|
|
43
|
+
? path.join(terminalContext.env.APPDATA, "Claude", "claude_desktop_config.json")
|
|
44
|
+
: null;
|
|
45
|
+
}
|
|
46
|
+
if (platform === "darwin" && homeDirectory) {
|
|
47
|
+
return path.join(homeDirectory, "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
function resolveClineConfigPath(env) {
|
|
52
|
+
const homeDirectory = resolveHomeDirectory(env);
|
|
53
|
+
const clineDirectory = env.CLINE_DIR
|
|
54
|
+
? path.resolve(env.CLINE_DIR)
|
|
55
|
+
: homeDirectory
|
|
56
|
+
? path.join(homeDirectory, ".cline")
|
|
57
|
+
: null;
|
|
58
|
+
return clineDirectory
|
|
59
|
+
? path.join(clineDirectory, "data", "settings", "cline_mcp_settings.json")
|
|
60
|
+
: null;
|
|
61
|
+
}
|
|
62
|
+
function resolveClientConfigPaths(terminalContext) {
|
|
63
|
+
const env = terminalContext.env;
|
|
64
|
+
const homeDirectory = resolveHomeDirectory(env);
|
|
65
|
+
return [
|
|
66
|
+
{
|
|
67
|
+
client: "Codex",
|
|
68
|
+
configPath: resolveCodexHome(env)
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
client: "Claude Desktop",
|
|
72
|
+
configPath: resolveClaudeConfigPath(terminalContext)
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
client: "Cursor",
|
|
76
|
+
configPath: homeDirectory ? path.join(homeDirectory, ".cursor", "mcp.json") : null
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
client: "Cline",
|
|
80
|
+
configPath: resolveClineConfigPath(env)
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
client: "Windsurf",
|
|
84
|
+
configPath: homeDirectory
|
|
85
|
+
? path.join(homeDirectory, ".codeium", "windsurf", "mcp_config.json")
|
|
86
|
+
: null
|
|
87
|
+
}
|
|
88
|
+
];
|
|
89
|
+
}
|
|
90
|
+
async function inspectClientConfig(client, configPath) {
|
|
91
|
+
if (!configPath) {
|
|
92
|
+
return {
|
|
93
|
+
client,
|
|
94
|
+
status: "warn",
|
|
95
|
+
configPath,
|
|
96
|
+
configExists: false,
|
|
97
|
+
directoryWritable: false,
|
|
98
|
+
summary: "Config path could not be resolved."
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
const configExists = await pathExists(configPath);
|
|
102
|
+
const directory = client === "Codex" ? configPath : path.dirname(configPath);
|
|
103
|
+
const directoryExists = await pathExists(directory);
|
|
104
|
+
const directoryWritable = directoryExists ? await pathWritable(directory) : false;
|
|
105
|
+
return {
|
|
106
|
+
client,
|
|
107
|
+
status: configExists || directoryWritable ? "pass" : "warn",
|
|
108
|
+
configPath,
|
|
109
|
+
configExists,
|
|
110
|
+
directoryWritable,
|
|
111
|
+
summary: configExists
|
|
112
|
+
? "Config path exists."
|
|
113
|
+
: directoryWritable
|
|
114
|
+
? "Config directory exists and is writable."
|
|
115
|
+
: "Config path was not detected on this machine."
|
|
116
|
+
};
|
|
117
|
+
}
|
|
25
118
|
export async function renderEnvironmentDoctor(terminalContext) {
|
|
26
119
|
const report = await buildEnvironmentDoctorReport(terminalContext);
|
|
27
120
|
return [
|
|
@@ -43,6 +136,24 @@ export async function renderEnvironmentDoctor(terminalContext) {
|
|
|
43
136
|
"codex-plugin-doctor init-ci ."
|
|
44
137
|
].join("\n");
|
|
45
138
|
}
|
|
139
|
+
export async function buildClientDoctorReport(terminalContext) {
|
|
140
|
+
return Promise.all(resolveClientConfigPaths(terminalContext).map((item) => inspectClientConfig(item.client, item.configPath)));
|
|
141
|
+
}
|
|
142
|
+
export async function renderClientDoctor(terminalContext) {
|
|
143
|
+
const results = await buildClientDoctorReport(terminalContext);
|
|
144
|
+
const lines = [
|
|
145
|
+
"Codex Plugin Doctor Clients",
|
|
146
|
+
"===========================",
|
|
147
|
+
""
|
|
148
|
+
];
|
|
149
|
+
for (const result of results) {
|
|
150
|
+
lines.push(`${result.client}: ${result.status.toUpperCase()} - ${result.summary}`);
|
|
151
|
+
lines.push(` Config: ${result.configPath ?? "(unknown)"}`);
|
|
152
|
+
lines.push(` Exists: ${result.configExists ? "yes" : "no"}`);
|
|
153
|
+
lines.push(` Writable: ${result.directoryWritable ? "yes" : "no"}`);
|
|
154
|
+
}
|
|
155
|
+
return lines.join("\n");
|
|
156
|
+
}
|
|
46
157
|
export async function buildEnvironmentDoctorReport(terminalContext) {
|
|
47
158
|
const codexHome = resolveCodexHome(terminalContext.env);
|
|
48
159
|
const codexHomeExists = codexHome ? await pathExists(codexHome) : false;
|
package/dist/core/fix-plan.d.ts
CHANGED
|
@@ -14,6 +14,9 @@ export interface ApplyFixPlanResult {
|
|
|
14
14
|
filesChanged: number;
|
|
15
15
|
backupDirectory: string;
|
|
16
16
|
}
|
|
17
|
+
export interface ApplyFixPlanOptions {
|
|
18
|
+
actionIndexes?: number[];
|
|
19
|
+
}
|
|
17
20
|
export interface FixPlanJsonReport {
|
|
18
21
|
schemaVersion: "1.0.0";
|
|
19
22
|
mode: "dry-run" | "apply";
|
|
@@ -26,7 +29,7 @@ export interface FixPlanJsonReport {
|
|
|
26
29
|
}
|
|
27
30
|
export declare function buildFixPlan(targetPath: string): Promise<FixPlan>;
|
|
28
31
|
export declare function renderFixPlan(plan: FixPlan, mode: "dry-run" | "interactive"): string;
|
|
29
|
-
export declare function applyFixPlan(targetPath: string): Promise<ApplyFixPlanResult>;
|
|
32
|
+
export declare function applyFixPlan(targetPath: string, options?: ApplyFixPlanOptions): Promise<ApplyFixPlanResult>;
|
|
30
33
|
export declare function renderApplyFixResult(result: ApplyFixPlanResult): string;
|
|
31
34
|
export declare function renderFixPlanJsonReport(plan: FixPlan, options: {
|
|
32
35
|
mode: "dry-run" | "apply";
|
package/dist/core/fix-plan.js
CHANGED
|
@@ -167,11 +167,19 @@ async function backupFile(rootPath, backupDirectory, filePath) {
|
|
|
167
167
|
await mkdir(path.dirname(backupPath), { recursive: true });
|
|
168
168
|
await copyFile(filePath, backupPath);
|
|
169
169
|
}
|
|
170
|
-
export async function applyFixPlan(targetPath) {
|
|
170
|
+
export async function applyFixPlan(targetPath, options = {}) {
|
|
171
171
|
const plan = await buildFixPlan(targetPath);
|
|
172
|
+
const selectedIndexes = new Set(options.actionIndexes ?? []);
|
|
173
|
+
const actions = selectedIndexes.size === 0
|
|
174
|
+
? plan.actions
|
|
175
|
+
: plan.actions.filter((_, index) => selectedIndexes.has(index + 1));
|
|
176
|
+
const appliedPlan = {
|
|
177
|
+
...plan,
|
|
178
|
+
actions
|
|
179
|
+
};
|
|
172
180
|
const backupDirectory = path.join(plan.targetPath, ".codex-doctor-backups", timestampForPath());
|
|
173
181
|
let filesChanged = 0;
|
|
174
|
-
for (const action of
|
|
182
|
+
for (const action of appliedPlan.actions) {
|
|
175
183
|
if (action.operation === "update-json" && action.id === "manifest.safe_defaults") {
|
|
176
184
|
await backupFile(plan.targetPath, backupDirectory, action.targetPath);
|
|
177
185
|
const manifest = JSON.parse(await readFile(action.targetPath, "utf8"));
|
|
@@ -210,7 +218,7 @@ export async function applyFixPlan(targetPath) {
|
|
|
210
218
|
}
|
|
211
219
|
}
|
|
212
220
|
return {
|
|
213
|
-
plan,
|
|
221
|
+
plan: appliedPlan,
|
|
214
222
|
filesChanged,
|
|
215
223
|
backupDirectory
|
|
216
224
|
};
|
|
@@ -1,6 +1,15 @@
|
|
|
1
|
+
export declare const initPluginTemplates: readonly ["skill-only", "mcp-stdio", "mcp-http", "full-runtime"];
|
|
2
|
+
export type InitPluginTemplate = (typeof initPluginTemplates)[number];
|
|
3
|
+
export interface InitPluginOptions {
|
|
4
|
+
template?: InitPluginTemplate;
|
|
5
|
+
}
|
|
1
6
|
export interface InitPluginResult {
|
|
2
7
|
rootPath: string;
|
|
3
8
|
manifestPath: string;
|
|
4
9
|
skillPath: string;
|
|
10
|
+
template: InitPluginTemplate;
|
|
11
|
+
mcpConfigPath?: string;
|
|
12
|
+
serverPath?: string;
|
|
5
13
|
}
|
|
6
|
-
export declare function
|
|
14
|
+
export declare function isInitPluginTemplate(value: string): value is InitPluginTemplate;
|
|
15
|
+
export declare function initPluginPackage(targetPath: string, options?: InitPluginOptions): Promise<InitPluginResult>;
|
package/dist/core/init-plugin.js
CHANGED
|
@@ -1,39 +1,186 @@
|
|
|
1
1
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
export const initPluginTemplates = [
|
|
4
|
+
"skill-only",
|
|
5
|
+
"mcp-stdio",
|
|
6
|
+
"mcp-http",
|
|
7
|
+
"full-runtime"
|
|
8
|
+
];
|
|
3
9
|
function toPackageName(inputPath) {
|
|
4
10
|
return path.basename(path.resolve(inputPath))
|
|
5
11
|
.toLowerCase()
|
|
6
12
|
.replace(/[^a-z0-9-]+/g, "-")
|
|
7
13
|
.replace(/^-+|-+$/g, "") || "codex-plugin";
|
|
8
14
|
}
|
|
9
|
-
export
|
|
15
|
+
export function isInitPluginTemplate(value) {
|
|
16
|
+
return initPluginTemplates.includes(value);
|
|
17
|
+
}
|
|
18
|
+
function buildSkillMarkdown(template) {
|
|
19
|
+
const description = template === "skill-only"
|
|
20
|
+
? "Use when verifying that this Codex plugin package loads correctly."
|
|
21
|
+
: "Use when testing this Codex plugin package and its bundled MCP server.";
|
|
22
|
+
return [
|
|
23
|
+
"---",
|
|
24
|
+
"name: hello",
|
|
25
|
+
`description: ${description}`,
|
|
26
|
+
"---",
|
|
27
|
+
"",
|
|
28
|
+
"# Hello",
|
|
29
|
+
"",
|
|
30
|
+
template === "skill-only"
|
|
31
|
+
? "This starter skill confirms the plugin package structure is valid."
|
|
32
|
+
: "This starter skill confirms the plugin package and MCP server wiring are valid.",
|
|
33
|
+
""
|
|
34
|
+
].join("\n");
|
|
35
|
+
}
|
|
36
|
+
function buildStdioMcpConfig(packageName) {
|
|
37
|
+
return `${JSON.stringify({
|
|
38
|
+
mcpServers: {
|
|
39
|
+
[packageName]: {
|
|
40
|
+
command: "node",
|
|
41
|
+
args: ["./mock-server.js"]
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}, null, 2)}\n`;
|
|
45
|
+
}
|
|
46
|
+
function buildHttpMcpConfig(packageName) {
|
|
47
|
+
return `${JSON.stringify({
|
|
48
|
+
mcpServers: {
|
|
49
|
+
[packageName]: {
|
|
50
|
+
url: "http://localhost:8787/mcp"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}, null, 2)}\n`;
|
|
54
|
+
}
|
|
55
|
+
function buildFullRuntimeServer() {
|
|
56
|
+
return [
|
|
57
|
+
"const readline = require(\"node:readline\");",
|
|
58
|
+
"",
|
|
59
|
+
"const rl = readline.createInterface({",
|
|
60
|
+
" input: process.stdin,",
|
|
61
|
+
" crlfDelay: Infinity",
|
|
62
|
+
"});",
|
|
63
|
+
"",
|
|
64
|
+
"function send(id, payload) {",
|
|
65
|
+
" process.stdout.write(`${JSON.stringify({ jsonrpc: \"2.0\", id, ...payload })}\\n`);",
|
|
66
|
+
"}",
|
|
67
|
+
"",
|
|
68
|
+
"rl.on(\"line\", (line) => {",
|
|
69
|
+
" const message = JSON.parse(line);",
|
|
70
|
+
" const cursor = message.params && message.params.cursor;",
|
|
71
|
+
"",
|
|
72
|
+
" if (message.method === \"initialize\") {",
|
|
73
|
+
" send(message.id, {",
|
|
74
|
+
" result: {",
|
|
75
|
+
" protocolVersion: \"2025-11-25\",",
|
|
76
|
+
" capabilities: { tools: {}, resources: {}, prompts: {} },",
|
|
77
|
+
" serverInfo: { name: \"codex-plugin-template\", version: \"0.1.0\" }",
|
|
78
|
+
" }",
|
|
79
|
+
" });",
|
|
80
|
+
" return;",
|
|
81
|
+
" }",
|
|
82
|
+
"",
|
|
83
|
+
" if (message.method === \"tools/list\") {",
|
|
84
|
+
" send(message.id, {",
|
|
85
|
+
" result: cursor === \"tools-page-2\"",
|
|
86
|
+
" ? { tools: [{ name: \"format_status\", description: \"Return a formatted health status.\", inputSchema: { type: \"object\", properties: {}, required: [] } }] }",
|
|
87
|
+
" : { tools: [{ name: \"ping\", description: \"Return a healthcheck response.\", inputSchema: { type: \"object\", properties: {}, required: [] } }], nextCursor: \"tools-page-2\" }",
|
|
88
|
+
" });",
|
|
89
|
+
" return;",
|
|
90
|
+
" }",
|
|
91
|
+
"",
|
|
92
|
+
" if (message.method === \"tools/call\") {",
|
|
93
|
+
" send(message.id, { result: { content: [{ type: \"text\", text: \"codex-plugin-template-ok\" }] } });",
|
|
94
|
+
" return;",
|
|
95
|
+
" }",
|
|
96
|
+
"",
|
|
97
|
+
" if (message.method === \"resources/list\") {",
|
|
98
|
+
" send(message.id, {",
|
|
99
|
+
" result: cursor === \"resources-page-2\"",
|
|
100
|
+
" ? { resources: [{ name: \"workspace-license\", uri: \"file:///workspace/LICENSE\" }] }",
|
|
101
|
+
" : { resources: [{ name: \"workspace-readme\", uri: \"file:///workspace/README.md\" }], nextCursor: \"resources-page-2\" }",
|
|
102
|
+
" });",
|
|
103
|
+
" return;",
|
|
104
|
+
" }",
|
|
105
|
+
"",
|
|
106
|
+
" if (message.method === \"resources/read\") {",
|
|
107
|
+
" send(message.id, { result: { contents: [{ uri: \"file:///workspace/README.md\", text: \"# Workspace README\" }] } });",
|
|
108
|
+
" return;",
|
|
109
|
+
" }",
|
|
110
|
+
"",
|
|
111
|
+
" if (message.method === \"resources/templates/list\") {",
|
|
112
|
+
" send(message.id, {",
|
|
113
|
+
" result: cursor === \"templates-page-2\"",
|
|
114
|
+
" ? { resourceTemplates: [{ name: \"log\", uriTemplate: \"file:///workspace/logs/{name}.log\" }] }",
|
|
115
|
+
" : { resourceTemplates: [{ name: \"doc\", uriTemplate: \"file:///workspace/docs/{name}.md\" }], nextCursor: \"templates-page-2\" }",
|
|
116
|
+
" });",
|
|
117
|
+
" return;",
|
|
118
|
+
" }",
|
|
119
|
+
"",
|
|
120
|
+
" if (message.method === \"prompts/list\") {",
|
|
121
|
+
" send(message.id, {",
|
|
122
|
+
" result: cursor === \"prompts-page-2\"",
|
|
123
|
+
" ? { prompts: [{ name: \"summary\", description: \"Summarize the current change.\" }] }",
|
|
124
|
+
" : { prompts: [{ name: \"code_review\", description: \"Review code for bugs.\", arguments: [{ name: \"diff\", required: true }] }], nextCursor: \"prompts-page-2\" }",
|
|
125
|
+
" });",
|
|
126
|
+
" return;",
|
|
127
|
+
" }",
|
|
128
|
+
"",
|
|
129
|
+
" if (message.method === \"prompts/get\") {",
|
|
130
|
+
" if (!message.params || !message.params.arguments || message.params.arguments.diff !== \"codex-plugin-doctor-probe\") {",
|
|
131
|
+
" send(message.id, { error: { code: -32602, message: \"Missing required diff argument\" } });",
|
|
132
|
+
" return;",
|
|
133
|
+
" }",
|
|
134
|
+
"",
|
|
135
|
+
" send(message.id, {",
|
|
136
|
+
" result: {",
|
|
137
|
+
" description: \"Prompt for code review\",",
|
|
138
|
+
" messages: [{ role: \"user\", content: { type: \"text\", text: \"Review this diff for bugs.\" } }]",
|
|
139
|
+
" }",
|
|
140
|
+
" });",
|
|
141
|
+
" return;",
|
|
142
|
+
" }",
|
|
143
|
+
"",
|
|
144
|
+
" send(message.id, { error: { code: -32601, message: `Unsupported method: ${message.method}` } });",
|
|
145
|
+
"});",
|
|
146
|
+
""
|
|
147
|
+
].join("\n");
|
|
148
|
+
}
|
|
149
|
+
export async function initPluginPackage(targetPath, options = {}) {
|
|
150
|
+
const template = options.template ?? "skill-only";
|
|
10
151
|
const rootPath = path.resolve(targetPath);
|
|
11
152
|
const manifestDirectory = path.join(rootPath, ".codex-plugin");
|
|
12
153
|
const skillsDirectory = path.join(rootPath, "skills", "hello");
|
|
13
154
|
const manifestPath = path.join(manifestDirectory, "plugin.json");
|
|
14
155
|
const skillPath = path.join(skillsDirectory, "SKILL.md");
|
|
156
|
+
const mcpConfigPath = path.join(rootPath, ".mcp.json");
|
|
157
|
+
const serverPath = path.join(rootPath, "mock-server.js");
|
|
158
|
+
const packageName = toPackageName(rootPath);
|
|
15
159
|
await mkdir(manifestDirectory, { recursive: true });
|
|
16
160
|
await mkdir(skillsDirectory, { recursive: true });
|
|
17
161
|
await writeFile(manifestPath, `${JSON.stringify({
|
|
18
|
-
name:
|
|
162
|
+
name: packageName,
|
|
19
163
|
version: "0.1.0",
|
|
20
164
|
description: "A Codex plugin package scaffolded by Codex Plugin Doctor.",
|
|
21
|
-
skills: "skills"
|
|
165
|
+
skills: "skills",
|
|
166
|
+
...(template === "skill-only" ? {} : { mcpServers: ".mcp.json" })
|
|
22
167
|
}, null, 2)}\n`, "utf8");
|
|
23
|
-
await writeFile(skillPath,
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
"This starter skill confirms the plugin package structure is valid.",
|
|
32
|
-
""
|
|
33
|
-
].join("\n"), "utf8");
|
|
168
|
+
await writeFile(skillPath, buildSkillMarkdown(template), "utf8");
|
|
169
|
+
if (template === "mcp-stdio" || template === "full-runtime") {
|
|
170
|
+
await writeFile(mcpConfigPath, buildStdioMcpConfig(packageName), "utf8");
|
|
171
|
+
await writeFile(serverPath, buildFullRuntimeServer(), "utf8");
|
|
172
|
+
}
|
|
173
|
+
if (template === "mcp-http") {
|
|
174
|
+
await writeFile(mcpConfigPath, buildHttpMcpConfig(packageName), "utf8");
|
|
175
|
+
}
|
|
34
176
|
return {
|
|
35
177
|
rootPath,
|
|
36
178
|
manifestPath,
|
|
37
|
-
skillPath
|
|
179
|
+
skillPath,
|
|
180
|
+
template,
|
|
181
|
+
mcpConfigPath: template === "skill-only" ? undefined : mcpConfigPath,
|
|
182
|
+
serverPath: template === "mcp-stdio" || template === "full-runtime"
|
|
183
|
+
? serverPath
|
|
184
|
+
: undefined
|
|
38
185
|
};
|
|
39
186
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { CheckResult } from "../domain/types.js";
|
|
2
2
|
import type { InstalledPlugin } from "../core/discover-installed-plugins.js";
|
|
3
|
+
import type { CompatibilityMatrix } from "../compatibility/compatibility-matrix.js";
|
|
3
4
|
export interface InstalledPluginCheckResult {
|
|
4
5
|
plugin: InstalledPlugin;
|
|
5
6
|
result: CheckResult;
|
|
7
|
+
compatibilityMatrix?: CompatibilityMatrix;
|
|
6
8
|
}
|
|
7
9
|
export declare function renderInstalledSummary(checkedPlugins: InstalledPluginCheckResult[]): string;
|
|
@@ -1,3 +1,36 @@
|
|
|
1
|
+
function statusLabel(status) {
|
|
2
|
+
return status.toUpperCase();
|
|
3
|
+
}
|
|
4
|
+
function statusCount(matrix, status) {
|
|
5
|
+
return matrix.results.filter((result) => result.status === status).length;
|
|
6
|
+
}
|
|
7
|
+
function renderCompatibilitySummary(checkedPlugins) {
|
|
8
|
+
const pluginsWithCompatibility = checkedPlugins.filter((item) => item.compatibilityMatrix);
|
|
9
|
+
if (pluginsWithCompatibility.length === 0) {
|
|
10
|
+
return [];
|
|
11
|
+
}
|
|
12
|
+
const lines = [
|
|
13
|
+
"",
|
|
14
|
+
"Installed Compatibility Summary",
|
|
15
|
+
"===============================",
|
|
16
|
+
`Checked: ${pluginsWithCompatibility.length}`,
|
|
17
|
+
"",
|
|
18
|
+
"Clients",
|
|
19
|
+
"-------"
|
|
20
|
+
];
|
|
21
|
+
for (const item of pluginsWithCompatibility) {
|
|
22
|
+
const matrix = item.compatibilityMatrix;
|
|
23
|
+
if (!matrix) {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
lines.push("", `${item.plugin.name} - ${item.plugin.relativePath}`);
|
|
27
|
+
lines.push(` Score: ${statusCount(matrix, "pass")} pass, ${statusCount(matrix, "warn")} warn, ${statusCount(matrix, "fail")} fail, ${statusCount(matrix, "skipped")} skipped`);
|
|
28
|
+
for (const result of matrix.results) {
|
|
29
|
+
lines.push(` ${result.client}: ${statusLabel(result.status)} - ${result.summary}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return lines;
|
|
33
|
+
}
|
|
1
34
|
export function renderInstalledSummary(checkedPlugins) {
|
|
2
35
|
const passCount = checkedPlugins.filter((item) => item.result.status === "pass").length;
|
|
3
36
|
const warnCount = checkedPlugins.filter((item) => item.result.status === "warn").length;
|
|
@@ -18,5 +51,6 @@ export function renderInstalledSummary(checkedPlugins) {
|
|
|
18
51
|
const suffix = findingIds.length > 0 ? ` (${findingIds.join(", ")})` : "";
|
|
19
52
|
lines.push(`${item.result.status.toUpperCase()} ${item.plugin.name} - ${item.plugin.relativePath}${suffix}`);
|
|
20
53
|
}
|
|
54
|
+
lines.push(...renderCompatibilitySummary(checkedPlugins));
|
|
21
55
|
return lines.join("\n");
|
|
22
56
|
}
|
package/dist/run-cli.d.ts
CHANGED
|
@@ -13,5 +13,6 @@ export interface CliTerminalContext {
|
|
|
13
13
|
export interface RunCliOptions {
|
|
14
14
|
terminalContext?: CliTerminalContext;
|
|
15
15
|
runCheckImpl?: typeof runCheck;
|
|
16
|
+
resolveLatestVersion?: () => Promise<string>;
|
|
16
17
|
}
|
|
17
18
|
export declare function runCli(args: string[], io?: CliIo, options?: RunCliOptions): Promise<number>;
|
package/dist/run-cli.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
1
2
|
import { writeFile } from "node:fs/promises";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { createInterface } from "node:readline/promises";
|
|
@@ -12,9 +13,9 @@ import { buildClineInstallPreview, renderClineInstallPreview } from "./compatibi
|
|
|
12
13
|
import { buildWindsurfInstallPreview, renderWindsurfInstallPreview } from "./compatibility/windsurf-install-preview.js";
|
|
13
14
|
import { applyDoctorConfig, loadDoctorConfig } from "./core/doctor-config.js";
|
|
14
15
|
import { applyFixPlan, buildFixPlan, renderApplyFixResult, renderFixPlanJsonReport, renderFixPlan } from "./core/fix-plan.js";
|
|
15
|
-
import { renderEnvironmentDoctor, renderEnvironmentDoctorJson } from "./core/environment-doctor.js";
|
|
16
|
+
import { renderClientDoctor, renderEnvironmentDoctor, renderEnvironmentDoctorJson } from "./core/environment-doctor.js";
|
|
16
17
|
import { initCiWorkflow } from "./core/init-ci.js";
|
|
17
|
-
import { initPluginPackage } from "./core/init-plugin.js";
|
|
18
|
+
import { initPluginPackage, initPluginTemplates, isInitPluginTemplate } from "./core/init-plugin.js";
|
|
18
19
|
import { runCheck } from "./index.js";
|
|
19
20
|
import { renderInstalledSummary } from "./reporting/render-installed-summary.js";
|
|
20
21
|
import { renderBadgeJson, renderBadgeMarkdown } from "./reporting/render-badge-report.js";
|
|
@@ -52,7 +53,7 @@ const defaultIo = {
|
|
|
52
53
|
}
|
|
53
54
|
};
|
|
54
55
|
function printUsage(io) {
|
|
55
|
-
io.writeStderr("Usage: codex-plugin-doctor check <path|--installed> [filter] [--json|--markdown|--badge-json|--badge-markdown] [--output <path>] [--history <path>] [--runtime] [--verbose-runtime] [--explain] [--no-animations] [--ascii]\n codex-plugin-doctor compat <path> [--all|--client <client>] [--json] [--scorecard] [--output <path>] [--install-preview|--apply --backup]\n codex-plugin-doctor fix <path> (--dry-run|--interactive --backup|--apply --backup)\n codex-plugin-doctor history <history.jsonl> [--json] [--fail-on-regression]\n codex-plugin-doctor doctor\n codex-plugin-doctor init-ci [path]\n codex-plugin-doctor self-test\n codex-plugin-doctor list --installed\n codex-plugin-doctor explain <finding-id>\n codex-plugin-doctor --version\n\nFirst run:\n codex-plugin-doctor doctor\n codex-plugin-doctor self-test\n codex-plugin-doctor init my-plugin\n codex-plugin-doctor check . --runtime --explain");
|
|
56
|
+
io.writeStderr("Usage: codex-plugin-doctor check <path|--installed> [filter] [--compat] [--json|--markdown|--badge-json|--badge-markdown] [--output <path>] [--history <path>] [--runtime] [--verbose-runtime] [--explain] [--no-animations] [--ascii]\n codex-plugin-doctor compat <path> [--all|--client <client>] [--json] [--scorecard] [--output <path>] [--install-preview|--apply --backup]\n codex-plugin-doctor fix <path> (--dry-run|--interactive --backup|--apply --backup)\n codex-plugin-doctor history <history.jsonl> [--json] [--fail-on-regression]\n codex-plugin-doctor doctor [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 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");
|
|
56
57
|
}
|
|
57
58
|
function renderInstalledPlugins(plugins) {
|
|
58
59
|
const lines = [
|
|
@@ -126,6 +127,42 @@ function renderSelfTestReport(targetPath, validationStatus, findingsCount, compa
|
|
|
126
127
|
renderCompatibilityScorecard(compatibilityMatrix)
|
|
127
128
|
].join("\n");
|
|
128
129
|
}
|
|
130
|
+
async function resolveLatestNpmVersion() {
|
|
131
|
+
return new Promise((resolve, reject) => {
|
|
132
|
+
execFile("npm", ["view", "codex-plugin-doctor", "version"], { shell: process.platform === "win32" }, (error, stdout, stderr) => {
|
|
133
|
+
if (error) {
|
|
134
|
+
reject(new Error(stderr.trim() || error.message));
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
resolve(stdout.trim());
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
function renderUpdateCheck(latestVersion) {
|
|
142
|
+
const updateAvailable = latestVersion !== packageVersion;
|
|
143
|
+
return [
|
|
144
|
+
"Codex Plugin Doctor Update Check",
|
|
145
|
+
"================================",
|
|
146
|
+
`Installed: ${packageVersion}`,
|
|
147
|
+
`Latest: ${latestVersion}`,
|
|
148
|
+
`Status: ${updateAvailable ? "UPDATE AVAILABLE" : "UP TO DATE"}`,
|
|
149
|
+
"",
|
|
150
|
+
updateAvailable
|
|
151
|
+
? "Next: npm install -g codex-plugin-doctor@latest"
|
|
152
|
+
: "Next: no update needed"
|
|
153
|
+
].join("\n");
|
|
154
|
+
}
|
|
155
|
+
function parseSelectedFixActionIndexes(answer, actionCount) {
|
|
156
|
+
if (!/^\d+(\s*,\s*\d+)*$/.test(answer)) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
const actionIndexes = [...new Set(answer.split(",").map((item) => Number(item.trim())))];
|
|
160
|
+
return actionIndexes.every((index) => Number.isInteger(index) &&
|
|
161
|
+
index >= 1 &&
|
|
162
|
+
index <= actionCount)
|
|
163
|
+
? actionIndexes
|
|
164
|
+
: null;
|
|
165
|
+
}
|
|
129
166
|
export async function runCli(args, io = defaultIo, options = {}) {
|
|
130
167
|
const [command, maybePath, ...remainingArgs] = args;
|
|
131
168
|
if (command === "--version" || command === "-v" || command === "version") {
|
|
@@ -146,6 +183,18 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
146
183
|
return 0;
|
|
147
184
|
}
|
|
148
185
|
if (command === "doctor") {
|
|
186
|
+
const doctorFlags = maybePath?.startsWith("--")
|
|
187
|
+
? [maybePath, ...remainingArgs]
|
|
188
|
+
: remainingArgs;
|
|
189
|
+
if (doctorFlags.includes("--update-check")) {
|
|
190
|
+
const latestVersion = await (options.resolveLatestVersion ?? resolveLatestNpmVersion)();
|
|
191
|
+
io.writeStdout(renderUpdateCheck(latestVersion));
|
|
192
|
+
return 0;
|
|
193
|
+
}
|
|
194
|
+
if (maybePath === "clients") {
|
|
195
|
+
io.writeStdout(await renderClientDoctor(terminalContext));
|
|
196
|
+
return 0;
|
|
197
|
+
}
|
|
149
198
|
io.writeStdout(maybePath === "--json"
|
|
150
199
|
? await renderEnvironmentDoctorJson(terminalContext)
|
|
151
200
|
: await renderEnvironmentDoctor(terminalContext));
|
|
@@ -202,14 +251,37 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
202
251
|
}
|
|
203
252
|
if (command === "init") {
|
|
204
253
|
const targetPath = maybePath && !maybePath.startsWith("--") ? maybePath : ".";
|
|
205
|
-
const
|
|
206
|
-
|
|
254
|
+
const initFlags = maybePath && maybePath.startsWith("--")
|
|
255
|
+
? [maybePath, ...remainingArgs]
|
|
256
|
+
: remainingArgs;
|
|
257
|
+
const templateIndex = initFlags.indexOf("--template");
|
|
258
|
+
const templateName = templateIndex === -1 ? "skill-only" : initFlags[templateIndex + 1];
|
|
259
|
+
if (templateIndex !== -1 && (!templateName || templateName.startsWith("--"))) {
|
|
260
|
+
io.writeStderr("Missing template after --template.");
|
|
261
|
+
return 2;
|
|
262
|
+
}
|
|
263
|
+
if (!isInitPluginTemplate(templateName)) {
|
|
264
|
+
io.writeStderr(`Unknown init template: ${templateName}. Supported templates: ${initPluginTemplates.join(", ")}.`);
|
|
265
|
+
return 2;
|
|
266
|
+
}
|
|
267
|
+
const result = await initPluginPackage(targetPath, { template: templateName });
|
|
268
|
+
const lines = [
|
|
207
269
|
"Initialized Codex plugin package",
|
|
270
|
+
`Template: ${result.template}`,
|
|
208
271
|
`Root: ${result.rootPath}`,
|
|
209
272
|
`Manifest: ${result.manifestPath}`,
|
|
210
|
-
`Skill: ${result.skillPath}
|
|
273
|
+
`Skill: ${result.skillPath}`
|
|
274
|
+
];
|
|
275
|
+
if (result.mcpConfigPath) {
|
|
276
|
+
lines.push(`MCP config: ${result.mcpConfigPath}`);
|
|
277
|
+
}
|
|
278
|
+
if (result.serverPath) {
|
|
279
|
+
lines.push(`Server: ${result.serverPath}`);
|
|
280
|
+
}
|
|
281
|
+
io.writeStdout([
|
|
282
|
+
...lines,
|
|
211
283
|
"",
|
|
212
|
-
`Next: codex-plugin-doctor check ${result.rootPath}`
|
|
284
|
+
`Next: codex-plugin-doctor check ${result.rootPath}${result.template === "full-runtime" ? " --runtime" : ""}`
|
|
213
285
|
].join("\n"));
|
|
214
286
|
return 0;
|
|
215
287
|
}
|
|
@@ -257,14 +329,17 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
257
329
|
io.writeStdout([
|
|
258
330
|
renderFixPlan(plan, "interactive"),
|
|
259
331
|
"",
|
|
260
|
-
"Type yes to apply these fixes with a backup. Anything else cancels."
|
|
332
|
+
"Type yes to apply these fixes with a backup, or enter action numbers like 1,3. Anything else cancels."
|
|
261
333
|
].join("\n"));
|
|
262
334
|
const answer = (await io.readStdin?.("Apply fixes? ") ?? "").trim().toLowerCase();
|
|
263
|
-
|
|
335
|
+
const selectedActionIndexes = answer === "yes"
|
|
336
|
+
? null
|
|
337
|
+
: parseSelectedFixActionIndexes(answer, plan.actions.length);
|
|
338
|
+
if (answer !== "yes" && !selectedActionIndexes) {
|
|
264
339
|
io.writeStdout("Fix cancelled. No files changed.");
|
|
265
340
|
return 0;
|
|
266
341
|
}
|
|
267
|
-
io.writeStdout(renderApplyFixResult(await applyFixPlan(maybePath)));
|
|
342
|
+
io.writeStdout(renderApplyFixResult(await applyFixPlan(maybePath, selectedActionIndexes ? { actionIndexes: selectedActionIndexes } : {})));
|
|
268
343
|
return 0;
|
|
269
344
|
}
|
|
270
345
|
const result = await applyFixPlan(maybePath);
|
|
@@ -420,6 +495,7 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
420
495
|
const noAnimations = normalizedFlags.includes("--no-animations");
|
|
421
496
|
const asciiMode = normalizedFlags.includes("--ascii");
|
|
422
497
|
const installedSummary = normalizedFlags.includes("--all-summary");
|
|
498
|
+
const installedCompatibility = normalizedFlags.includes("--compat");
|
|
423
499
|
const outputIndex = normalizedFlags.indexOf("--output");
|
|
424
500
|
const outputPath = outputIndex === -1 ? null : normalizedFlags[outputIndex + 1];
|
|
425
501
|
const configIndex = normalizedFlags.indexOf("--config");
|
|
@@ -480,6 +556,12 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
480
556
|
const checkedPlugins = [];
|
|
481
557
|
for (const plugin of installedPlugins) {
|
|
482
558
|
const config = await loadDoctorConfig(plugin.rootPath, configPath);
|
|
559
|
+
const compatibilityMatrix = installedCompatibility
|
|
560
|
+
? await buildCompatibilityMatrix(plugin.rootPath, {
|
|
561
|
+
env: terminalContext.env,
|
|
562
|
+
platform: terminalContext.platform
|
|
563
|
+
})
|
|
564
|
+
: undefined;
|
|
483
565
|
checkedPlugins.push({
|
|
484
566
|
plugin,
|
|
485
567
|
result: applyDoctorConfig(await runCheckImpl(plugin.rootPath, {
|
|
@@ -487,7 +569,8 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
487
569
|
runtimeTranscript: effectiveRuntimeProbeEnabled && verboseRuntime
|
|
488
570
|
? (line) => io.writeStderr(line)
|
|
489
571
|
: undefined
|
|
490
|
-
}), applyCheckProfile(config, checkProfile))
|
|
572
|
+
}), applyCheckProfile(config, checkProfile)),
|
|
573
|
+
compatibilityMatrix
|
|
491
574
|
});
|
|
492
575
|
}
|
|
493
576
|
const report = installedSummary
|
|
@@ -508,7 +591,10 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
508
591
|
await writeFile(outputPath, report, "utf8");
|
|
509
592
|
}
|
|
510
593
|
io.writeStdout(report);
|
|
511
|
-
return checkedPlugins.some((item) => item.result.exitCode === 1
|
|
594
|
+
return checkedPlugins.some((item) => item.result.exitCode === 1 ||
|
|
595
|
+
(item.compatibilityMatrix && matrixExitCode(item.compatibilityMatrix) === 1))
|
|
596
|
+
? 1
|
|
597
|
+
: 0;
|
|
512
598
|
}
|
|
513
599
|
const renderer = outputPolicy.interactive
|
|
514
600
|
&& !verboseRuntime
|
package/package.json
CHANGED