mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 12:39:42 +02:00
fix(cli-registry): toggling a CLI off in Settings never hid it anywhere else
window.__codemanCliAvailable — the flag isCliAvailable() reads client-side to gate the welcome-screen buttons, the Run-menu dropdown and the mobile overview — was built purely from each CLI's own installed-on-PATH resolver (isClaudeAvailable() etc.), with no reference to the registry's `enabled` flag at all. So disabling a CLI via the new Settings UI (or a hand-edited clis.json) updated the settings row and nothing else: every launch surface kept offering it, both live and after a full page reload, since even a fresh render never consulted the registry. Fixed in two places: - server.ts: after building `available`, intersect the nine real SessionMode ids against `enabledClis()`. git/cloudflared (utility binaries, not CLI registry entries) and deepseekBinary (a secondary installed-only flag for the "add a profile" affordance) are deliberately left alone. - settings-ui.js: `toggleCliEnabled()` now patches `window.__codemanCliAvailable` in place and refreshes the welcome screen, the mobile overview and an already-open Run menu, mirroring the existing `installDeepSeekProfile()` pattern for the same "injected once, needs an explicit patch" reason — without this half, the server-side fix alone still left every surface stale until the next reload. New test in test/render-index-html.test.ts: an installed-but-disabled CLI (codex, forced via clis.json + reloadCliRegistry()) reads as unavailable, while an installed-and-enabled one (claude) is unaffected by the override. Verified on the Debian devbox (codeman-devbox, real tmux — this sandbox has none and WebServer's constructor hard-requires it): typecheck clean, the new test passes (17/17 in render-index-html.test.ts), the CLI-registry suites pass (86/86), and the full CI gate is green (415 test files, 7855 tests, 0 failures). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N6eadpRyqpA9PD3i139cSD
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
db4557d94b
commit
0c77dd0a18
@@ -2810,6 +2810,13 @@ Object.assign(CodemanApp.prototype, {
|
||||
.join('');
|
||||
},
|
||||
|
||||
/**
|
||||
* ⚠️ A successful toggle must patch `window.__codemanCliAvailable` and refresh
|
||||
* every surface that reads it, or the change is invisible everywhere except
|
||||
* this settings row until the next full page reload — `window.__codemanCliAvailable`
|
||||
* is injected ONCE at initial page render (server.ts) and nothing else refetches
|
||||
* it. Same pattern `installDeepSeekProfile()` already uses for the same reason.
|
||||
*/
|
||||
async toggleCliEnabled(id, checkbox) {
|
||||
const next = checkbox.checked;
|
||||
const res = await this._api(`/api/clis/${encodeURIComponent(id)}`, { method: 'PUT', body: { enabled: next } });
|
||||
@@ -2824,6 +2831,11 @@ Object.assign(CodemanApp.prototype, {
|
||||
this.showToast(`Failed to ${next ? 'enable' : 'disable'} "${id}"${detail ? `: ${detail}` : ''}`, 'error');
|
||||
return;
|
||||
}
|
||||
window.__codemanCliAvailable = { ...(window.__codemanCliAvailable || {}), [id]: next };
|
||||
this.applyWelcomeCliVisibility?.();
|
||||
this.renderMobileOverview?.();
|
||||
const menu = document.getElementById('runModeMenu');
|
||||
if (menu) this._refreshRunModeAvailability?.(menu);
|
||||
await this.loadCliListForSettings();
|
||||
},
|
||||
|
||||
|
||||
@@ -1663,6 +1663,29 @@ export class WebServer extends EventEmitter {
|
||||
// keep without git (issue #236), same reasoning as cloudflared above.
|
||||
git: isGitAvailable(),
|
||||
};
|
||||
// A CLI disabled via the registry (docs/cli-enable-disable-plan.md's Settings UI,
|
||||
// or a hand-edited clis.json) must read as unavailable here too — `isCliAvailable()`
|
||||
// on the frontend is what the welcome screen, the Run-menu dropdown and the mobile
|
||||
// overview all gate on, and none of them otherwise know the registry's `enabled`
|
||||
// flag exists; without this, disabling a CLI in Settings toggled the row there but
|
||||
// left every launch surface still offering it. `git`/`cloudflared` are utility
|
||||
// binaries, not CLI registry entries, and `deepseekBinary` is a secondary
|
||||
// installed-only flag for the "add a profile" affordance — none of the three are
|
||||
// registry ids, so only the nine real SessionMode entries are gated.
|
||||
const registryEnabledIds = new Set<string>(enabledClis().map((entry) => entry.id));
|
||||
for (const id of [
|
||||
'claude',
|
||||
'opencode',
|
||||
'codex',
|
||||
'gemini',
|
||||
'antigravity',
|
||||
'pi',
|
||||
'grok',
|
||||
'deepseek',
|
||||
'omp',
|
||||
] as const) {
|
||||
if (!registryEnabledIds.has(id)) available[id] = false;
|
||||
}
|
||||
html = html.replace(
|
||||
'</head>',
|
||||
() => `<script>window.__codemanCliAvailable=${JSON.stringify(available)};</script>\n</head>`
|
||||
|
||||
@@ -23,8 +23,11 @@ import { isDeepSeekAvailable, isDeepSeekRunnable } from '../src/utils/deepseek-c
|
||||
import { isOmpAvailable } from '../src/utils/omp-cli-resolver.js';
|
||||
import { isCloudflaredAvailable } from '../src/utils/cloudflared-resolver.js';
|
||||
import { isGitAvailable } from '../src/git-clone.js';
|
||||
import { enabledClis } from '../src/config/cli-registry/registry.js';
|
||||
import { enabledClis, reloadCliRegistry } from '../src/config/cli-registry/registry.js';
|
||||
import { STOCK_CLIS } from '../src/config/cli-registry/stock.js';
|
||||
import { dataPath } from '../src/config/instance.js';
|
||||
import { mkdirSync, writeFileSync } from 'node:fs';
|
||||
import { dirname } from 'node:path';
|
||||
|
||||
// renderIndexHtml probes the real PATH for every CLI, which would make the
|
||||
// assertions below depend on whatever happens to be installed on the machine
|
||||
@@ -197,6 +200,31 @@ describe('WebServer.renderIndexHtml', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('reads as unavailable for a CLI disabled via the registry, even though it is installed', async () => {
|
||||
// The bug this guards: a CLI toggled off in Settings (docs/cli-enable-disable-plan.md)
|
||||
// still offered itself in the welcome screen / Run menu / mobile overview, because
|
||||
// window.__codemanCliAvailable was built purely from each resolver's own PATH probe —
|
||||
// it never consulted the registry's `enabled` flag at all. Installed AND enabled must
|
||||
// both hold for `isCliAvailable()` (the client-side gate every one of those surfaces
|
||||
// reads) to read true.
|
||||
vi.mocked(isCodexAvailable).mockReturnValue(true);
|
||||
vi.mocked(isClaudeAvailable).mockReturnValue(true);
|
||||
const path = dataPath('clis.json');
|
||||
mkdirSync(dirname(path), { recursive: true });
|
||||
writeFileSync(path, JSON.stringify({ clis: { codex: { enabled: false } } }, null, 2), { mode: 0o600 });
|
||||
reloadCliRegistry();
|
||||
try {
|
||||
const { server } = makeServer({});
|
||||
const html = await render(server);
|
||||
const flags = JSON.parse(html.match(/window\.__codemanCliAvailable=(\{.*?\});/)![1]);
|
||||
expect(flags.codex).toBe(false); // installed, but disabled in the registry
|
||||
expect(flags.claude).toBe(true); // installed and enabled — unaffected by codex's override
|
||||
} finally {
|
||||
writeFileSync(path, JSON.stringify({ clis: {} }, null, 2), { mode: 0o600 });
|
||||
reloadCliRegistry();
|
||||
}
|
||||
});
|
||||
|
||||
it('reports which run modes the custom-model Run-menu picker may generate an entry for', async () => {
|
||||
// Read generically off the CLI registry's own capabilities, not a hardcoded id
|
||||
// list — antigravity (`unsupported`) and shell (`kind !== 'agent'`) must be
|
||||
|
||||
Reference in New Issue
Block a user