mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-08 16:39:42 +02:00
fix(run-mode): gate dropdown entries on CLI availability
Follow-up to the welcome-screen gating (#200): the run-mode dropdown (gear menu next to Run) had the same problem — Claude/Opencode/Codex/ Gemini entries were always shown regardless of whether the CLI is actually installed, so picking one could spawn a session that immediately errors out. - Add _refreshRunModeAvailability() (session-ui.js), called each time the dropdown opens; hides entries whose /api/<cli>/status reports unavailable. - Shell is intentionally never gated (no external CLI dependency). Depends on isClaudeAvailable()/GET /api/claude/status, which don't exist on upstream/master yet — duplicated here from #200 so this PR is self-contained and independently mergeable. Once #200 lands this branch should be rebased onto master, which will collapse the duplicate cleanly.
This commit is contained in:
@@ -26,6 +26,15 @@ const CLAUDE_SEARCH_DIRS = [
|
|||||||
/** Cached directory containing the claude binary (empty string = searched but not found) */
|
/** Cached directory containing the claude binary (empty string = searched but not found) */
|
||||||
let _claudeDir: string | null = null;
|
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.
|
* Finds the directory containing the `claude` binary.
|
||||||
* Checks `which claude` first, then falls back to common install locations.
|
* Checks `which claude` first, then falls back to common install locations.
|
||||||
|
|||||||
@@ -441,6 +441,7 @@ Object.assign(CodemanApp.prototype, {
|
|||||||
// Load history sessions when menu opens
|
// Load history sessions when menu opens
|
||||||
if (menu.classList.contains('active')) {
|
if (menu.classList.contains('active')) {
|
||||||
this._loadRunModeHistory();
|
this._loadRunModeHistory();
|
||||||
|
this._refreshRunModeAvailability();
|
||||||
const close = (ev) => {
|
const close = (ev) => {
|
||||||
if (!menu.contains(ev.target)) {
|
if (!menu.contains(ev.target)) {
|
||||||
menu.classList.remove('active');
|
menu.classList.remove('active');
|
||||||
@@ -451,6 +452,32 @@ Object.assign(CodemanApp.prototype, {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hides run-mode dropdown entries for CLIs that aren't installed, so
|
||||||
|
* picking one doesn't spawn a session that immediately errors out.
|
||||||
|
* Shell has no external CLI dependency and is never gated.
|
||||||
|
*/
|
||||||
|
async _refreshRunModeAvailability() {
|
||||||
|
const checks = [
|
||||||
|
['claude', '/api/claude/status'],
|
||||||
|
['opencode', '/api/opencode/status'],
|
||||||
|
['codex', '/api/codex/status'],
|
||||||
|
['gemini', '/api/gemini/status'],
|
||||||
|
];
|
||||||
|
await Promise.all(checks.map(async ([mode, url]) => {
|
||||||
|
const btn = document.querySelector(`.run-mode-option[data-mode="${mode}"]`);
|
||||||
|
if (!btn) return;
|
||||||
|
try {
|
||||||
|
const res = await fetch(url);
|
||||||
|
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 _loadRunModeHistory() {
|
async _loadRunModeHistory() {
|
||||||
const container = document.getElementById('runModeHistory');
|
const container = document.getElementById('runModeHistory');
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
|
|||||||
@@ -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 ==========
|
// ========== OpenCode ==========
|
||||||
|
|
||||||
app.get('/api/opencode/status', async () => {
|
app.get('/api/opencode/status', async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user