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 608d0c1c..63d6df0b 100644
--- a/src/web/public/index.html
+++ b/src/web/public/index.html
@@ -311,27 +311,19 @@
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 3dd6dde8..2974faa9 100644
--- a/src/web/public/settings-ui.js
+++ b/src/web/public/settings-ui.js
@@ -718,6 +718,31 @@ Object.assign(CodemanApp.prototype, {
this._updatePollTimer = setInterval(poll, 1500);
},
+ async loadGeminiAvailability() {
+ 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(statusUrl);
+ const env = await res.json();
+ const status = env?.success === true ? env.data : env;
+ btn.style.display = status?.available ? 'flex' : 'none';
+ } catch {
+ btn.style.display = 'none';
+ }
+ },
+
async loadTunnelStatus() {
try {
const res = await fetch('/api/tunnel/status');
diff --git a/src/web/public/terminal-ui.js b/src/web/public/terminal-ui.js
index f19f37ee..1449981c 100644
--- a/src/web/public/terminal-ui.js
+++ b/src/web/public/terminal-ui.js
@@ -1195,6 +1195,9 @@ 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 () => {