Files
ccs/src/commands/proxy-command.ts
T
Tam Nhu Tran 4f6e61739c refactor(config): adopt config-loader-facade across the codebase
Issue #1161. Sweeps 127 files to import from
src/config/config-loader-facade.ts instead of unified-config-loader or
utils/config-manager directly.

WRITE callers (32 files): replaced raw saveUnifiedConfig /
mutateUnifiedConfig / updateUnifiedConfig calls with the facade's
cache-coherent wrappers saveConfig / mutateConfig / updateConfig. This
fixes a latent stale-cache window where direct writes through the
underlying loader bypassed the facade's memoization.

READ callers (95 files): mechanical import-path migration only —
function names unchanged because the facade re-exports them. No
behavior change.

Also updated:
- tests/unit/utils/browser/browser-setup.test.ts (DI interface rename)
- src/management/checks/image-analysis-check.ts (dynamic import rename)
- src/web-server/health-service.ts (dynamic require rename)
- src/ccs.ts (path prefix fix from sweep script)

After sweep: zero raw write callers remain outside src/config/. Direct
imports of config-manager remain only for symbols not in the facade
(getConfigPath, getCcsDirSource, etc). Behavior unchanged; full suite
passes 1824/1824.

Out of scope: switching loadOrCreateUnifiedConfig() callers to
getCachedConfig() — needs per-callsite cache-safety analysis. Tracked
as follow-up.

Refs #1161
2026-05-03 01:42:53 -04:00

279 lines
8.4 KiB
TypeScript

