feat: auto-detect gemini binary path, LLM health check in status
- Setup wizard auto-detects gemini binary via `which`, saves full path so systemd service can find it without user PATH - `mcpctl status` tests LLM provider health (gemini: quick prompt test, ollama: health check, API providers: key stored confirmation) - Shows error details inline: "gemini-cli / gemini-2.5-flash (not authenticated)" Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -42,6 +42,7 @@ function buildDeps(overrides: {
|
||||
secrets?: Record<string, string>;
|
||||
answers?: unknown[];
|
||||
fetchModels?: ConfigSetupDeps['fetchModels'];
|
||||
whichBinary?: ConfigSetupDeps['whichBinary'];
|
||||
} = {}): ConfigSetupDeps {
|
||||
return {
|
||||
configDeps: { configDir: tempDir },
|
||||
@@ -49,6 +50,7 @@ function buildDeps(overrides: {
|
||||
log: (...args: string[]) => logs.push(args.join(' ')),
|
||||
prompt: mockPrompt(overrides.answers ?? []),
|
||||
fetchModels: overrides.fetchModels ?? vi.fn(async () => []),
|
||||
whichBinary: overrides.whichBinary ?? vi.fn(async () => '/usr/bin/gemini'),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -76,26 +78,49 @@ describe('config setup wizard', () => {
|
||||
});
|
||||
|
||||
describe('provider: gemini-cli', () => {
|
||||
it('saves gemini-cli with selected model', async () => {
|
||||
// Answers: select provider, select model, confirm custom binary=false
|
||||
const deps = buildDeps({ answers: ['gemini-cli', 'gemini-2.5-flash', false] });
|
||||
it('auto-detects binary path and saves config', async () => {
|
||||
// Answers: select provider, select model (no binary prompt — auto-detected)
|
||||
const deps = buildDeps({
|
||||
answers: ['gemini-cli', 'gemini-2.5-flash'],
|
||||
whichBinary: vi.fn(async () => '/home/user/.npm-global/bin/gemini'),
|
||||
});
|
||||
await runSetup(deps);
|
||||
|
||||
const config = readConfig();
|
||||
expect((config.llm as Record<string, unknown>).provider).toBe('gemini-cli');
|
||||
expect((config.llm as Record<string, unknown>).model).toBe('gemini-2.5-flash');
|
||||
const llm = config.llm as Record<string, unknown>;
|
||||
expect(llm.provider).toBe('gemini-cli');
|
||||
expect(llm.model).toBe('gemini-2.5-flash');
|
||||
expect(llm.binaryPath).toBe('/home/user/.npm-global/bin/gemini');
|
||||
expect(logs.some((l) => l.includes('Found gemini at'))).toBe(true);
|
||||
cleanup();
|
||||
});
|
||||
|
||||
it('saves gemini-cli with custom model and binary path', async () => {
|
||||
// Answers: select provider, select custom, enter model name, confirm custom binary=true, enter path
|
||||
const deps = buildDeps({ answers: ['gemini-cli', '__custom__', 'gemini-3.0-flash', true, '/opt/gemini'] });
|
||||
it('prompts for manual path when binary not found', async () => {
|
||||
// Answers: select provider, select model, enter manual path
|
||||
const deps = buildDeps({
|
||||
answers: ['gemini-cli', 'gemini-2.5-flash', '/opt/gemini'],
|
||||
whichBinary: vi.fn(async () => null),
|
||||
});
|
||||
await runSetup(deps);
|
||||
|
||||
const config = readConfig();
|
||||
const llm = config.llm as Record<string, unknown>;
|
||||
expect(llm.binaryPath).toBe('/opt/gemini');
|
||||
expect(logs.some((l) => l.includes('not found'))).toBe(true);
|
||||
cleanup();
|
||||
});
|
||||
|
||||
it('saves gemini-cli with custom model', async () => {
|
||||
// Answers: select provider, select custom, enter model name
|
||||
const deps = buildDeps({
|
||||
answers: ['gemini-cli', '__custom__', 'gemini-3.0-flash'],
|
||||
whichBinary: vi.fn(async () => '/usr/bin/gemini'),
|
||||
});
|
||||
await runSetup(deps);
|
||||
|
||||
const config = readConfig();
|
||||
const llm = config.llm as Record<string, unknown>;
|
||||
expect(llm.model).toBe('gemini-3.0-flash');
|
||||
expect(llm.binaryPath).toBe('/opt/gemini');
|
||||
cleanup();
|
||||
});
|
||||
});
|
||||
@@ -250,7 +275,7 @@ describe('config setup wizard', () => {
|
||||
|
||||
describe('output messages', () => {
|
||||
it('shows restart instruction', async () => {
|
||||
const deps = buildDeps({ answers: ['gemini-cli', 'gemini-2.5-flash', false] });
|
||||
const deps = buildDeps({ answers: ['gemini-cli', 'gemini-2.5-flash'] });
|
||||
await runSetup(deps);
|
||||
|
||||
expect(logs.some((l) => l.includes('systemctl --user restart mcplocal'))).toBe(true);
|
||||
@@ -258,7 +283,7 @@ describe('config setup wizard', () => {
|
||||
});
|
||||
|
||||
it('shows configured provider and model', async () => {
|
||||
const deps = buildDeps({ answers: ['gemini-cli', 'gemini-2.5-flash', false] });
|
||||
const deps = buildDeps({ answers: ['gemini-cli', 'gemini-2.5-flash'] });
|
||||
await runSetup(deps);
|
||||
|
||||
expect(logs.some((l) => l.includes('gemini-cli') && l.includes('gemini-2.5-flash'))).toBe(true);
|
||||
|
||||
Reference in New Issue
Block a user