Skip to content

Commit 18c28fc

Browse files
committed
feat: make doctor conversational and init welcoming
1 parent e608a51 commit 18c28fc

2 files changed

Lines changed: 36 additions & 42 deletions

File tree

src/commands/doctor.ts

Lines changed: 21 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -32,35 +32,35 @@ export async function runDoctor(options: CliOverrides): Promise<void> {
3232

3333
const results = await Promise.all([
3434
// 1. Node.js version check
35-
check('Node.js version >= 20', () => {
35+
check('Node.js', () => {
3636
const version = process.version;
3737
const major = parseInt(version.slice(1).split('.')[0] ?? '0', 10);
3838
if (major < 20) {
3939
return Promise.resolve({ status: 'fail' as const, detail: `Node.js ${version} detected — upgrade to v20 or later` });
4040
}
41-
return Promise.resolve({ status: 'pass' as const, detail: version });
41+
return Promise.resolve({ status: 'pass' as const, detail: `${version} — solid.` });
4242
}),
4343

4444
// 2. Config file parse check
45-
check('Config file is valid', async () => {
45+
check('Config', async () => {
4646
const config = await loadConfig(process.cwd(), options);
47-
return { status: 'pass' as const, detail: `Active profile: "${config.defaultProfile}"` };
47+
return { status: 'pass' as const, detail: `using "${config.defaultProfile}" profile.` };
4848
}),
4949

5050
// 3. Provider connectivity
51-
check('Provider connectivity', async () => {
51+
check('Provider', async () => {
5252
const config = await loadConfig(process.cwd(), options);
5353
const profile = getActiveProfile(config);
5454
const adapter = await createProvider(profile);
5555
await adapter.validateCredentials();
5656
const via = profile.provider === 'copilot' && process.env['JAM_VSCODE_LM_PORT']
5757
? 'copilot (via VSCode)'
5858
: profile.provider;
59-
return { status: 'pass' as const, detail: `Provider: ${via}` };
59+
return { status: 'pass' as const, detail: `${via} connected and ready.` };
6060
}),
6161

6262
// 4. Copilot CLI availability
63-
check('Copilot CLI (@github/copilot)', async () => {
63+
check('Copilot CLI', async () => {
6464
try {
6565
const { stdout } = await execFileAsync('npx', ['@github/copilot', '--version'], { timeout: 15_000 });
6666
return { status: 'pass' as const, detail: stdout.trim() };
@@ -73,46 +73,42 @@ export async function runDoctor(options: CliOverrides): Promise<void> {
7373
}),
7474

7575
// 5. ripgrep availability (optional)
76-
check('ripgrep (rg) is available', async () => {
76+
check('ripgrep', async () => {
7777
try {
7878
const { stdout } = await execFileAsync('rg', ['--version'], { timeout: 5000 });
7979
const firstLine = stdout.split('\n')[0]?.trim() ?? 'rg';
80-
return { status: 'pass' as const, detail: firstLine };
80+
return { status: 'pass' as const, detail: `${firstLine} — searches will be fast.` };
8181
} catch {
82-
return { status: 'warn' as const, detail: 'Not installed — using JavaScript-based search (slower)' };
82+
return { status: 'warn' as const, detail: 'Not installed — searches will be slower without it' };
8383
}
8484
}),
8585

8686
// 6. Keytar availability (optional)
87-
check('keytar (secure credential storage)', async () => {
87+
check('Keychain', async () => {
8888
try {
8989
await import('keytar');
90-
return { status: 'pass' as const, detail: 'loaded' };
90+
return { status: 'pass' as const, detail: 'secrets are secure.' };
9191
} catch {
9292
return {
9393
status: 'warn' as const,
94-
detail: 'Not available — API keys must be provided via environment variables or config',
94+
detail: 'Not available — use env vars for API keys',
9595
};
9696
}
9797
}),
9898
]);
9999

100-
process.stdout.write('\nJam Doctor — system diagnostics\n');
101-
process.stdout.write(chalk.dim('─'.repeat(60) + '\n\n'));
100+
process.stdout.write('\nChecking your setup...\n\n');
102101

103102
for (const result of results) {
104103
let icon: string;
105-
let label: string;
106104
if (result.status === 'pass') {
107-
icon = chalk.green('[✓]');
108-
label = chalk.green(result.description);
105+
icon = chalk.green(' ✓');
109106
} else if (result.status === 'warn') {
110-
icon = chalk.yellow('[!]');
111-
label = chalk.yellow(result.description);
107+
icon = chalk.yellow(' !');
112108
} else {
113-
icon = chalk.red('[✗]');
114-
label = chalk.red(result.description);
109+
icon = chalk.red(' ✗');
115110
}
111+
const label = result.status === 'fail' ? chalk.red(result.description) : result.description;
116112
const detail = result.detail ? chalk.dim(` — ${result.detail}`) : '';
117113
process.stdout.write(`${icon} ${label}${detail}\n`);
118114
}
@@ -123,12 +119,10 @@ export async function runDoctor(options: CliOverrides): Promise<void> {
123119
const warnCount = results.filter((r) => r.status === 'warn').length;
124120

125121
if (failCount === 0 && warnCount === 0) {
126-
process.stdout.write(chalk.green('All checks passed.\n'));
122+
process.stdout.write(chalk.green('\nYou\'re good to go.\n'));
127123
} else if (failCount === 0) {
128-
process.stdout.write(chalk.green(`All checks passed (${warnCount} optional).\n`));
124+
process.stdout.write(chalk.green(`\nLooking good. ${warnCount} optional thing${warnCount === 1 ? '' : 's'} to improve.\n`));
129125
} else {
130-
process.stdout.write(
131-
chalk.yellow(`${failCount} check${failCount === 1 ? '' : 's'} failed, ${warnCount} optional.\n`)
132-
);
126+
process.stdout.write(chalk.yellow(`\n${failCount} issue${failCount === 1 ? '' : 's'} to fix.\n`));
133127
}
134128
}

src/commands/init.ts

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -79,20 +79,20 @@ export async function runInit(options: InitOptions = {}): Promise<void> {
7979

8080
try {
8181
write('\n');
82-
write(chalk.bold(' Jam — Project Setup\n'));
83-
write(chalk.dim(' ' + '─'.repeat(50) + '\n'));
82+
write(chalk.bold(' Welcome to Jam.\n'));
83+
write('\n');
84+
write(' Let me figure out what you\'ve got...\n');
8485
write('\n');
8586

8687
// Check workspace
8788
const workspaceRoot = await getWorkspaceRoot();
8889
const gitRepo = await isGitRepo(workspaceRoot);
8990

90-
write(` Project root: ${chalk.cyan(workspaceRoot)}\n`);
91-
write(` Git repo: ${gitRepo ? chalk.green('yes') : chalk.yellow('no')}\n`);
91+
write(` Project: ${chalk.cyan(workspaceRoot)}${gitRepo ? '' : chalk.yellow(' (not a git repo)')}\n`);
9292
write('\n');
9393

9494
// Detect providers
95-
write(chalk.bold(' Detecting providers...\n'));
95+
write(' Scanning for providers...\n');
9696
write('\n');
9797

9898
const providers = await detectProviders();
@@ -108,9 +108,9 @@ export async function runInit(options: InitOptions = {}): Promise<void> {
108108
write('\n');
109109

110110
if (available.length === 0) {
111-
write(chalk.yellow(' No providers detected. You can still configure one manually.\n'));
112-
write(chalk.dim(' Start Ollama: ollama serve\n'));
113-
write(chalk.dim(' Or set an API key: export ANTHROPIC_API_KEY=sk-ant-...\n'));
111+
write(chalk.yellow(' No providers found yet. That\'s fine.\n'));
112+
write(chalk.dim(' Start Ollama: ollama serve\n'));
113+
write(chalk.dim(' Or set a key: export ANTHROPIC_API_KEY=sk-ant-...\n'));
114114
write('\n');
115115
}
116116

@@ -122,7 +122,7 @@ export async function runInit(options: InitOptions = {}): Promise<void> {
122122
// Auto-select: first available provider, or ollama as fallback
123123
selectedProvider = available.length > 0 ? available[0]!.name : 'ollama';
124124
selectedModel = DEFAULT_MODELS[selectedProvider] ?? 'llama3.2';
125-
write(` Auto-selected: ${chalk.bold(selectedProvider)} (${selectedModel})\n`);
125+
write(` → Using ${chalk.bold(selectedProvider)} (${selectedModel})\n`);
126126
} else {
127127
const rl = createInterface({ input: process.stdin, output: process.stderr });
128128

@@ -182,7 +182,7 @@ export async function runInit(options: InitOptions = {}): Promise<void> {
182182
write(chalk.yellow(` .jamrc already exists — skipping config.\n`));
183183
} else {
184184
await writeFile(configPath, JSON.stringify(configContent, null, 2) + '\n');
185-
write(` ${chalk.green('[✓]')} Created ${chalk.cyan('.jamrc')}\n`);
185+
write(` ${chalk.green('')} Created ${chalk.cyan('.jamrc')} with your settings.\n`);
186186
}
187187

188188
// Generate JAM.md
@@ -193,12 +193,12 @@ export async function runInit(options: InitOptions = {}): Promise<void> {
193193
} else {
194194
const content = await generateContextContent(workspaceRoot);
195195
const path = await writeContextFile(workspaceRoot, content);
196-
write(` ${chalk.green('[✓]')} Created ${chalk.cyan(CONTEXT_FILENAME)} at ${path}\n`);
196+
write(` ${chalk.green('')} Created ${chalk.cyan(CONTEXT_FILENAME)} for project context.\n`);
197197
}
198198

199199
// Quick connectivity check
200200
write('\n');
201-
write(chalk.bold(' Verifying setup...\n'));
201+
write(' Checking connectivity...\n');
202202
write('\n');
203203

204204
try {
@@ -209,17 +209,17 @@ export async function runInit(options: InitOptions = {}): Promise<void> {
209209
...(selectedProvider === 'ollama' ? { baseUrl: 'http://localhost:11434' } : {}),
210210
});
211211
await adapter.validateCredentials();
212-
write(` ${chalk.green('[✓]')} ${selectedProvider} is reachable\n`);
212+
write(` ${chalk.green('')} ${selectedProvider} is reachable\n`);
213213
} catch (err) {
214214
const msg = err instanceof Error ? err.message : String(err);
215-
write(` ${chalk.red('[✗]')} ${selectedProvider} connection failed: ${chalk.dim(msg)}\n`);
215+
write(` ${chalk.red('')} ${selectedProvider} connection failed: ${chalk.dim(msg)}\n`);
216216
write(chalk.dim(' You can fix this later and run: jam doctor\n'));
217217
}
218218

219219
write('\n');
220220
write(chalk.dim(' ' + '─'.repeat(50) + '\n'));
221221
write('\n');
222-
await printSuccess(' Setup complete! Try: jam ask "hello"');
222+
await printSuccess(` Ready. Try: ${chalk.cyan('jam ask "how does this project work?"')}`);
223223
write('\n');
224224

225225
} catch (err) {

0 commit comments

Comments
 (0)