Skip to content

Commit 7716d6c

Browse files
committed
feat: integrate skill installation prompt into CLI workflow
1 parent 589373d commit 7716d6c

File tree

3 files changed

+77
-0
lines changed

3 files changed

+77
-0
lines changed

packages/react-doctor/src/cli.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { loadConfig } from "./utils/load-config.js";
1515
import { logger } from "./utils/logger.js";
1616
import { clearSelectBanner, prompts, setSelectBanner } from "./utils/prompts.js";
1717
import { selectProjects } from "./utils/select-projects.js";
18+
import { maybePromptSkillInstall } from "./utils/skill-prompt.js";
1819

1920
const VERSION = process.env.VERSION ?? "0.0.0";
2021

@@ -190,6 +191,7 @@ const program = new Command()
190191
}
191192

192193
if (!isScoreOnly && !shouldSkipAmiPrompts && !flags.fix) {
194+
await maybePromptSkillInstall(shouldSkipAmiPrompts);
193195
const estimatedScoreResult = flags.offline
194196
? null
195197
: await fetchEstimatedScore(allDiagnostics);

packages/react-doctor/src/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ export const SHARE_BASE_URL = "https://www.react.doctor/share";
2626

2727
export const OPEN_BASE_URL = "https://www.react.doctor/open";
2828

29+
export const INSTALL_SKILL_URL = "https://www.react.doctor/install-skill";
30+
2931
export const GIT_LS_FILES_MAX_BUFFER_BYTES = 50 * 1024 * 1024;
3032

3133
export const OFFLINE_MESSAGE =
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { execSync } from "node:child_process";
2+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3+
import { homedir } from "node:os";
4+
import { join } from "node:path";
5+
import { INSTALL_SKILL_URL } from "../constants.js";
6+
import { highlighter } from "./highlighter.js";
7+
import { logger } from "./logger.js";
8+
import { prompts } from "./prompts.js";
9+
10+
const CONFIG_DIRECTORY = join(homedir(), ".react-doctor");
11+
const CONFIG_FILE = join(CONFIG_DIRECTORY, "config.json");
12+
13+
interface SkillPromptConfig {
14+
skillPromptDismissed?: boolean;
15+
}
16+
17+
const readSkillPromptConfig = (): SkillPromptConfig => {
18+
try {
19+
if (!existsSync(CONFIG_FILE)) return {};
20+
return JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
21+
} catch {
22+
return {};
23+
}
24+
};
25+
26+
const writeSkillPromptConfig = (config: SkillPromptConfig): void => {
27+
try {
28+
if (!existsSync(CONFIG_DIRECTORY)) {
29+
mkdirSync(CONFIG_DIRECTORY, { recursive: true });
30+
}
31+
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
32+
} catch {}
33+
};
34+
35+
const installSkill = (): void => {
36+
try {
37+
execSync(`curl -fsSL ${INSTALL_SKILL_URL} | bash`, { stdio: "inherit" });
38+
} catch {
39+
logger.break();
40+
logger.dim("Skill install failed. You can install manually:");
41+
logger.dim(` curl -fsSL ${INSTALL_SKILL_URL} | bash`);
42+
}
43+
};
44+
45+
export const maybePromptSkillInstall = async (shouldSkipPrompts: boolean): Promise<void> => {
46+
const config = readSkillPromptConfig();
47+
if (config.skillPromptDismissed) return;
48+
if (shouldSkipPrompts) return;
49+
50+
logger.break();
51+
logger.log(
52+
`${highlighter.info("💡")} Have your coding agent fix these issues automatically?`,
53+
);
54+
logger.dim(
55+
` Install the ${highlighter.info("react-doctor")} skill to teach Cursor, Claude Code,`,
56+
);
57+
logger.dim(" Ami, and other AI agents how to diagnose and fix React issues.");
58+
logger.break();
59+
60+
const { shouldInstall } = await prompts({
61+
type: "confirm",
62+
name: "shouldInstall",
63+
message: "Install skill? (recommended)",
64+
initial: true,
65+
});
66+
67+
if (shouldInstall) {
68+
logger.break();
69+
installSkill();
70+
}
71+
72+
writeSkillPromptConfig({ ...config, skillPromptDismissed: true });
73+
};

0 commit comments

Comments
 (0)