Files
Codeman/src/utils/cli-installed-probes.ts
T
Devvyn c5de8383e7 feat(cli-registry): GitHub Copilot CLI as a run mode
Copilot (`copilot`, npm @github/copilot) as a stock CLI entry with the same launch options as the other agent CLIs: model (--model), resume (--resume), continue (--continue) and allow-all (--yolo, clamped off for non-granted multi-user owners). Adds a resolver and GET /api/copilot/status probe, the agent-image package, Run-menu/mobile/logo entries, skill docs and tests.
2026-10-18 00:47:46 +08:00

73 lines
2.8 KiB
TypeScript

/**
* @fileoverview One answer to "is this CLI installed here?", shared by the page render
* (`renderIndexHtml` in server.ts, which injects `window.__codemanCliAvailable` and
* `window.__codemanCliCatalog`) and `GET /api/clis` (the Settings list's badge).
*
* The two used to keep their own copies of the per-CLI probe map, so they could drift apart
* and the badge could disagree with the Run menu.
*
* Every probe is a memoized resolver, so this is cheap to call per request. Dynamic imports
* keep the ten resolvers out of any module that never asks.
*/
import type { CliEntry } from '../config/cli-registry/types.js';
import { isCliAvailable as isRegistryCliAvailable } from './cli-resolver.js';
/**
* The stock CLIs whose own resolver answers availability. It keeps the resolver's specific
* semantics (pi/grok/deepseek identity probes). DeepSeek reports RUNNABLE here, not merely
* installed: `dsh` is a profile launcher, and a dsh with no pane-capable profile would
* offer a Run button that spawns a pane which dies on arrival.
*/
export async function probeStockCliAvailability(): Promise<Record<string, boolean>> {
const [
{ isClaudeAvailable },
{ isOpenCodeAvailable },
{ isCodexAvailable },
{ isGeminiAvailable },
{ isAntigravityAvailable },
{ isPiAvailable },
{ isGrokAvailable },
{ isDeepSeekRunnable },
{ isOmpAvailable },
{ isCopilotAvailable },
] = await Promise.all([
import('./claude-cli-resolver.js'),
import('./opencode-cli-resolver.js'),
import('./codex-cli-resolver.js'),
import('./gemini-cli-resolver.js'),
import('./antigravity-cli-resolver.js'),
import('./pi-cli-resolver.js'),
import('./grok-cli-resolver.js'),
import('./deepseek-cli-resolver.js'),
import('./omp-cli-resolver.js'),
import('./copilot-cli-resolver.js'),
]);
return {
claude: isClaudeAvailable(),
opencode: isOpenCodeAvailable(),
codex: isCodexAvailable(),
gemini: isGeminiAvailable(),
antigravity: isAntigravityAvailable(),
pi: isPiAvailable(),
grok: isGrokAvailable(),
deepseek: isDeepSeekRunnable(),
omp: isOmpAvailable(),
copilot: isCopilotAvailable(),
};
}
/**
* Is `entry` installed? A shell entry has no binary to probe, since it is the server's own
* login shell. A stock entry with a dedicated resolver uses `stockAvailability`. Anything
* else, custom entries included, uses the registry's GENERIC resolver. That is the one a
* session spawn uses, and it understands the entry's declared binaries and search dirs.
*/
export function isCliEntryInstalled(entry: CliEntry, stockAvailability: Record<string, boolean>): boolean {
if (entry.kind === 'shell') return true;
const id = entry.id as string;
return Object.prototype.hasOwnProperty.call(stockAvailability, id)
? stockAvailability[id]
: isRegistryCliAvailable(id);
}