import { detectShell, formatExportLine } from './env-command';
import { getSettingsPath } from '../utils/config-manager';
import { expandPath } from '../utils/helpers';
import { fail, info, ok } from '../utils/ui';
import {
buildOpenAICompatProxyEnv,
getOpenAICompatProxyStatus,
listOpenAICompatProxyStatuses,
resolveOpenAICompatProfileConfig,
startOpenAICompatProxy,
stopOpenAICompatProxy,
} from '../proxy';
import { loadSettings } from '../config/config-loader-facade';
function parseOptionValue(args: string[], key: string): string | undefined {
const exactIndex = args.findIndex((arg) => arg === key);
if (exactIndex !== -1 && args[exactIndex + 1]) {
return args[exactIndex + 1];
}
const prefix = `${key}=`;
const withEquals = args.find((arg) => arg.startsWith(prefix));
return withEquals ? withEquals.slice(prefix.length) : undefined;
}
function hasHelpFlag(args: string[]): boolean {
return args.includes('--help') || args.includes('-h');
}
export function findPositionalArg(
args: string[],
optionsWithValues: string[] = [],
flagOptions: string[] = []
): string | undefined {
for (let i = 0; i < args.length; i += 1) {
const arg = args[i];
if (arg === '--') {
return i + 1 < args.length ? args[i + 1] : undefined;
}
if (arg.startsWith('-')) {
if (flagOptions.includes(arg)) {
continue;
}
if (optionsWithValues.includes(arg)) {
i += 1;
}
continue;
}
return arg;
}
return undefined;
}
function showHelp(): number {
console.log('OpenAI-Compatible Proxy');
console.log('');
console.log('Usage: ccs proxy <start|stop|status|activate> [profile] [options]');
console.log('');
console.log('Commands:');
console.log(
' start <profile> Start the local proxy for an OpenAI-compatible settings profile'
);
console.log(' stop [profile] Stop the running proxy (or all proxies when omitted)');
console.log(' status [profile] Show daemon status');
console.log(' activate [profile] Print shell exports for the running proxy');
console.log('');
console.log('Options:');
console.log(' --port <n> Pin an exact local proxy port for this launch');
console.log(' --host <addr> Bind the proxy server to a specific host (default: 127.0.0.1)');
console.log(' --shell <name> activate only: auto|bash|zsh|fish|powershell');
console.log(' --fish activate only: shorthand for --shell fish');
console.log(' --insecure Disable upstream TLS verification');
console.log('');
console.log('Examples:');
console.log(' ccs proxy start hf');
console.log(' eval "$(ccs proxy activate)"');
console.log(' ccs proxy activate --fish');
console.log(' ccs proxy status');
console.log(' ccs proxy stop');
console.log('');
return 0;
}
function resolveProfile(profileName: string) {
const settingsPath = expandPath(getSettingsPath(profileName));
const settings = loadSettings(settingsPath);
const profile = resolveOpenAICompatProfileConfig(profileName, settingsPath, settings.env || {});
if (!profile) {
throw new Error(`Profile "${profileName}" is not configured for an OpenAI-compatible endpoint`);
}
return profile;
}
async function handleStart(args: string[]): Promise<number> {
if (hasHelpFlag(args)) {
return showHelp();
}
const profileName = findPositionalArg(args, ['--port', '--host'], ['--insecure']);
if (!profileName) {
console.error(
fail('Usage: ccs proxy start <profile> [--port <n>] [--host <addr>] [--insecure]')
);
return 1;
}
const portValue = parseOptionValue(args, '--port');
const host = parseOptionValue(args, '--host');
const parsedPort = portValue ? Number(portValue) : undefined;
if (
portValue &&
(parsedPort === undefined ||
!/^\d+$/.test(portValue) ||
!Number.isInteger(parsedPort) ||
parsedPort < 1 ||
parsedPort > 65535)
) {
console.error(fail(`Invalid port: ${portValue}`));
return 1;
}
const port = parsedPort;
let profile;
try {
profile = resolveProfile(profileName);
} catch (error) {
console.error(fail((error as Error).message));
return 1;
}
const result = await startOpenAICompatProxy(profile, {
...(port ? { port } : {}),
...(host ? { host } : {}),
insecure: args.includes('--insecure'),
});
if (!result.success) {
console.error(fail(result.error || 'Failed to start proxy'));
return 1;
}
console.log(
result.alreadyRunning
? info(`Proxy already running on port ${result.port}`)
: ok(`Proxy started on port ${result.port}`)
);
return 0;
}
async function handleStatus(args: string[] = []): Promise<number> {
if (hasHelpFlag(args)) {
return showHelp();
}
const profileName = findPositionalArg(args);
const printStatus = (status: Awaited<ReturnType<typeof getOpenAICompatProxyStatus>>) => {
console.log(
status.running
? ok(`Proxy running on port ${status.port ?? 'unknown'}`)
: info(`Proxy is not running${status.port ? ` (last known port ${status.port})` : ''}`)
);
if (status.host && status.port) {
console.log(` Host: ${status.host}`);
console.log(` Local URL: http://${status.host}:${status.port}`);
}
if (status.profileName) {
console.log(` Profile: ${status.profileName}`);
}
if (status.baseUrl) {
console.log(` Base URL: ${status.baseUrl}`);
}
if (status.model) {
console.log(` Model: ${status.model}`);
}
if (status.pid) {
console.log(` PID: ${status.pid}`);
}
};
if (profileName) {
const status = await getOpenAICompatProxyStatus(profileName);
if (!status.running && !status.profileName) {
console.log(info('Proxy is not running'));
return 0;
}
printStatus(status);
return 0;
}
const status = await getOpenAICompatProxyStatus();
if (!status.running && !status.profileName) {
console.log(info('Proxy is not running'));
return 0;
}
if (status.running && !status.profileName) {
const running = (await listOpenAICompatProxyStatuses()).filter((entry) => entry.running);
for (const entry of running) {
printStatus(entry);
}
return 0;
}
printStatus(status);
return 0;
}
async function handleActivate(args: string[]): Promise<number> {
if (hasHelpFlag(args)) {
return showHelp();
}
const profileName = findPositionalArg(args, ['--shell']);
if (!profileName) {
const running = (await listOpenAICompatProxyStatuses()).filter((entry) => entry.running);
if (running.length > 1) {
console.error(
fail('Multiple proxies are running. Specify a profile: ccs proxy activate <profile>')
);
return 1;
}
}
const status = await getOpenAICompatProxyStatus(profileName);
if (!status.running || !status.profileName || !status.port || !status.authToken) {
console.error(fail('Proxy is not running. Start it with: ccs proxy start <profile>'));
return 1;
}
const shell = detectShell(args.includes('--fish') ? 'fish' : parseOptionValue(args, '--shell'));
let profile;
try {
profile = resolveProfile(status.profileName);
} catch (error) {
console.error(fail((error as Error).message));
return 1;
}
const env = buildOpenAICompatProxyEnv(
profile,
status.port,
status.authToken,
undefined,
status.host || '127.0.0.1'
);
Object.entries(env).forEach(([key, value]) => {
console.log(formatExportLine(shell, key, value));
});
return 0;
}
export async function handleProxyCommand(args: string[]): Promise<number> {
const subcommand = args[0];
switch (subcommand) {
case undefined:
case 'help':
case '--help':
case '-h':
return showHelp();
case 'start':
return handleStart(args.slice(1));
case 'stop': {
if (hasHelpFlag(args.slice(1))) {
return showHelp();
}
const profileName = args[1] && !args[1].startsWith('-') ? args[1] : undefined;
const result = await stopOpenAICompatProxy(profileName);
if (!result.success) {
console.error(fail(result.error || 'Failed to stop proxy'));
return 1;
}
console.log(ok(profileName ? `Proxy stopped for profile ${profileName}` : 'Proxy stopped'));
return 0;
}
case 'status':
return handleStatus(args.slice(1));
case 'activate':
return handleActivate(args.slice(1));
default:
console.error(fail(`Unknown proxy subcommand: ${subcommand}`));
return 1;
}
}