fix(welcome): gate Claude and Opencode buttons on CLI availability too

Extends the Gemini gating from bb7fb9e to the other welcome-screen
buttons that had the same problem: shown unconditionally even when the
underlying CLI isn't installed.

- Add isClaudeAvailable() (claude-cli-resolver.ts) and GET
  /api/claude/status, mirroring the existing opencode/codex/gemini
  resolvers and status endpoints.
- Opencode already had a working /api/opencode/status the welcome
  screen just wasn't checking; wire it up the same way.
- Refactor loadGeminiAvailability() into a shared
  _loadCliAvailability(buttonId, statusUrl) helper instead of
  duplicating the fetch/try-catch three times.

Run-mode dropdown entries (Opencode/Codex) are intentionally left
unconditional here — follow-up PR.
This commit is contained in:
timkjr
2026-08-04 16:22:21 -05:00
parent 62008fb408
commit 3ea1ea28f0
5 changed files with 38 additions and 5 deletions
+2 -2
View File
@@ -311,11 +311,11 @@
<h1 class="welcome-title">Codeman</h1>
<p class="welcome-desc">Manage AI Coding tools in persistent tmux sessions.</p>
<div class="welcome-actions">
<button class="welcome-btn welcome-btn-claude" onclick="app.setRunMode('claude'); app.runClaude()">
<button class="welcome-btn welcome-btn-claude" id="welcomeClaudeBtn" style="display: none;" onclick="app.setRunMode('claude'); app.runClaude()">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="5 3 19 12 5 21 5 3"/></svg>
Run Claude Code
</button>
<button class="welcome-btn welcome-btn-opencode" onclick="app.setRunMode('opencode'); app.runOpenCode()">
<button class="welcome-btn welcome-btn-opencode" id="welcomeOpencodeBtn" style="display: none;" onclick="app.setRunMode('opencode'); app.runOpenCode()">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="5 3 19 12 5 21 5 3"/></svg>
Run OpenCode
</button>
+14 -2
View File
@@ -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';
+2
View File
@@ -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();
+11 -1
View File
@@ -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 () => {