diff --git a/src/utils/claude-cli-resolver.ts b/src/utils/claude-cli-resolver.ts index b9292201..6d735fa5 100644 --- a/src/utils/claude-cli-resolver.ts +++ b/src/utils/claude-cli-resolver.ts @@ -26,6 +26,15 @@ const CLAUDE_SEARCH_DIRS = [ /** Cached directory containing the claude binary (empty string = searched but not found) */ let _claudeDir: string | null = null; +/** + * Returns true if the Claude CLI binary can be located (via `which` or one of + * the common install directories). Mirrors `isGeminiAvailable`/`isOpenCodeAvailable`/ + * `isCodexAvailable` in the sibling resolvers. + */ +export function isClaudeAvailable(): boolean { + return findClaudeDir() !== null; +} + /** * Finds the directory containing the `claude` binary. * Checks `which claude` first, then falls back to common install locations. diff --git a/src/web/public/index.html b/src/web/public/index.html index 770cffcc..a9b6a0f6 100644 --- a/src/web/public/index.html +++ b/src/web/public/index.html @@ -311,11 +311,11 @@

Codeman

Manage AI Coding tools in persistent tmux sessions.

- - diff --git a/src/web/public/settings-ui.js b/src/web/public/settings-ui.js index a8eb46fd..58a98da1 100644 --- a/src/web/public/settings-ui.js +++ b/src/web/public/settings-ui.js @@ -695,10 +695,22 @@ Object.assign(CodemanApp.prototype, { }, async loadGeminiAvailability() { - const btn = document.getElementById('welcomeGeminiBtn'); + await this._loadCliAvailability('welcomeGeminiBtn', '/api/gemini/status'); + }, + + async loadClaudeAvailability() { + await this._loadCliAvailability('welcomeClaudeBtn', '/api/claude/status'); + }, + + async loadOpencodeAvailability() { + await this._loadCliAvailability('welcomeOpencodeBtn', '/api/opencode/status'); + }, + + async _loadCliAvailability(buttonId, statusUrl) { + const btn = document.getElementById(buttonId); if (!btn) return; try { - const res = await fetch('/api/gemini/status'); + const res = await fetch(statusUrl); const env = await res.json(); const status = env?.success === true ? env.data : env; btn.style.display = status?.available ? 'flex' : 'none'; diff --git a/src/web/public/terminal-ui.js b/src/web/public/terminal-ui.js index 4fe01c19..1449981c 100644 --- a/src/web/public/terminal-ui.js +++ b/src/web/public/terminal-ui.js @@ -1195,6 +1195,8 @@ Object.assign(CodemanApp.prototype, { if (overlay) { overlay.classList.add('visible'); this.loadTunnelStatus(); + this.loadClaudeAvailability(); + this.loadOpencodeAvailability(); this.loadGeminiAvailability(); this.loadHistorySessions(); this.initSearchPanel(); diff --git a/src/web/routes/system-routes.ts b/src/web/routes/system-routes.ts index 4715ac91..64e43c76 100644 --- a/src/web/routes/system-routes.ts +++ b/src/web/routes/system-routes.ts @@ -374,9 +374,19 @@ export function registerSystemRoutes( }); // ═══════════════════════════════════════════════════════════════ - // CLI Integrations (OpenCode, Codex, Gemini, Antigravity) + // CLI Integrations (Claude, OpenCode, Codex, Gemini, Antigravity) // ═══════════════════════════════════════════════════════════════ + // ========== Claude ========== + + app.get('/api/claude/status', async () => { + const { isClaudeAvailable, findClaudeDir } = await import('../../utils/claude-cli-resolver.js'); + return { + available: isClaudeAvailable(), + path: findClaudeDir(), + }; + }); + // ========== OpenCode ========== app.get('/api/opencode/status', async () => {