react-doctor 0.0.44 → 0.0.45

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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/dist/cli.js +46 -74
  3. package/package.json +2 -1
package/README.md CHANGED
@@ -48,7 +48,7 @@ npx -y react-doctor@latest install
48
48
 
49
49
  You'll be prompted to pick which detected agents to install for. Pass `--yes` to skip prompts and install for every detected agent.
50
50
 
51
- Supports Claude Code, Codex, GitHub Copilot, Gemini CLI, Cursor, OpenCode, Factory Droid, and Pi.
51
+ Supports 50+ coding agents via [`agent-install`](https://www.npmjs.com/package/agent-install), including Claude Code, Codex, Cursor, Factory Droid, Gemini CLI, GitHub Copilot, Goose, OpenCode, Pi, Windsurf, Roo Code, Cline, Kilo Code, Warp, Replit, OpenHands, Continue, and many more. Detection is the union of CLI binaries on `$PATH` and config dirs in `$HOME` (`~/.claude`, `~/.cursor`, `~/.codex`, `~/.factory`, `~/.pi`, etc.).
52
52
 
53
53
  ## GitHub Actions
54
54
 
package/dist/cli.js CHANGED
@@ -1,10 +1,11 @@
1
1
  import { createRequire } from "node:module";
2
- import fs, { accessSync, constants, cpSync, existsSync, mkdirSync, mkdtempSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
2
+ import fs, { accessSync, constants, existsSync, mkdirSync, mkdtempSync, readdirSync, statSync, writeFileSync } from "node:fs";
3
3
  import os, { tmpdir } from "node:os";
4
4
  import path, { join } from "node:path";
5
5
  import { performance } from "node:perf_hooks";
6
6
  import { Command } from "commander";
7
7
  import { fileURLToPath } from "node:url";
8
+ import { SKILL_MANIFEST_FILE, detectInstalledSkillAgents, getSkillAgentConfig, getSkillAgentTypes, installSkillsFromSource } from "agent-install";
8
9
  import pc from "picocolors";
9
10
  import basePrompts from "prompts";
10
11
  import ora from "ora";
@@ -43,54 +44,21 @@ const IGNORED_DIRECTORIES = new Set([
43
44
  "coverage"
44
45
  ]);
45
46
  const CANONICAL_GITHUB_URL = "https://github.com/millionco/react-doctor";
47
+ const SKILL_NAME = "react-doctor";
46
48
  const PROXY_OUTPUT_MAX_BYTES = 50 * 1024 * 1024;
47
49
  const buildNoReactDependencyError = (directory) => `No React dependency found in ${directory}/package.json. Add "react" to dependencies (or peerDependencies) and re-run.`;
48
50
  //#endregion
49
51
  //#region src/utils/detect-agents.ts
50
- const AGENTS_SKILL_DIR = ".agents/skills";
51
- const SUPPORTED_AGENTS = {
52
- claude: {
53
- binaries: ["claude"],
54
- displayName: "Claude Code",
55
- skillDir: ".claude/skills"
56
- },
57
- codex: {
58
- binaries: ["codex"],
59
- displayName: "Codex",
60
- skillDir: AGENTS_SKILL_DIR
61
- },
62
- copilot: {
63
- binaries: ["copilot"],
64
- displayName: "GitHub Copilot",
65
- skillDir: AGENTS_SKILL_DIR
66
- },
67
- gemini: {
68
- binaries: ["gemini"],
69
- displayName: "Gemini CLI",
70
- skillDir: AGENTS_SKILL_DIR
71
- },
72
- cursor: {
73
- binaries: ["cursor", "agent"],
74
- displayName: "Cursor",
75
- skillDir: AGENTS_SKILL_DIR
76
- },
77
- opencode: {
78
- binaries: ["opencode"],
79
- displayName: "OpenCode",
80
- skillDir: AGENTS_SKILL_DIR
81
- },
82
- droid: {
83
- binaries: ["droid"],
84
- displayName: "Factory Droid",
85
- skillDir: ".factory/skills"
86
- },
87
- pi: {
88
- binaries: ["pi", "omegon"],
89
- displayName: "Pi",
90
- skillDir: AGENTS_SKILL_DIR
91
- }
52
+ const PATH_BINARIES = {
53
+ "claude-code": ["claude"],
54
+ codex: ["codex"],
55
+ cursor: ["cursor", "agent"],
56
+ droid: ["droid"],
57
+ "gemini-cli": ["gemini"],
58
+ "github-copilot": ["copilot"],
59
+ opencode: ["opencode"],
60
+ pi: ["pi", "omegon"]
92
61
  };
93
- const ALL_SUPPORTED_AGENTS = Object.keys(SUPPORTED_AGENTS);
94
62
  const isCommandAvailable = (command) => {
95
63
  const pathDirectories = (process.env.PATH ?? "").split(path.delimiter).filter(Boolean);
96
64
  for (const directory of pathDirectories) {
@@ -104,9 +72,15 @@ const isCommandAvailable = (command) => {
104
72
  }
105
73
  return false;
106
74
  };
107
- const detectAvailableAgents = () => ALL_SUPPORTED_AGENTS.filter((agent) => SUPPORTED_AGENTS[agent].binaries.some(isCommandAvailable));
108
- const toDisplayName = (agent) => SUPPORTED_AGENTS[agent].displayName;
109
- const toSkillDir = (agent) => SUPPORTED_AGENTS[agent].skillDir;
75
+ const detectPathAvailableAgents = () => {
76
+ const detected = [];
77
+ for (const [agent, binaries] of Object.entries(PATH_BINARIES)) if (binaries.some(isCommandAvailable)) detected.push(agent);
78
+ return detected;
79
+ };
80
+ const detectAvailableAgents = async () => {
81
+ const detected = new Set([...detectPathAvailableAgents(), ...await detectInstalledSkillAgents()]);
82
+ return getSkillAgentTypes().filter((agent) => agent !== "universal" && detected.has(agent));
83
+ };
110
84
  //#endregion
111
85
  //#region src/utils/highlighter.ts
112
86
  const highlighter = {
@@ -117,19 +91,6 @@ const highlighter = {
117
91
  dim: pc.dim
118
92
  };
119
93
  //#endregion
120
- //#region src/utils/install-skill-for-agent.ts
121
- const installSkillForAgent = (projectRoot, agent, skillSourceDirectory, skillName, alreadyInstalledDirectories) => {
122
- const installedSkillDirectory = path.join(projectRoot, toSkillDir(agent), skillName);
123
- if (alreadyInstalledDirectories?.has(installedSkillDirectory)) return installedSkillDirectory;
124
- rmSync(installedSkillDirectory, {
125
- recursive: true,
126
- force: true
127
- });
128
- mkdirSync(path.dirname(installedSkillDirectory), { recursive: true });
129
- cpSync(skillSourceDirectory, installedSkillDirectory, { recursive: true });
130
- return installedSkillDirectory;
131
- };
132
- //#endregion
133
94
  //#region src/utils/logger.ts
134
95
  let isSilent$1 = false;
135
96
  const setLoggerSilent = (silent) => {
@@ -274,31 +235,34 @@ const spinner = (text) => ({ start() {
274
235
  return handle;
275
236
  } });
276
237
  //#endregion
238
+ //#region src/utils/to-display-name.ts
239
+ const toDisplayName = (agent) => getSkillAgentConfig(agent).displayName;
240
+ //#endregion
277
241
  //#region src/install-skill.ts
278
- const SKILL_NAME = "react-doctor";
279
242
  const getSkillSourceDirectory = () => {
280
243
  const distDirectory = path.dirname(fileURLToPath(import.meta.url));
281
244
  return path.join(distDirectory, "skills", SKILL_NAME);
282
245
  };
283
246
  const runInstallSkill = async (options = {}) => {
284
- const projectRoot = process.cwd();
285
- const sourceDir = getSkillSourceDirectory();
286
- if (!existsSync(path.join(sourceDir, "SKILL.md"))) {
247
+ const projectRoot = options.projectRoot ?? process.cwd();
248
+ const sourceDir = options.sourceDir ?? getSkillSourceDirectory();
249
+ if (!existsSync(path.join(sourceDir, SKILL_MANIFEST_FILE))) {
287
250
  logger.error(`Could not locate the ${SKILL_NAME} skill bundled with this package.`);
288
251
  process.exitCode = 1;
289
252
  return;
290
253
  }
291
- const detectedAgents = detectAvailableAgents();
254
+ const detectedAgents = options.detectedAgents ?? await detectAvailableAgents();
292
255
  if (detectedAgents.length === 0) {
293
- logger.error("No supported coding agents detected on your PATH.");
294
- logger.dim(" Supported: Claude Code, Codex, GitHub Copilot, Gemini CLI, Cursor, OpenCode, Factory Droid, Pi.");
256
+ logger.error("No supported coding agents detected.");
257
+ logger.dim(" Looked for binaries on PATH (claude, codex, cursor, droid, gemini, copilot, opencode, pi)");
258
+ logger.dim(" and config dirs in $HOME (~/.claude, ~/.cursor, ~/.codex, ~/.gemini, ...).");
295
259
  process.exitCode = 1;
296
260
  return;
297
261
  }
298
262
  const selectedAgents = Boolean(options.yes) || !process.stdin.isTTY ? detectedAgents : (await prompts({
299
263
  type: "multiselect",
300
264
  name: "agents",
301
- message: `Install the ${highlighter.info(SKILL_NAME)} skill for:`,
265
+ message: `Install the ${highlighter.info("react-doctor")} skill for:`,
302
266
  choices: detectedAgents.map((agent) => ({
303
267
  title: toDisplayName(agent),
304
268
  value: agent,
@@ -315,12 +279,20 @@ const runInstallSkill = async (options = {}) => {
315
279
  return;
316
280
  }
317
281
  const installSpinner = spinner(`Installing ${SKILL_NAME} skill...`).start();
318
- const installedDirectories = /* @__PURE__ */ new Set();
319
- for (const agent of selectedAgents) {
320
- const installedDirectory = installSkillForAgent(projectRoot, agent, sourceDir, SKILL_NAME, installedDirectories);
321
- installedDirectories.add(installedDirectory);
282
+ try {
283
+ const installResult = await installSkillsFromSource({
284
+ source: sourceDir,
285
+ agents: selectedAgents,
286
+ cwd: projectRoot,
287
+ mode: "copy"
288
+ });
289
+ if (installResult.skills.length === 0) throw new Error(`Could not parse ${SKILL_MANIFEST_FILE} for ${SKILL_NAME} (missing or invalid frontmatter).`);
290
+ if (installResult.failed.length > 0) throw new Error(installResult.failed.map((failure) => `${toDisplayName(failure.agent)}: ${failure.error}`).join("\n"));
291
+ installSpinner.succeed(`${SKILL_NAME} skill installed for ${selectedAgents.map(toDisplayName).join(", ")}.`);
292
+ } catch (error) {
293
+ installSpinner.fail(`Failed to install ${SKILL_NAME} skill.`);
294
+ throw error;
322
295
  }
323
- installSpinner.succeed(`${SKILL_NAME} skill installed for ${selectedAgents.map(toDisplayName).join(", ")}.`);
324
296
  };
325
297
  //#endregion
326
298
  //#region src/core/calculate-score-locally.ts
@@ -3358,7 +3330,7 @@ const promptProjectSelection = async (workspacePackages, rootDirectory) => {
3358
3330
  };
3359
3331
  //#endregion
3360
3332
  //#region src/cli.ts
3361
- const VERSION = "0.0.44";
3333
+ const VERSION = "0.0.45";
3362
3334
  const VALID_FAIL_ON_LEVELS = new Set([
3363
3335
  "error",
3364
3336
  "warning",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-doctor",
3
- "version": "0.0.44",
3
+ "version": "0.0.45",
4
4
  "description": "Diagnose and fix React codebases for security, performance, correctness, accessibility, bundle-size, and architecture issues",
5
5
  "keywords": [
6
6
  "accessibility",
@@ -62,6 +62,7 @@
62
62
  }
63
63
  },
64
64
  "dependencies": {
65
+ "agent-install": "0.0.4",
65
66
  "commander": "^14.0.3",
66
67
  "knip": "^6.3.1",
67
68
  "ora": "^9.3.0",