diff --git a/src/ccs.ts b/src/ccs.ts index 0fde15ea..19b797d4 100644 --- a/src/ccs.ts +++ b/src/ccs.ts @@ -22,7 +22,7 @@ import { isAuthenticated, } from './cliproxy'; import { getEffectiveEnvVars, getCompositeEnvVars } from './cliproxy/config/env-builder'; -import { CLIPROXY_DEFAULT_PORT } from './cliproxy/config/port-manager'; +import { resolveLifecyclePort } from './cliproxy/config/port-manager'; import { ensureWebSearchMcpOrThrow, displayWebSearchStatus, @@ -929,7 +929,7 @@ async function main(): Promise { } const customSettingsPath = profileInfo.settingsPath; // undefined for hardcoded profiles const variantPort = profileInfo.port; // variant-specific port for isolation - const cliproxyPort = variantPort || CLIPROXY_DEFAULT_PORT; + const cliproxyPort = variantPort || resolveLifecyclePort(); if (resolvedTarget !== 'claude') { const adapter = targetAdapter; @@ -1385,7 +1385,7 @@ async function main(): Promise { }; } else if (imageAnalysisStatus.proxyReadiness === 'stopped') { const ensureServiceResult = await ensureCliproxyService( - CLIPROXY_DEFAULT_PORT, + resolveLifecyclePort(), verboseProxyLaunch ); if (!ensureServiceResult.started) { diff --git a/src/cliproxy/binary-manager.ts b/src/cliproxy/binary-manager.ts index baa11726..17cf408d 100644 --- a/src/cliproxy/binary-manager.ts +++ b/src/cliproxy/binary-manager.ts @@ -8,7 +8,8 @@ import * as fs from 'fs'; import * as path from 'path'; import { info, warn } from '../utils/ui'; -import { getBinDir, CLIPROXY_DEFAULT_PORT } from './config/config-generator'; +import { getBinDir } from './config/config-generator'; +import { resolveLifecyclePort } from './config/port-manager'; import { BinaryInfo, BinaryManagerConfig } from './types'; import { BACKEND_CONFIG, @@ -340,7 +341,7 @@ export async function installCliproxyVersion( const result = await stopProxyFn(); if (result.stopped) { // Wait for port to be fully released - const portFree = await waitForPortFreeFn(CLIPROXY_DEFAULT_PORT, 5000); + const portFree = await waitForPortFreeFn(resolveLifecyclePort(), 5000); if (!portFree && verbose) { console.log(formatWarn('Port did not free up in time, proceeding anyway...')); } diff --git a/src/cliproxy/binary/lifecycle.ts b/src/cliproxy/binary/lifecycle.ts index 737354e0..474a3857 100644 --- a/src/cliproxy/binary/lifecycle.ts +++ b/src/cliproxy/binary/lifecycle.ts @@ -14,7 +14,7 @@ import { import { downloadAndInstall, deleteBinary, getBinaryPath } from './installer'; import { info, warn } from '../../utils/ui'; import { isCliproxyRunning } from '../services/stats-fetcher'; -import { CLIPROXY_DEFAULT_PORT } from '../config/config-generator'; +import { resolveLifecyclePort } from '../config/port-manager'; import { CLIPROXY_MAX_STABLE_VERSION, CLIPROXY_FAULTY_RANGE, @@ -82,7 +82,7 @@ async function handleAutoUpdate(config: BinaryManagerConfig, verbose: boolean): return; } - const proxyRunning = await isCliproxyRunning(CLIPROXY_DEFAULT_PORT); + const proxyRunning = await isCliproxyRunning(resolveLifecyclePort()); const latestNote = isAboveMaxStable(latestVersion) ? ` (latest v${latestVersion} unstable)` : ''; const updateMsg = `${backendLabel} update: v${currentVersion} -> v${targetVersion}${latestNote}`; diff --git a/src/cliproxy/config/port-manager.ts b/src/cliproxy/config/port-manager.ts index 3f2b05d2..44e9a057 100644 --- a/src/cliproxy/config/port-manager.ts +++ b/src/cliproxy/config/port-manager.ts @@ -3,6 +3,9 @@ * Handles port number validation and default port resolution */ +import { loadOrCreateUnifiedConfig } from '../../config/unified-config-loader'; +import type { UnifiedConfig } from '../../config/unified-config-types'; + /** Default CLIProxy port */ export const CLIPROXY_DEFAULT_PORT = 8317; @@ -57,3 +60,13 @@ export function normalizeProtocol(protocol: string | undefined): 'http' | 'https // Invalid protocol (e.g., 'ftp') - default to http return 'http'; } + +/** + * Resolve the local CLIProxy lifecycle port from unified config. + * Falls back to default port when unset/invalid. + */ +export function resolveLifecyclePort( + config: Pick = loadOrCreateUnifiedConfig() +): number { + return validatePort(config.cliproxy_server?.local?.port ?? CLIPROXY_DEFAULT_PORT); +} diff --git a/src/commands/cliproxy/proxy-lifecycle-subcommand.ts b/src/commands/cliproxy/proxy-lifecycle-subcommand.ts index 747cadb0..001e995f 100644 --- a/src/commands/cliproxy/proxy-lifecycle-subcommand.ts +++ b/src/commands/cliproxy/proxy-lifecycle-subcommand.ts @@ -11,7 +11,7 @@ import { initUI, header, color, dim, ok, warn, info } from '../../utils/ui'; import { getProxyStatus, startProxy, stopProxy } from '../../cliproxy/services'; import { detectRunningProxy } from '../../cliproxy/proxy/proxy-detector'; -import { resolveLifecyclePort } from './resolve-lifecycle-port'; +import { resolveLifecyclePort } from '../../cliproxy/config/port-manager'; export async function handleStart(verbose = false): Promise { await initUI(); diff --git a/src/commands/cliproxy/resolve-lifecycle-port.ts b/src/commands/cliproxy/resolve-lifecycle-port.ts deleted file mode 100644 index e250d5e6..00000000 --- a/src/commands/cliproxy/resolve-lifecycle-port.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { CLIPROXY_DEFAULT_PORT, validatePort } from '../../cliproxy/config/port-manager'; -import { loadOrCreateUnifiedConfig } from '../../config/unified-config-loader'; -import type { UnifiedConfig } from '../../config/unified-config-types'; - -type LifecyclePortConfig = Pick; - -/** - * Resolve the local CLIProxy lifecycle port from unified config. - * Falls back to default port when unset/invalid. - */ -export function resolveLifecyclePort( - config: LifecyclePortConfig = loadOrCreateUnifiedConfig() -): number { - return validatePort(config.cliproxy_server?.local?.port ?? CLIPROXY_DEFAULT_PORT); -} diff --git a/src/commands/config-command.ts b/src/commands/config-command.ts index bf47b324..907cbfe5 100644 --- a/src/commands/config-command.ts +++ b/src/commands/config-command.ts @@ -11,7 +11,7 @@ import open from 'open'; import { startServer } from '../web-server'; import { setupGracefulShutdown } from '../web-server/shutdown'; import { ensureCliproxyService } from '../cliproxy/service-manager'; -import { CLIPROXY_DEFAULT_PORT } from '../cliproxy/config/config-generator'; +import { resolveLifecyclePort } from '../cliproxy/config/port-manager'; import { getDashboardAuthConfig } from '../config/unified-config-loader'; import { initUI, header, ok, info, warn, fail } from '../utils/ui'; import { resolveNamedCommand, type NamedCommandRoute } from './named-command-router'; @@ -137,7 +137,7 @@ export async function handleConfigCommand( // Ensure CLIProxy service is running for dashboard features console.log(deps.info('Starting CLIProxy service...')); - const cliproxyResult = await deps.ensureCliproxyService(CLIPROXY_DEFAULT_PORT, verbose); + const cliproxyResult = await deps.ensureCliproxyService(resolveLifecyclePort(), verbose); logger.info('cliproxy.ensure_result', 'Config command checked CLIProxy availability', { started: cliproxyResult.started, alreadyRunning: cliproxyResult.alreadyRunning, diff --git a/src/copilot/copilot-executor.ts b/src/copilot/copilot-executor.ts index b7d96962..c27c6000 100644 --- a/src/copilot/copilot-executor.ts +++ b/src/copilot/copilot-executor.ts @@ -10,7 +10,7 @@ import { CopilotConfig } from '../config/unified-config-types'; import { getGlobalEnvConfig } from '../config/unified-config-loader'; import { ensureCliproxyService } from '../cliproxy'; import { getEffectiveApiKey } from '../cliproxy/auth/auth-token-manager'; -import { CLIPROXY_DEFAULT_PORT } from '../cliproxy/config/port-manager'; +import { resolveLifecyclePort } from '../cliproxy/config/port-manager'; import { checkAuthStatus, isCopilotApiInstalled } from './copilot-auth'; import { isDaemonRunning, startDaemon } from './copilot-daemon'; import { ensureCopilotApi } from './copilot-package-manager'; @@ -142,7 +142,7 @@ export async function resolveCopilotImageAnalysisEnv( if (status.proxyReadiness === 'stopped') { const ensureServiceResult = await resolvedDeps.ensureCliproxyService( - CLIPROXY_DEFAULT_PORT, + resolveLifecyclePort(), verbose ); if (!ensureServiceResult.started) { diff --git a/src/web-server/routes/cliproxy-local-proxy.ts b/src/web-server/routes/cliproxy-local-proxy.ts index dc55e0fb..9c512e96 100644 --- a/src/web-server/routes/cliproxy-local-proxy.ts +++ b/src/web-server/routes/cliproxy-local-proxy.ts @@ -9,8 +9,7 @@ import http from 'http'; import { Request, Response, Router } from 'express'; -import { CLIPROXY_DEFAULT_PORT, validatePort } from '../../cliproxy/config/port-manager'; -import { loadOrCreateUnifiedConfig } from '../../config/unified-config-loader'; +import { resolveLifecyclePort } from '../../cliproxy/config/port-manager'; import { requireLocalAccessWhenAuthDisabled } from '../middleware/auth-middleware'; export interface CliproxyLocalProxyDeps { @@ -22,15 +21,6 @@ export interface CliproxyLocalProxyDeps { /** Proxy request timeout in milliseconds (30 seconds) */ const PROXY_TIMEOUT_MS = 30_000; -function resolveLocalCliproxyPort(): number { - try { - const config = loadOrCreateUnifiedConfig(); - return validatePort(config.cliproxy_server?.local?.port ?? CLIPROXY_DEFAULT_PORT); - } catch { - return CLIPROXY_DEFAULT_PORT; - } -} - function isJsonContentType(contentType: string | string[] | undefined): boolean { const values = Array.isArray(contentType) ? contentType : [contentType]; return values.some((value) => value?.toLowerCase().includes('application/json') === true); @@ -82,7 +72,7 @@ export function createCliproxyLocalProxyRouter(deps: CliproxyLocalProxyDeps = {} 'CLIProxy local proxy requires localhost access when dashboard auth is disabled.' )); const createRequest = deps.request ?? http.request; - const resolveTargetPort = deps.resolveTargetPort ?? resolveLocalCliproxyPort; + const resolveTargetPort = deps.resolveTargetPort ?? resolveLifecyclePort; router.use((req: Request, res: Response, next) => { if (enforceAccess(req, res)) { diff --git a/src/web-server/routes/cliproxy-stats-routes.ts b/src/web-server/routes/cliproxy-stats-routes.ts index f9430e8c..105c2e62 100644 --- a/src/web-server/routes/cliproxy-stats-routes.ts +++ b/src/web-server/routes/cliproxy-stats-routes.ts @@ -52,7 +52,7 @@ import { CLIPROXY_MAX_STABLE_VERSION, CLIPROXY_FAULTY_RANGE, } from '../../cliproxy/binary/platform-detector'; -import { CLIPROXY_DEFAULT_PORT } from '../../cliproxy/config/port-manager'; +import { resolveLifecyclePort } from '../../cliproxy/config/port-manager'; import { MODEL_ENV_VAR_KEYS, canonicalizeModelIdForProvider, @@ -321,7 +321,7 @@ router.get('/usage', handleStatsRequest); */ router.get('/status', async (_req: Request, res: Response): Promise => { try { - const running = await isCliproxyRunning(); + const running = await isCliproxyRunning(resolveLifecyclePort()); res.json({ running }); } catch (error) { console.error(`[cliproxy-stats] ${(error as Error).message}`); @@ -345,15 +345,16 @@ router.get('/proxy-status', async (_req: Request, res: Response): Promise return; } + const port = resolveLifecyclePort(); // Session tracker says not running, but proxy might be running without session tracking // (e.g., started before session persistence was implemented) - const actuallyRunning = await isCliproxyRunning(); + const actuallyRunning = await isCliproxyRunning(port); if (actuallyRunning) { // Proxy running but no session lock - legacy/untracked instance res.json({ running: true, - port: CLIPROXY_DEFAULT_PORT, + port, sessionCount: 0, // Unknown sessions // No pid/startedAt since we don't have session lock }); diff --git a/tests/unit/commands/proxy-lifecycle-subcommand.test.ts b/tests/unit/commands/proxy-lifecycle-subcommand.test.ts index f0607add..32260077 100644 --- a/tests/unit/commands/proxy-lifecycle-subcommand.test.ts +++ b/tests/unit/commands/proxy-lifecycle-subcommand.test.ts @@ -1,6 +1,5 @@ import { describe, expect, it } from 'bun:test'; -import { CLIPROXY_DEFAULT_PORT } from '../../../src/cliproxy/config/port-manager'; -import { resolveLifecyclePort } from '../../../src/commands/cliproxy/resolve-lifecycle-port'; +import { CLIPROXY_DEFAULT_PORT, resolveLifecyclePort } from '../../../src/cliproxy/config/port-manager'; describe('resolveLifecyclePort', () => { it('uses configured cliproxy_server.local.port', () => {