feat: add OpenCode CLI support with status endpoint

- Implemented OpenCode CLI installation and authentication status check.
- Added new route for OpenCode status in setup routes.
- Updated HttpApiClient to include method for fetching OpenCode status.
- Enhanced system paths to include OpenCode's default installation directories.

This commit introduces functionality to check the installation and authentication status of the OpenCode CLI, improving integration with the overall system.
This commit is contained in:
webdevcody
2026-01-08 23:15:35 -05:00
parent 55c2530d5a
commit be88a07329
5 changed files with 94 additions and 0 deletions

View File

@@ -180,18 +180,21 @@ export class OpencodeProvider extends CliProvider {
npxPackage: 'opencode-ai@latest',
commonPaths: {
linux: [
path.join(os.homedir(), '.opencode/bin/opencode'),
path.join(os.homedir(), '.npm-global/bin/opencode'),
'/usr/local/bin/opencode',
'/usr/bin/opencode',
path.join(os.homedir(), '.local/bin/opencode'),
],
darwin: [
path.join(os.homedir(), '.opencode/bin/opencode'),
path.join(os.homedir(), '.npm-global/bin/opencode'),
'/usr/local/bin/opencode',
'/opt/homebrew/bin/opencode',
path.join(os.homedir(), '.local/bin/opencode'),
],
win32: [
path.join(os.homedir(), '.opencode', 'bin', 'opencode.exe'),
path.join(os.homedir(), 'AppData', 'Roaming', 'npm', 'opencode.cmd'),
path.join(os.homedir(), 'AppData', 'Roaming', 'npm', 'opencode'),
path.join(process.env.APPDATA || '', 'npm', 'opencode.cmd'),

View File

@@ -17,6 +17,7 @@ import { createCursorStatusHandler } from './routes/cursor-status.js';
import { createCodexStatusHandler } from './routes/codex-status.js';
import { createInstallCodexHandler } from './routes/install-codex.js';
import { createAuthCodexHandler } from './routes/auth-codex.js';
import { createOpencodeStatusHandler } from './routes/opencode-status.js';
import {
createGetCursorConfigHandler,
createSetCursorDefaultModelHandler,
@@ -49,6 +50,9 @@ export function createSetupRoutes(): Router {
router.get('/codex-status', createCodexStatusHandler());
router.post('/install-codex', createInstallCodexHandler());
router.post('/auth-codex', createAuthCodexHandler());
// OpenCode CLI routes
router.get('/opencode-status', createOpencodeStatusHandler());
router.get('/cursor-config', createGetCursorConfigHandler());
router.post('/cursor-config/default-model', createSetCursorDefaultModelHandler());
router.post('/cursor-config/models', createSetCursorModelsHandler());

View File

@@ -0,0 +1,57 @@
/**
* GET /opencode-status endpoint - Get OpenCode CLI installation and auth status
*/
import type { Request, Response } from 'express';
import { OpencodeProvider } from '../../../providers/opencode-provider.js';
import { getErrorMessage, logError } from '../common.js';
/**
* Creates handler for GET /api/setup/opencode-status
* Returns OpenCode CLI installation and authentication status
*/
export function createOpencodeStatusHandler() {
const installCommand = 'curl -fsSL https://opencode.ai/install | bash';
const loginCommand = 'opencode auth';
return async (_req: Request, res: Response): Promise<void> => {
try {
const provider = new OpencodeProvider();
const status = await provider.detectInstallation();
// Derive auth method from authenticated status and API key presence
let authMethod = 'none';
if (status.authenticated) {
authMethod = status.hasApiKey ? 'api_key_env' : 'cli_authenticated';
}
res.json({
success: true,
installed: status.installed,
version: status.version || null,
path: status.path || null,
auth: {
authenticated: status.authenticated || false,
method: authMethod,
hasApiKey: status.hasApiKey || false,
hasEnvApiKey: !!process.env.ANTHROPIC_API_KEY || !!process.env.OPENAI_API_KEY,
hasOAuthToken: false, // OpenCode doesn't use OAuth
},
recommendation: status.installed
? undefined
: 'Install OpenCode CLI to use multi-provider AI models.',
installCommands: {
macos: installCommand,
linux: installCommand,
npm: 'npm install -g opencode-ai',
},
});
} catch (error) {
logError(error, 'Get OpenCode status failed');
res.status(500).json({
success: false,
error: getErrorMessage(error),
});
}
};
}

View File

@@ -1284,6 +1284,32 @@ export class HttpApiClient implements ElectronAPI {
error?: string;
}> => this.post('/api/setup/verify-codex-auth', { authMethod, apiKey }),
// OpenCode CLI methods
getOpencodeStatus: (): Promise<{
success: boolean;
status?: string;
installed?: boolean;
method?: string;
version?: string;
path?: string;
recommendation?: string;
installCommands?: {
macos?: string;
linux?: string;
npm?: string;
};
auth?: {
authenticated: boolean;
method: string;
hasAuthFile?: boolean;
hasOAuthToken?: boolean;
hasApiKey?: boolean;
hasStoredApiKey?: boolean;
hasEnvApiKey?: boolean;
};
error?: string;
}> => this.get('/api/setup/opencode-status'),
onInstallProgress: (callback: (progress: unknown) => void) => {
return this.subscribeToEvent('agent:stream', callback);
},

View File

@@ -1034,6 +1034,8 @@ export function getOpenCodeCliPaths(): string[] {
const appData = process.env.APPDATA || path.join(homeDir, 'AppData', 'Roaming');
const localAppData = process.env.LOCALAPPDATA || path.join(homeDir, 'AppData', 'Local');
return [
// OpenCode's default installation directory
path.join(homeDir, '.opencode', 'bin', 'opencode.exe'),
path.join(homeDir, '.local', 'bin', 'opencode.exe'),
path.join(appData, 'npm', 'opencode.cmd'),
path.join(appData, 'npm', 'opencode'),
@@ -1060,6 +1062,8 @@ export function getOpenCodeCliPaths(): string[] {
const pnpmHome = process.env.PNPM_HOME || path.join(homeDir, '.local', 'share', 'pnpm');
return [
// OpenCode's default installation directory
path.join(homeDir, '.opencode', 'bin', 'opencode'),
// Standard locations
path.join(homeDir, '.local', 'bin', 'opencode'),
'/opt/homebrew/bin/opencode',