mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 20:49:41 +02:00
Antigravity (agy) was wired into the session layer but never propagated to the surfaces around it, while Gemini CLI stayed documented as a consumer product despite being enterprise-only since Google's cutover. Gemini keeps full support; Antigravity now sits beside it everywhere. Functional fixes: - docker/agent.Dockerfile never installed agy, so a docker case with mode 'antigravity' died on command-not-found. agy is not on npm, so it gets its own installer step. --dir /usr/local/bin is load-bearing: the default $HOME/.local/bin resolves to root's home at build time and is unreachable by the `agent` user the container runs as. Verified inside codeman/agent:base (v1.1.10, reachable as `agent`). Note the binary is ~190MB, the largest layer in the image. - Welcome screen gained a Run Antigravity action, gated on agy being present like the other CLI buttons, with a cyan identity matching the toolbar run button and run-mode dot. - install.sh now detects agy (search paths mirroring the resolver), counts it as a satisfying AI CLI, and recommends it over Gemini in the install hints. Detection only, no new auto-install path. Docs corrected where they were factually wrong: - architecture-invariants documented isExternalCliMode() as opencode/codex/gemini when the code has included antigravity for a while, said "all three modes", and omitted ANTIGRAVITY_ from the env prefix allowlist row. - cron-guide's agentType enum, cron-discovery's SessionMode, and remote-sessions' RemoteCommandMode were all stale. Also: README + README.zh-CN (five CLIs, Gemini marked enterprise-only), package.json keyword, and comment drift in 8 places. test/run-mode-ui.test.ts now covers the new welcome button; verified it fails without the settings-ui wiring. Antigravity nests its whole state under ~/.gemini/antigravity-cli/, not ~/.antigravity, so the existing .gemini docker credential seed already covers it. Recorded as a comment so nobody adds dead config later. isAltScreenStripMode() deliberately still excludes antigravity: whether its TUI needs the alt-screen strip is a behavioural question that needs a real agy session, not a guess. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
66 lines
2.2 KiB
TypeScript
66 lines
2.2 KiB
TypeScript
/**
|
|
* @fileoverview Resolve the `cloudflared` binary across common install paths.
|
|
*
|
|
* Mirrors the CLI resolvers (antigravity-cli-resolver.ts et al), for the same reason
|
|
* they exist: the welcome screen should not offer a button whose only possible
|
|
* outcome is an error toast.
|
|
*
|
|
* The search list is deliberately the SAME one `TunnelManager.resolveCloudflared()`
|
|
* has always used, and that method now delegates here so the two can never drift.
|
|
* The difference is the fallback: this module answers "is it installed?" honestly
|
|
* with null, while the tunnel manager keeps falling back to the bare name so a
|
|
* cloudflared that only exists somewhere on the tunnel process's PATH still
|
|
* starts. A stricter answer there would turn a working tunnel into a refusal.
|
|
*
|
|
* @module utils/cloudflared-resolver
|
|
*/
|
|
|
|
import { execSync } from 'node:child_process';
|
|
import { existsSync } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
import { homedir } from 'node:os';
|
|
import { EXEC_TIMEOUT_MS } from '../config/exec-timeout.js';
|
|
|
|
/** Common directories where the cloudflared binary may be installed */
|
|
const CLOUDFLARED_SEARCH_DIRS = [join(homedir(), '.local', 'bin'), '/usr/local/bin'];
|
|
|
|
/** Cached path to the cloudflared binary (empty string = searched but not found) */
|
|
let _cloudflaredPath: string | null = null;
|
|
|
|
/**
|
|
* Finds the `cloudflared` binary.
|
|
*
|
|
* @returns Absolute path, or null if not found
|
|
*/
|
|
export function resolveCloudflaredPath(): string | null {
|
|
if (_cloudflaredPath !== null) return _cloudflaredPath || null;
|
|
|
|
for (const dir of CLOUDFLARED_SEARCH_DIRS) {
|
|
const candidate = join(dir, 'cloudflared');
|
|
if (existsSync(candidate)) {
|
|
_cloudflaredPath = candidate;
|
|
return candidate;
|
|
}
|
|
}
|
|
|
|
try {
|
|
const result = execSync('which cloudflared', { encoding: 'utf-8', timeout: EXEC_TIMEOUT_MS }).trim();
|
|
if (result && existsSync(result)) {
|
|
_cloudflaredPath = result;
|
|
return result;
|
|
}
|
|
} catch {
|
|
// Not on PATH either.
|
|
}
|
|
|
|
_cloudflaredPath = ''; // mark as searched, not found
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Check if cloudflared is available on the system.
|
|
*/
|
|
export function isCloudflaredAvailable(): boolean {
|
|
return resolveCloudflaredPath() !== null;
|
|
}
|