mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 00:22:34 +00:00
refactor: merge facade adoption from #1169 into executor extraction
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { getCcsDir, loadConfigSafe } from '../../utils/config-manager';
|
||||
|
||||
import { buildProxyUrl, getProxyTarget } from '../../cliproxy/proxy/proxy-target-resolver';
|
||||
import { getEffectiveApiKey } from '../../cliproxy/auth/auth-token-manager';
|
||||
import { getModelMappingFromConfig } from '../../cliproxy/config/base-config-loader';
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
mapExternalProviderName,
|
||||
} from '../../cliproxy/provider-capabilities';
|
||||
import { extractProviderFromPathname } from '../../cliproxy/ai-providers/model-id-normalizer';
|
||||
import { isUnifiedMode, loadOrCreateUnifiedConfig } from '../../config/unified-config-loader';
|
||||
|
||||
import type { TargetType } from '../../targets/target-adapter';
|
||||
import type { Settings } from '../../types/config';
|
||||
import type { CLIProxyProvider } from '../../cliproxy/types';
|
||||
@@ -21,6 +21,12 @@ import type {
|
||||
ModelMapping,
|
||||
ResolvedCliproxyBridgeProfile,
|
||||
} from './profile-types';
|
||||
import {
|
||||
getCcsDir,
|
||||
isUnifiedMode,
|
||||
loadConfigSafe,
|
||||
loadOrCreateUnifiedConfig,
|
||||
} from '../../config/config-loader-facade';
|
||||
|
||||
const DEFAULT_PROFILE_SUFFIX = '-api';
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { getCcsDir } from '../../utils/config-manager';
|
||||
import { getCcsDir } from '../../config/config-loader-facade';
|
||||
|
||||
const OPENROUTER_API_URL = 'https://openrouter.ai/api/v1/models';
|
||||
function getCacheFile() {
|
||||
|
||||
@@ -9,12 +9,12 @@ import * as path from 'path';
|
||||
import type { Config, Settings } from '../../types';
|
||||
import type { TargetType } from '../../targets/target-adapter';
|
||||
import { getPersistedTargetChoices, isPersistedTargetType } from '../../targets/target-metadata';
|
||||
import { getCcsDir, getConfigPath, loadConfigSafe } from '../../utils/config-manager';
|
||||
import { getConfigPath } from '../../utils/config-manager';
|
||||
import { ensureWebSearchMcpOrThrow } from '../../utils/websearch-manager';
|
||||
import { ensureImageAnalysisMcpOrThrow } from '../../utils/image-analysis';
|
||||
import { isSensitiveKey } from '../../utils/sensitive-keys';
|
||||
import { isReservedName } from '../../config/reserved-names';
|
||||
import { isUnifiedMode, mutateUnifiedConfig } from '../../config/unified-config-loader';
|
||||
|
||||
import { validateApiName } from './validation-service';
|
||||
import { listApiProfiles } from './profile-reader';
|
||||
import { validateApiProfileSettingsPayload } from './profile-lifecycle-validation';
|
||||
@@ -26,6 +26,12 @@ import type {
|
||||
ImportApiProfileResult,
|
||||
RegisterApiProfileOrphansResult,
|
||||
} from './profile-types';
|
||||
import {
|
||||
getCcsDir,
|
||||
isUnifiedMode,
|
||||
loadConfigSafe,
|
||||
mutateConfig,
|
||||
} from '../../config/config-loader-facade';
|
||||
|
||||
const SETTINGS_FILE_SUFFIX = '.settings.json';
|
||||
const REDACTED_TOKEN_SENTINEL = '__CCS_REDACTED__';
|
||||
@@ -62,7 +68,7 @@ function writeJsonObjectAtomically(filePath: string, value: unknown): void {
|
||||
|
||||
function registerApiProfileInConfig(name: string, target: TargetType, force = false): void {
|
||||
if (isUnifiedMode()) {
|
||||
mutateUnifiedConfig((config) => {
|
||||
mutateConfig((config) => {
|
||||
if (config.profiles[name] && !force) {
|
||||
throw new Error(`API profile already exists: ${name}`);
|
||||
}
|
||||
|
||||
@@ -6,14 +6,18 @@
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import { loadConfigSafe } from '../../utils/config-manager';
|
||||
import { loadOrCreateUnifiedConfig, isUnifiedMode } from '../../config/unified-config-loader';
|
||||
|
||||
import { expandPath } from '../../utils/helpers';
|
||||
import type { TargetType } from '../../targets/target-adapter';
|
||||
import { isPersistedTargetType } from '../../targets/target-metadata';
|
||||
import type { Settings } from '../../types/config';
|
||||
import type { ApiProfileInfo, CliproxyVariantInfo, ApiListResult } from './profile-types';
|
||||
import { resolveCliproxyBridgeMetadata } from './cliproxy-profile-bridge';
|
||||
import {
|
||||
isUnifiedMode,
|
||||
loadConfigSafe,
|
||||
loadOrCreateUnifiedConfig,
|
||||
} from '../../config/config-loader-facade';
|
||||
|
||||
function sanitizeTarget(target: unknown): TargetType {
|
||||
if (isPersistedTargetType(target)) {
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
*/
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { getCcsDir, getConfigPath, loadConfigSafe } from '../../utils/config-manager';
|
||||
import { getConfigPath } from '../../utils/config-manager';
|
||||
import { expandPath } from '../../utils/helpers';
|
||||
import { validateApiName } from './validation-service';
|
||||
import { mutateUnifiedConfig, isUnifiedMode } from '../../config/unified-config-loader';
|
||||
|
||||
import { ensureWebSearchMcpOrThrow } from '../../utils/websearch-manager';
|
||||
import { ensureImageAnalysisMcpOrThrow } from '../../utils/image-analysis';
|
||||
import type { TargetType } from '../../targets/target-adapter';
|
||||
@@ -31,6 +31,12 @@ import {
|
||||
resolveCliproxyBridgeMetadata,
|
||||
resolveCliproxyBridgeProfile,
|
||||
} from './cliproxy-profile-bridge';
|
||||
import {
|
||||
getCcsDir,
|
||||
isUnifiedMode,
|
||||
loadConfigSafe,
|
||||
mutateConfig,
|
||||
} from '../../config/config-loader-facade';
|
||||
|
||||
/** Check if URL is an OpenRouter endpoint */
|
||||
function isOpenRouterUrl(baseUrl: string): boolean {
|
||||
@@ -232,7 +238,7 @@ function createApiProfileUnified(
|
||||
throw error;
|
||||
}
|
||||
|
||||
mutateUnifiedConfig((config) => {
|
||||
mutateConfig((config) => {
|
||||
config.profiles[name] = {
|
||||
type: 'api',
|
||||
settings: `~/.ccs/${settingsFile}`,
|
||||
@@ -343,7 +349,7 @@ export function updateApiProfileTarget(
|
||||
): UpdateApiProfileTargetResult {
|
||||
try {
|
||||
if (isUnifiedMode()) {
|
||||
mutateUnifiedConfig((config) => {
|
||||
mutateConfig((config) => {
|
||||
if (!config.profiles[name]) {
|
||||
throw new Error(`API profile not found: ${name}`);
|
||||
}
|
||||
@@ -392,7 +398,7 @@ export function updateApiProfileTarget(
|
||||
|
||||
/** Remove API profile from unified config */
|
||||
function removeApiProfileUnified(name: string): void {
|
||||
mutateUnifiedConfig((config) => {
|
||||
mutateConfig((config) => {
|
||||
const profile = config.profiles[name];
|
||||
|
||||
if (!profile) {
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
getWindowsEscapedCommandShell,
|
||||
stripClaudeCodeEnv,
|
||||
} from '../../utils/shell-executor';
|
||||
import { isUnifiedMode } from '../../config/unified-config-loader';
|
||||
|
||||
import { ProfileMetadata } from '../../types';
|
||||
import {
|
||||
resolveCreateAccountContext,
|
||||
@@ -25,6 +25,7 @@ import { exitWithError } from '../../errors';
|
||||
import { ExitCode } from '../../errors/exit-codes';
|
||||
import { CommandContext, parseArgs } from './types';
|
||||
import { stripAmbientProviderCredentials } from './create-command-env';
|
||||
import { isUnifiedMode } from '../../config/config-loader-facade';
|
||||
|
||||
function sanitizeProfileNameForInstance(name: string): string {
|
||||
return name.replace(/[^a-zA-Z0-9_-]/g, '-').toLowerCase();
|
||||
|
||||
@@ -5,10 +5,11 @@
|
||||
*/
|
||||
|
||||
import { initUI, color, dim, ok, fail } from '../../utils/ui';
|
||||
import { isUnifiedMode } from '../../config/unified-config-loader';
|
||||
|
||||
import { exitWithError } from '../../errors';
|
||||
import { ExitCode } from '../../errors/exit-codes';
|
||||
import { CommandContext, parseArgs } from './types';
|
||||
import { isUnifiedMode } from '../../config/config-loader-facade';
|
||||
|
||||
/**
|
||||
* Handle the default command (set default profile)
|
||||
|
||||
@@ -8,10 +8,11 @@ import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { initUI, color, ok, fail, info } from '../../utils/ui';
|
||||
import { InteractivePrompt } from '../../utils/prompt';
|
||||
import { isUnifiedMode } from '../../config/unified-config-loader';
|
||||
|
||||
import { exitWithError } from '../../errors';
|
||||
import { ExitCode } from '../../errors/exit-codes';
|
||||
import { CommandContext, parseArgs } from './types';
|
||||
import { isUnifiedMode } from '../../config/config-loader-facade';
|
||||
|
||||
/**
|
||||
* Handle the remove command
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import * as fs from 'fs';
|
||||
import {
|
||||
getConfigJsonPath,
|
||||
getContinuityInheritanceMap,
|
||||
isUnifiedMode,
|
||||
} from '../config/unified-config-loader';
|
||||
|
||||
import { warn } from '../utils/ui';
|
||||
import InstanceManager from '../management/instance-manager';
|
||||
import ProfileRegistry from './profile-registry';
|
||||
import { isAccountContextMetadata, resolveAccountContextPolicy } from './account-context';
|
||||
import type { ProfileType } from '../types/profile';
|
||||
import { getProfileLookupCandidates, resolveAliasToCanonical } from '../utils/profile-compat';
|
||||
import {
|
||||
getConfigJsonPath,
|
||||
getContinuityInheritanceMap,
|
||||
isUnifiedMode,
|
||||
} from '../config/config-loader-facade';
|
||||
|
||||
export interface ProfileContinuityInheritanceInput {
|
||||
profileName: string;
|
||||
|
||||
@@ -21,8 +21,7 @@ import {
|
||||
CompositeVariantConfig,
|
||||
CompositeTierConfig,
|
||||
} from '../config/unified-config-types';
|
||||
import { loadUnifiedConfig, isUnifiedMode, getCursorConfig } from '../config/unified-config-loader';
|
||||
import { getCcsDir } from '../utils/config-manager';
|
||||
|
||||
import { getProfileLookupCandidates, isLegacyProfileAlias } from '../utils/profile-compat';
|
||||
import type { CLIProxyProvider } from '../cliproxy/types';
|
||||
import { CLIPROXY_PROVIDER_IDS, isCLIProxyProvider } from '../cliproxy/provider-capabilities';
|
||||
@@ -30,6 +29,12 @@ import { LEGACY_CURSOR_PROFILE_NAME } from '../cursor/constants';
|
||||
import { normalizeCopilotModelId } from '../copilot/copilot-model-normalizer';
|
||||
import type { TargetType } from '../targets/target-adapter';
|
||||
import type { ProfileType } from '../types/profile';
|
||||
import {
|
||||
getCcsDir,
|
||||
getCursorConfig,
|
||||
isUnifiedMode,
|
||||
loadUnifiedConfig,
|
||||
} from '../config/config-loader-facade';
|
||||
export type { ProfileType } from '../types/profile';
|
||||
|
||||
/** CLIProxy profile names (OAuth-based, zero config) */
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { ProfileMetadata } from '../types';
|
||||
import {
|
||||
loadOrCreateUnifiedConfig,
|
||||
mutateUnifiedConfig,
|
||||
isUnifiedMode,
|
||||
} from '../config/unified-config-loader';
|
||||
|
||||
import type { AccountConfig } from '../config/unified-config-types';
|
||||
import { getCcsDir } from '../utils/config-manager';
|
||||
|
||||
import { isValidContextGroupName, normalizeContextGroupName } from './account-context';
|
||||
import { createLogger } from '../services/logging';
|
||||
import {
|
||||
getCcsDir,
|
||||
isUnifiedMode,
|
||||
loadOrCreateUnifiedConfig,
|
||||
mutateConfig,
|
||||
} from '../config/config-loader-facade';
|
||||
|
||||
const logger = createLogger('auth:profile-registry');
|
||||
|
||||
@@ -323,7 +325,7 @@ export class ProfileRegistry {
|
||||
* Create account in unified config (config.yaml)
|
||||
*/
|
||||
createAccountUnified(name: string, metadata: CreateMetadata = {}): void {
|
||||
mutateUnifiedConfig((config) => {
|
||||
mutateConfig((config) => {
|
||||
if (config.accounts[name]) {
|
||||
throw new Error(`Account already exists: ${name}`);
|
||||
}
|
||||
@@ -342,7 +344,7 @@ export class ProfileRegistry {
|
||||
* Update account metadata in unified config
|
||||
*/
|
||||
updateAccountUnified(name: string, updates: Partial<AccountConfig>): void {
|
||||
mutateUnifiedConfig((config) => {
|
||||
mutateConfig((config) => {
|
||||
if (!config.accounts[name]) {
|
||||
throw new Error(`Account not found: ${name}`);
|
||||
}
|
||||
@@ -357,7 +359,7 @@ export class ProfileRegistry {
|
||||
* Remove account from unified config
|
||||
*/
|
||||
removeAccountUnified(name: string): void {
|
||||
mutateUnifiedConfig((config) => {
|
||||
mutateConfig((config) => {
|
||||
if (!config.accounts[name]) {
|
||||
throw new Error(`Account not found: ${name}`);
|
||||
}
|
||||
@@ -372,7 +374,7 @@ export class ProfileRegistry {
|
||||
* Set default profile in unified config
|
||||
*/
|
||||
setDefaultUnified(name: string): void {
|
||||
mutateUnifiedConfig((config) => {
|
||||
mutateConfig((config) => {
|
||||
const exists =
|
||||
config.accounts[name] || config.profiles[name] || config.cliproxy?.variants?.[name];
|
||||
if (!exists) {
|
||||
@@ -386,7 +388,7 @@ export class ProfileRegistry {
|
||||
* Clear default profile in unified config (restore original CCS behavior)
|
||||
*/
|
||||
clearDefaultUnified(): void {
|
||||
mutateUnifiedConfig((config) => {
|
||||
mutateConfig((config) => {
|
||||
config.default = undefined;
|
||||
});
|
||||
}
|
||||
@@ -426,7 +428,7 @@ export class ProfileRegistry {
|
||||
* Update account last_used in unified config
|
||||
*/
|
||||
touchAccountUnified(name: string): void {
|
||||
mutateUnifiedConfig((config) => {
|
||||
mutateConfig((config) => {
|
||||
if (!config.accounts[name]) {
|
||||
throw new Error(`Account not found: ${name}`);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { getDefaultClaudeConfigDir } from '../utils/claude-config-path';
|
||||
import { getCcsDir } from '../utils/config-manager';
|
||||
|
||||
import InstanceManager from '../management/instance-manager';
|
||||
import ProfileDetector from './profile-detector';
|
||||
import { resolveConfiguredContinuitySourceAccount } from './profile-continuity-inheritance';
|
||||
import type { ProfileType } from '../types/profile';
|
||||
import { getCcsDir } from '../config/config-loader-facade';
|
||||
|
||||
export type ResumeLaneKind =
|
||||
| 'native'
|
||||
|
||||
+8
-11
@@ -2,12 +2,7 @@ import './utils/fetch-proxy-setup';
|
||||
|
||||
import * as fs from 'fs';
|
||||
import { detectClaudeCli } from './utils/claude-detector';
|
||||
import {
|
||||
getSettingsPath,
|
||||
loadSettings,
|
||||
setGlobalConfigDir,
|
||||
detectCloudSyncPath,
|
||||
} from './utils/config-manager';
|
||||
import { getSettingsPath, setGlobalConfigDir, detectCloudSyncPath } from './utils/config-manager';
|
||||
import { expandPath } from './utils/helpers';
|
||||
import {
|
||||
validateGlmKey,
|
||||
@@ -46,11 +41,7 @@ import {
|
||||
resolveOptionalBrowserAttachRuntime,
|
||||
syncBrowserMcpToConfigDir,
|
||||
} from './utils/browser';
|
||||
import {
|
||||
getBrowserConfig,
|
||||
getGlobalEnvConfig,
|
||||
getOfficialChannelsConfig,
|
||||
} from './config/unified-config-loader';
|
||||
|
||||
import {
|
||||
ensureProfileHooks as ensureImageAnalyzerHooks,
|
||||
removeImageAnalysisProfileHook,
|
||||
@@ -122,6 +113,12 @@ import {
|
||||
checkCachedUpdate,
|
||||
isCacheStale,
|
||||
} from './utils/update-checker';
|
||||
import {
|
||||
getBrowserConfig,
|
||||
getGlobalEnvConfig,
|
||||
getOfficialChannelsConfig,
|
||||
loadSettings,
|
||||
} from './config/config-loader-facade';
|
||||
// Note: npm is now the only supported installation method
|
||||
|
||||
// ========== Profile Detection ==========
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { getCcsDir } from '../utils/config-manager';
|
||||
|
||||
import { getDefaultClaudeConfigDir } from '../utils/claude-config-path';
|
||||
import type { OfficialChannelId } from '../config/unified-config-types';
|
||||
import {
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
getOfficialChannelTokenIds,
|
||||
isOfficialChannelTokenRequired,
|
||||
} from './official-channels-runtime';
|
||||
import { getCcsDir } from '../config/config-loader-facade';
|
||||
|
||||
export type OfficialChannelTokenSource = 'saved_env' | 'process_env' | 'missing';
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import * as path from 'path';
|
||||
import { warn, info } from '../../utils/ui';
|
||||
import { CLIProxyProvider } from '../types';
|
||||
import { loadAccountsRegistry, pauseAccount, resumeAccount } from './registry';
|
||||
import { getCcsDir } from '../../utils/config-manager';
|
||||
import { getCcsDir } from '../../config/config-loader-facade';
|
||||
|
||||
const ISSUE_509_URL = 'https://github.com/kaitranntt/ccs/issues/509';
|
||||
|
||||
@@ -690,7 +690,7 @@ export async function handleQuotaExhaustion(
|
||||
// Dynamic imports to avoid circular dependencies
|
||||
const { applyCooldown, findHealthyAccount } = await import('../quota/quota-manager');
|
||||
const { setDefaultAccount, touchAccount } = await import('./account-manager');
|
||||
const { loadOrCreateUnifiedConfig } = await import('../../config/unified-config-loader');
|
||||
const { loadOrCreateUnifiedConfig } = await import('../../config/config-loader-facade');
|
||||
const config = loadOrCreateUnifiedConfig();
|
||||
const threshold = config.quota_management?.auto?.exhaustion_threshold ?? 5;
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
import { createInterface, Interface } from 'readline';
|
||||
import { fail, info, ok, warn } from '../../utils/ui';
|
||||
import { getCliproxySafetyConfig } from '../../config/unified-config-loader';
|
||||
import { getCliproxySafetyConfig } from '../../config/config-loader-facade';
|
||||
|
||||
export const ANTIGRAVITY_RISK_ISSUE_URL = 'https://github.com/kaitranntt/ccs/issues/509';
|
||||
export const ANTIGRAVITY_ACK_VERSION = '2026-02-24-antigravity-oauth-v2';
|
||||
|
||||
@@ -8,8 +8,9 @@
|
||||
*/
|
||||
|
||||
import { randomBytes } from 'crypto';
|
||||
import { loadOrCreateUnifiedConfig, mutateUnifiedConfig } from '../../config/unified-config-loader';
|
||||
|
||||
import { CCS_INTERNAL_API_KEY, CCS_CONTROL_PANEL_SECRET } from '../config/generator';
|
||||
import { loadOrCreateUnifiedConfig, mutateConfig } from '../../config/config-loader-facade';
|
||||
|
||||
/**
|
||||
* Generate a cryptographically secure token.
|
||||
@@ -87,7 +88,7 @@ export function getEffectiveManagementSecret(): string {
|
||||
* @param apiKey - New API key (or undefined to reset to default)
|
||||
*/
|
||||
export function setGlobalApiKey(apiKey: string | undefined): void {
|
||||
mutateUnifiedConfig((config) => {
|
||||
mutateConfig((config) => {
|
||||
if (!config.cliproxy.auth) {
|
||||
config.cliproxy.auth = {};
|
||||
}
|
||||
@@ -107,7 +108,7 @@ export function setGlobalApiKey(apiKey: string | undefined): void {
|
||||
* @param secret - New management secret (or undefined to reset to default)
|
||||
*/
|
||||
export function setGlobalManagementSecret(secret: string | undefined): void {
|
||||
mutateUnifiedConfig((config) => {
|
||||
mutateConfig((config) => {
|
||||
if (!config.cliproxy.auth) {
|
||||
config.cliproxy.auth = {};
|
||||
}
|
||||
@@ -128,7 +129,7 @@ export function setGlobalManagementSecret(secret: string | undefined): void {
|
||||
* @param apiKey - New API key (or undefined to remove override)
|
||||
*/
|
||||
export function setVariantApiKey(variantName: string, apiKey: string | undefined): void {
|
||||
mutateUnifiedConfig((config) => {
|
||||
mutateConfig((config) => {
|
||||
const variant = config.cliproxy.variants[variantName];
|
||||
|
||||
if (!variant) {
|
||||
@@ -155,7 +156,7 @@ export function setVariantApiKey(variantName: string, apiKey: string | undefined
|
||||
* Removes cliproxy.auth and all variant auth overrides.
|
||||
*/
|
||||
export function resetAuthToDefaults(): void {
|
||||
mutateUnifiedConfig((config) => {
|
||||
mutateConfig((config) => {
|
||||
delete config.cliproxy.auth;
|
||||
|
||||
for (const variantName of Object.keys(config.cliproxy.variants)) {
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
* Returns null if disabled or not configured.
|
||||
*/
|
||||
|
||||
import { loadOrCreateUnifiedConfig } from '../../config/unified-config-loader';
|
||||
import type { TokenRefreshSettings } from '../../config/unified-config-types';
|
||||
import { loadOrCreateUnifiedConfig } from '../../config/config-loader-facade';
|
||||
|
||||
/**
|
||||
* Get token refresh configuration from unified config
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
} from './binary/platform-detector';
|
||||
import { stopProxy } from './services/proxy-lifecycle-service';
|
||||
import { waitForPortFree } from '../utils/port-utils';
|
||||
import { loadOrCreateUnifiedConfig } from '../config/unified-config-loader';
|
||||
|
||||
import {
|
||||
UpdateCheckResult,
|
||||
checkForUpdates,
|
||||
@@ -39,6 +39,7 @@ import {
|
||||
|
||||
import type { CLIProxyBackend } from './types';
|
||||
import { getVersionListCachePath } from './binary/version-cache';
|
||||
import { loadOrCreateUnifiedConfig } from '../config/config-loader-facade';
|
||||
|
||||
export const CLIPROXY_DELETED_PLUS_REPO = 'router-for-me/CLIProxyAPIPlus';
|
||||
export const CLIPROXY_PLUS_FALLBACK_TRACKING_URL = 'https://github.com/kaitranntt/ccs/issues/1062';
|
||||
|
||||
@@ -7,7 +7,7 @@ import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import type { CLIProxyProvider, ProviderModelMapping } from '../types';
|
||||
import { getModelMappingFromConfig, getEnvVarsFromConfig } from '../config/base-config-loader';
|
||||
import { getGlobalEnvConfig } from '../../config/unified-config-loader';
|
||||
|
||||
import { getEffectiveApiKey } from '../auth/auth-token-manager';
|
||||
import { expandPath } from '../../utils/helpers';
|
||||
import { warn } from '../../utils/ui';
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
normalizeIFlowLegacyModelAliases,
|
||||
normalizeModelIdForProvider,
|
||||
} from '../ai-providers/model-id-normalizer';
|
||||
import { getGlobalEnvConfig } from '../../config/config-loader-facade';
|
||||
|
||||
/** Settings file structure for user overrides */
|
||||
interface ProviderSettings {
|
||||
|
||||
@@ -9,11 +9,12 @@ import type { CLIProxyProvider, ProviderConfig } from '../types';
|
||||
import { getProviderDisplayName } from '../provider-capabilities';
|
||||
import { getModelMappingFromConfig } from '../config/base-config-loader';
|
||||
import { AI_PROVIDER_FAMILY_IDS } from '../ai-providers/types';
|
||||
import { loadOrCreateUnifiedConfig } from '../../config/unified-config-loader';
|
||||
|
||||
import { getEffectiveApiKey, getEffectiveManagementSecret } from '../auth/auth-token-manager';
|
||||
import { getDeniedModelIdReasonForProvider } from '../ai-providers/model-id-normalizer';
|
||||
import { getAuthDir, getProviderAuthDir, getConfigPathForPort } from './path-resolver';
|
||||
import { CLIPROXY_DEFAULT_PORT } from './port-manager';
|
||||
import { loadOrCreateUnifiedConfig } from '../../config/config-loader-facade';
|
||||
|
||||
/** Internal API key for CCS-managed requests */
|
||||
export const CCS_INTERNAL_API_KEY = 'ccs-internal-managed';
|
||||
|
||||
@@ -12,8 +12,9 @@ import { getProviderCatalog, supportsModelConfig, ModelEntry } from '../model-ca
|
||||
import { getClaudeEnvVars, resolveProviderSettingsPath } from './config-generator';
|
||||
import { CLIProxyProvider } from '../types';
|
||||
import { initUI, color, bold, dim, ok, info, header } from '../../utils/ui';
|
||||
import { getCcsDir } from '../../utils/config-manager';
|
||||
|
||||
import { normalizeModelIdForProvider } from '../ai-providers/model-id-normalizer';
|
||||
import { getCcsDir } from '../../config/config-loader-facade';
|
||||
|
||||
function canonicalizeModelForProvider(provider: CLIProxyProvider, model: string): string {
|
||||
return normalizeModelIdForProvider(model, provider);
|
||||
|
||||
@@ -4,9 +4,10 @@
|
||||
*/
|
||||
|
||||
import * as path from 'path';
|
||||
import { getCcsDir } from '../../utils/config-manager';
|
||||
|
||||
import type { CLIProxyProvider } from '../types';
|
||||
import { CLIPROXY_DEFAULT_PORT } from './port-manager';
|
||||
import { getCcsDir } from '../../config/config-loader-facade';
|
||||
|
||||
/**
|
||||
* Get CLIProxy base directory
|
||||
|
||||
@@ -6,11 +6,12 @@
|
||||
import type { CLIProxyProvider } from '../types';
|
||||
import { DEFAULT_THINKING_TIER_DEFAULTS } from '../../config/unified-config-types';
|
||||
import type { ThinkingConfig } from '../../config/unified-config-types';
|
||||
import { getThinkingConfig } from '../../config/unified-config-loader';
|
||||
|
||||
import { getModelThinkingSupport, supportsThinking } from '../model-catalog';
|
||||
import { isThinkingOffValue, validateThinking } from '../thinking-validator';
|
||||
import { normalizeModelIdForProvider } from '../ai-providers/model-id-normalizer';
|
||||
import { warn } from '../../utils/ui';
|
||||
import { getThinkingConfig } from '../../config/config-loader-facade';
|
||||
|
||||
/** Model tier types for thinking budget defaults */
|
||||
export type ModelTier = 'opus' | 'sonnet' | 'haiku';
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
resolveOptionalBrowserAttachRuntime,
|
||||
syncBrowserMcpToConfigDir,
|
||||
} from '../../utils/browser';
|
||||
import { getBrowserConfig } from '../../config/unified-config-loader';
|
||||
import { getBrowserConfig } from '../../config/config-loader-facade';
|
||||
|
||||
export interface BrowserLaunchSetupResult {
|
||||
/** CLI override flag if --browser-launch / --no-browser-launch was passed */
|
||||
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
ensureImageAnalysisMcpOrThrow,
|
||||
syncImageAnalysisMcpToConfigDir,
|
||||
} from '../../utils/image-analysis';
|
||||
import { loadOrCreateUnifiedConfig, getThinkingConfig } from '../../config/unified-config-loader';
|
||||
import { loadOrCreateUnifiedConfig, getThinkingConfig } from '../../config/config-loader-facade';
|
||||
import { HttpsTunnelProxy } from '../proxy/https-tunnel-proxy';
|
||||
import { resolveProfileContinuityInheritance } from '../../auth/profile-continuity-inheritance';
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
import * as path from 'path';
|
||||
import { warn } from '../../utils/ui';
|
||||
import { getCcsDir } from '../../utils/config-manager';
|
||||
import { getCcsDir } from '../../config/config-loader-facade';
|
||||
import {
|
||||
ToolSanitizationProxy,
|
||||
type ToolSanitizationProxyConfig,
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
* based on unified config. Used by stats-fetcher, auth-routes, and UI.
|
||||
*/
|
||||
|
||||
import { loadOrCreateUnifiedConfig } from '../../config/unified-config-loader';
|
||||
import type { CliproxyServerConfig } from '../../config/unified-config-types';
|
||||
import {
|
||||
CLIPROXY_DEFAULT_PORT,
|
||||
@@ -15,6 +14,7 @@ import {
|
||||
} from '../config/port-manager';
|
||||
import { getProxyEnvVars } from './proxy-config-resolver';
|
||||
import { getEffectiveManagementSecret } from '../auth/auth-token-manager';
|
||||
import { loadOrCreateUnifiedConfig } from '../../config/config-loader-facade';
|
||||
|
||||
/** Resolved proxy target for making requests */
|
||||
export interface ProxyTarget {
|
||||
|
||||
@@ -25,8 +25,9 @@ import {
|
||||
stripCodexEffortSuffix,
|
||||
} from '../ai-providers/model-id-normalizer';
|
||||
import { getModelMaxLevel } from '../model-catalog';
|
||||
import { getCcsDir } from '../../utils/config-manager';
|
||||
|
||||
import { createLogger } from '../../services/logging';
|
||||
import { getCcsDir } from '../../config/config-loader-facade';
|
||||
|
||||
export interface ToolSanitizationProxyConfig {
|
||||
/** Upstream CLIProxy URL */
|
||||
|
||||
@@ -32,8 +32,9 @@ import {
|
||||
touchAccount,
|
||||
type AccountInfo,
|
||||
} from '../accounts/account-manager';
|
||||
import { loadOrCreateUnifiedConfig } from '../../config/unified-config-loader';
|
||||
|
||||
import type { RuntimeMonitorConfig } from '../../config/unified-config-types';
|
||||
import { loadOrCreateUnifiedConfig } from '../../config/config-loader-facade';
|
||||
|
||||
export type ManagedQuotaProvider = 'agy' | 'claude' | 'codex' | 'gemini' | 'ghcp';
|
||||
type ManagedQuotaResult =
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { mutateUnifiedConfig, loadOrCreateUnifiedConfig } from '../../config/unified-config-loader';
|
||||
import { regenerateConfig } from '../config/generator';
|
||||
import { getAuthDir, getConfigPathForPort } from '../config/path-resolver';
|
||||
import {
|
||||
@@ -7,6 +6,7 @@ import {
|
||||
getRoutingErrorMessage,
|
||||
} from './routing-strategy-http';
|
||||
import type { CliproxyRoutingStrategy } from '../types';
|
||||
import { loadOrCreateUnifiedConfig, mutateConfig } from '../../config/config-loader-facade';
|
||||
|
||||
export const DEFAULT_CLIPROXY_ROUTING_STRATEGY: CliproxyRoutingStrategy = 'round-robin';
|
||||
export const DEFAULT_CLIPROXY_SESSION_AFFINITY_ENABLED = false;
|
||||
@@ -223,7 +223,7 @@ export async function applyCliproxyRoutingStrategy(
|
||||
};
|
||||
}
|
||||
|
||||
mutateUnifiedConfig((config) => {
|
||||
mutateConfig((config) => {
|
||||
if (config.cliproxy) {
|
||||
config.cliproxy.routing = { ...config.cliproxy.routing, strategy };
|
||||
}
|
||||
@@ -278,7 +278,7 @@ export async function applyCliproxySessionAffinitySettings(
|
||||
current.ttl ??
|
||||
DEFAULT_CLIPROXY_SESSION_AFFINITY_TTL;
|
||||
|
||||
mutateUnifiedConfig((config) => {
|
||||
mutateConfig((config) => {
|
||||
if (config.cliproxy) {
|
||||
config.cliproxy.routing = {
|
||||
...config.cliproxy.routing,
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
} from '../binary-manager';
|
||||
import { BACKEND_CONFIG, DEFAULT_BACKEND } from '../binary/platform-detector';
|
||||
import { CLIProxyBackend } from '../types';
|
||||
import { loadOrCreateUnifiedConfig } from '../../config/unified-config-loader';
|
||||
import { loadOrCreateUnifiedConfig } from '../../config/config-loader-facade';
|
||||
|
||||
/** Binary status result */
|
||||
export interface BinaryStatusResult {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { getCcsDir } from '../../utils/config-manager';
|
||||
|
||||
import type { CLIProxyProvider } from '../types';
|
||||
import type { ModelEntry, ProviderCatalog, ThinkingSupport } from '../model-catalog';
|
||||
import { MODEL_CATALOG } from '../model-catalog';
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
buildProxyUrl,
|
||||
getProxyTarget,
|
||||
} from '../proxy/proxy-target-resolver';
|
||||
import { getCcsDir } from '../../config/config-loader-facade';
|
||||
|
||||
const CACHE_FILE_NAME = 'model-catalog-cache.json';
|
||||
const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as fs from 'fs';
|
||||
import { getConfigPath, loadConfigSafe } from '../../utils/config-manager';
|
||||
import { getConfigPath } from '../../utils/config-manager';
|
||||
import { CLIProxyProvider } from '../types';
|
||||
import type { TargetType } from '../../targets/target-adapter';
|
||||
import {
|
||||
@@ -8,12 +8,14 @@ import {
|
||||
CompositeTierConfig,
|
||||
CLIPROXY_SUPPORTED_PROVIDERS,
|
||||
} from '../../config/unified-config-types';
|
||||
import {
|
||||
loadOrCreateUnifiedConfig,
|
||||
mutateUnifiedConfig,
|
||||
isUnifiedMode,
|
||||
} from '../../config/unified-config-loader';
|
||||
|
||||
import { CLIPROXY_DEFAULT_PORT } from '../config/config-generator';
|
||||
import {
|
||||
isUnifiedMode,
|
||||
loadConfigSafe,
|
||||
loadOrCreateUnifiedConfig,
|
||||
mutateConfig,
|
||||
} from '../../config/config-loader-facade';
|
||||
|
||||
export const VARIANT_PORT_BASE = CLIPROXY_DEFAULT_PORT + 1;
|
||||
export const VARIANT_PORT_MAX_OFFSET = 100;
|
||||
@@ -171,7 +173,7 @@ export function listVariantsFromConfig(): Record<string, VariantConfig> {
|
||||
}
|
||||
|
||||
export function saveCompositeVariantUnified(name: string, config: CompositeVariantConfig): void {
|
||||
mutateUnifiedConfig((unifiedConfig) => {
|
||||
mutateConfig((unifiedConfig) => {
|
||||
if (!unifiedConfig.cliproxy) {
|
||||
unifiedConfig.cliproxy = {
|
||||
oauth_accounts: {},
|
||||
@@ -195,7 +197,7 @@ export function saveVariantUnified(
|
||||
port?: number,
|
||||
target: TargetType = 'claude'
|
||||
): void {
|
||||
mutateUnifiedConfig((config) => {
|
||||
mutateConfig((config) => {
|
||||
if (!config.cliproxy) {
|
||||
config.cliproxy = {
|
||||
oauth_accounts: {},
|
||||
@@ -266,7 +268,7 @@ export function saveVariantLegacy(
|
||||
|
||||
export function removeVariantFromUnifiedConfig(name: string): VariantConfig | null {
|
||||
let removedVariant: CLIProxyVariantConfig | CompositeVariantConfig | null = null;
|
||||
mutateUnifiedConfig((config) => {
|
||||
mutateConfig((config) => {
|
||||
if (!config.cliproxy?.variants || !(name in config.cliproxy.variants)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -12,11 +12,11 @@ import { CLIProxyProvider, PLUS_ONLY_PROVIDERS } from '../types';
|
||||
import { CompositeTierConfig, CompositeVariantConfig } from '../../config/unified-config-types';
|
||||
import type { TargetType } from '../../targets/target-adapter';
|
||||
import { isReservedName, isWindowsReservedName } from '../../config/reserved-names';
|
||||
import { isUnifiedMode } from '../../config/unified-config-loader';
|
||||
|
||||
import { deleteConfigForPort } from '../config/config-generator';
|
||||
import { hasActiveSessions, deleteSessionLockForPort } from '../session-tracker';
|
||||
import { warn } from '../../utils/ui';
|
||||
import { getCcsDir } from '../../utils/config-manager';
|
||||
|
||||
import { validateCompositeTiers } from '../config/composite-validator';
|
||||
import {
|
||||
canonicalizeModelIdForProvider,
|
||||
@@ -43,6 +43,7 @@ import {
|
||||
getNextAvailablePort,
|
||||
} from './variant-config-adapter';
|
||||
import { getConfiguredBackend, getPlusBackendUnavailableMessage } from '../binary-manager';
|
||||
import { getCcsDir, isUnifiedMode } from '../../config/config-loader-facade';
|
||||
|
||||
// Re-export VariantConfig from adapter
|
||||
export type { VariantConfig } from './variant-config-adapter';
|
||||
|
||||
@@ -9,7 +9,7 @@ import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
import { CLIProxyProfileName } from '../../auth/profile-detector';
|
||||
import { getCcsDir } from '../../utils/config-manager';
|
||||
|
||||
import { expandPath } from '../../utils/helpers';
|
||||
import { getClaudeEnvVars, CLIPROXY_DEFAULT_PORT } from '../config/config-generator';
|
||||
import { CLIProxyProvider } from '../types';
|
||||
@@ -24,6 +24,7 @@ import { prepareImageAnalysisFallbackHook } from '../../utils/hooks';
|
||||
import { getEffectiveApiKey } from '../auth/auth-token-manager';
|
||||
import { warn } from '../../utils/ui';
|
||||
import { normalizeModelIdForProvider } from '../ai-providers/model-id-normalizer';
|
||||
import { getCcsDir } from '../../config/config-loader-facade';
|
||||
|
||||
/** Environment settings structure */
|
||||
interface SettingsEnv {
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
|
||||
import * as chokidar from 'chokidar';
|
||||
import * as path from 'path';
|
||||
import { getCcsDir } from '../../utils/config-manager';
|
||||
import { loadOrCreateUnifiedConfig } from '../../config/unified-config-loader';
|
||||
|
||||
import { syncToLocalConfig } from './local-config-sync';
|
||||
import { getCcsDir, loadOrCreateUnifiedConfig } from '../../config/config-loader-facade';
|
||||
|
||||
/** Debounce delay in milliseconds */
|
||||
const DEBOUNCE_MS = 3000;
|
||||
|
||||
@@ -6,10 +6,11 @@
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { getCcsDir } from '../../utils/config-manager';
|
||||
|
||||
import { expandPath } from '../../utils/helpers';
|
||||
import { listApiProfiles, isApiProfileConfigured } from '../../api/services/profile-reader';
|
||||
import type { ClaudeKey } from '../management/management-api-types';
|
||||
import { getCcsDir } from '../../config/config-loader-facade';
|
||||
|
||||
/**
|
||||
* Profile info with settings for sync.
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import * as browserUtils from '../utils/browser';
|
||||
import { getBrowserConfig, mutateUnifiedConfig } from '../config/unified-config-loader';
|
||||
|
||||
import type { BrowserToolPolicy } from '../config/unified-config-types';
|
||||
import { getCcsPathDisplay } from '../utils/config-manager';
|
||||
import { getNodePlatformKey } from '../utils/browser/platform';
|
||||
import { color, dim, header, initUI, subheader } from '../utils/ui';
|
||||
import { getBrowserConfig, mutateConfig } from '../config/config-loader-facade';
|
||||
|
||||
type HelpWriter = (line: string) => void;
|
||||
type BrowserLane = 'claude' | 'codex' | 'all';
|
||||
@@ -216,7 +217,7 @@ function updateBrowserPolicies(updates: {
|
||||
claude?: BrowserToolPolicy;
|
||||
codex?: BrowserToolPolicy;
|
||||
}): void {
|
||||
mutateUnifiedConfig((config) => {
|
||||
mutateConfig((config) => {
|
||||
const current = getBrowserConfig();
|
||||
config.browser = {
|
||||
claude: {
|
||||
@@ -235,7 +236,7 @@ function updateBrowserPolicies(updates: {
|
||||
|
||||
function updateBrowserEnabled(subcommand: 'enable' | 'disable', lane: BrowserLane): void {
|
||||
const nextEnabled = subcommand === 'enable';
|
||||
mutateUnifiedConfig((config) => {
|
||||
mutateConfig((config) => {
|
||||
const current = getBrowserConfig();
|
||||
config.browser = {
|
||||
claude: {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
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';
|
||||
import { loadOrCreateUnifiedConfig } from '../../config/config-loader-facade';
|
||||
|
||||
type LifecyclePortConfig = Pick<UnifiedConfig, 'cliproxy_server'>;
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import type { CliproxyProviderRoutingHints } from '../../shared/cliproxy-model-r
|
||||
import { CLIProxyProvider, CLIProxyBackend } from '../../cliproxy/types';
|
||||
import type { TargetType } from '../../targets/target-adapter';
|
||||
import { getPersistedTargetChoices, isPersistedTargetType } from '../../targets/target-metadata';
|
||||
import { isUnifiedMode } from '../../config/unified-config-loader';
|
||||
|
||||
import { initUI, header, color, ok, fail, warn, info, infoBox, dim } from '../../utils/ui';
|
||||
import { InteractivePrompt } from '../../utils/prompt';
|
||||
import {
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
import { DEFAULT_BACKEND } from '../../cliproxy/binary/platform-detector';
|
||||
import { CompositeTierConfig } from '../../config/unified-config-types';
|
||||
import { formatAccountDisplayName } from '../../cliproxy/accounts/email-account-identity';
|
||||
import { isUnifiedMode } from '../../config/config-loader-facade';
|
||||
|
||||
interface CliproxyProfileArgs {
|
||||
name?: string;
|
||||
|
||||
@@ -5,8 +5,9 @@
|
||||
*/
|
||||
|
||||
import { InteractivePrompt } from '../../utils/prompt';
|
||||
import { getDashboardAuthConfig, mutateUnifiedConfig } from '../../config/unified-config-loader';
|
||||
|
||||
import { initUI, header, ok, info, warn, dim } from '../../utils/ui';
|
||||
import { getDashboardAuthConfig, mutateConfig } from '../../config/config-loader-facade';
|
||||
|
||||
/**
|
||||
* Handle disable command - disable auth with confirmation
|
||||
@@ -53,7 +54,7 @@ export async function handleDisable(): Promise<void> {
|
||||
}
|
||||
|
||||
// Disable auth
|
||||
mutateUnifiedConfig((fullConfig) => {
|
||||
mutateConfig((fullConfig) => {
|
||||
fullConfig.dashboard_auth = {
|
||||
enabled: false,
|
||||
username: fullConfig.dashboard_auth?.username ?? '',
|
||||
|
||||
@@ -8,9 +8,10 @@
|
||||
|
||||
import bcrypt from 'bcrypt';
|
||||
import { InteractivePrompt } from '../../utils/prompt';
|
||||
import { mutateUnifiedConfig } from '../../config/unified-config-loader';
|
||||
|
||||
import { initUI, header, subheader, ok, fail, info, warn, dim } from '../../utils/ui';
|
||||
import type { AuthSetupResult } from './types';
|
||||
import { mutateConfig } from '../../config/config-loader-facade';
|
||||
|
||||
const BCRYPT_ROUNDS = 10;
|
||||
const MIN_PASSWORD_LENGTH = 8;
|
||||
@@ -99,7 +100,7 @@ export async function handleSetup(): Promise<AuthSetupResult> {
|
||||
// Hash password
|
||||
const passwordHash = await bcrypt.hash(password, BCRYPT_ROUNDS);
|
||||
|
||||
const config = mutateUnifiedConfig((currentConfig) => {
|
||||
const config = mutateConfig((currentConfig) => {
|
||||
currentConfig.dashboard_auth = {
|
||||
enabled: true,
|
||||
username,
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
* Display current dashboard authentication status.
|
||||
*/
|
||||
|
||||
import { getDashboardAuthConfig } from '../../config/unified-config-loader';
|
||||
import { initUI, header, subheader, ok, info, warn, dim, color } from '../../utils/ui';
|
||||
import type { AuthStatusInfo } from './types';
|
||||
import { getDashboardAuthConfig } from '../../config/config-loader-facade';
|
||||
|
||||
/**
|
||||
* Get auth status info with ENV override detection
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import { initUI, header, ok, info, warn, fail, subheader, color, dim } from '../utils/ui';
|
||||
import {
|
||||
getOfficialChannelsConfig,
|
||||
loadOrCreateUnifiedConfig,
|
||||
updateUnifiedConfig,
|
||||
} from '../config/unified-config-loader';
|
||||
|
||||
import type { OfficialChannelId } from '../config/unified-config-types';
|
||||
import { DEFAULT_OFFICIAL_CHANNELS_CONFIG } from '../config/unified-config-types';
|
||||
import {
|
||||
@@ -42,6 +38,11 @@ import {
|
||||
isOfficialChannelSelectionValid,
|
||||
} from '../channels/official-channels-runtime';
|
||||
import { extractOption, hasAnyFlag } from './arg-extractor';
|
||||
import {
|
||||
getOfficialChannelsConfig,
|
||||
loadOrCreateUnifiedConfig,
|
||||
updateConfig,
|
||||
} from '../config/config-loader-facade';
|
||||
|
||||
interface ChannelsCommandOptions {
|
||||
enable: boolean;
|
||||
@@ -440,7 +441,7 @@ export async function handleConfigChannelsCommand(args: string[]): Promise<void>
|
||||
|
||||
try {
|
||||
if (hasConfigMutation) {
|
||||
updateUnifiedConfig({ channels: nextConfig });
|
||||
updateConfig({ channels: nextConfig });
|
||||
}
|
||||
|
||||
if (options.setToken) {
|
||||
|
||||
@@ -12,7 +12,7 @@ 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 { getDashboardAuthConfig } from '../config/unified-config-loader';
|
||||
|
||||
import { initUI, header, ok, info, warn, fail } from '../utils/ui';
|
||||
import { resolveNamedCommand, type NamedCommandRoute } from './named-command-router';
|
||||
import {
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
} from './config-dashboard-host';
|
||||
import { parseConfigCommandArgs, showConfigCommandHelp } from './config-command-options';
|
||||
import { createLogger } from '../services/logging';
|
||||
import { getDashboardAuthConfig } from '../config/config-loader-facade';
|
||||
|
||||
const logger = createLogger('command:config');
|
||||
|
||||
|
||||
@@ -6,11 +6,7 @@
|
||||
*/
|
||||
|
||||
import { initUI, header, ok, info, warn, fail, subheader, color, dim } from '../utils/ui';
|
||||
import {
|
||||
getImageAnalysisConfig,
|
||||
updateUnifiedConfig,
|
||||
loadOrCreateUnifiedConfig,
|
||||
} from '../config/unified-config-loader';
|
||||
|
||||
import { DEFAULT_IMAGE_ANALYSIS_CONFIG } from '../config/unified-config-types';
|
||||
import {
|
||||
CLIPROXY_PROVIDER_IDS,
|
||||
@@ -19,6 +15,11 @@ import {
|
||||
} from '../cliproxy/provider-capabilities';
|
||||
import { extractOption, hasAnyFlag } from './arg-extractor';
|
||||
import { normalizeImageAnalysisBackendId } from '../utils/hooks';
|
||||
import {
|
||||
getImageAnalysisConfig,
|
||||
loadOrCreateUnifiedConfig,
|
||||
updateConfig,
|
||||
} from '../config/config-loader-facade';
|
||||
|
||||
interface ImageAnalysisCommandOptions {
|
||||
enable?: boolean;
|
||||
@@ -346,7 +347,7 @@ export async function handleConfigImageAnalysisCommand(args: string[]): Promise<
|
||||
}
|
||||
|
||||
if (hasChanges) {
|
||||
updateUnifiedConfig({ image_analysis: imageConfig });
|
||||
updateConfig({ image_analysis: imageConfig });
|
||||
console.log(ok('Configuration updated'));
|
||||
console.log('');
|
||||
}
|
||||
|
||||
@@ -6,11 +6,7 @@
|
||||
*/
|
||||
|
||||
import { initUI, header, ok, info, warn, fail, subheader, color, dim } from '../utils/ui';
|
||||
import {
|
||||
getThinkingConfig,
|
||||
updateUnifiedConfig,
|
||||
loadOrCreateUnifiedConfig,
|
||||
} from '../config/unified-config-loader';
|
||||
|
||||
import { DEFAULT_THINKING_TIER_DEFAULTS } from '../config/unified-config-types';
|
||||
import { VALID_THINKING_LEVELS } from '../cliproxy/thinking-validator';
|
||||
import {
|
||||
@@ -18,6 +14,11 @@ import {
|
||||
parseThinkingCommandArgs,
|
||||
parseThinkingOverrideInput,
|
||||
} from './config-thinking-parser';
|
||||
import {
|
||||
getThinkingConfig,
|
||||
loadOrCreateUnifiedConfig,
|
||||
updateConfig,
|
||||
} from '../config/config-loader-facade';
|
||||
|
||||
const VALID_THINKING_MODES = ['auto', 'off', 'manual'] as const;
|
||||
|
||||
@@ -293,7 +294,7 @@ export async function handleConfigThinkingCommand(args: string[]): Promise<void>
|
||||
}
|
||||
|
||||
if (hasChanges) {
|
||||
updateUnifiedConfig({ thinking: thinkingConfig });
|
||||
updateConfig({ thinking: thinkingConfig });
|
||||
console.log(ok('Configuration updated'));
|
||||
console.log('');
|
||||
}
|
||||
|
||||
@@ -15,10 +15,11 @@ import {
|
||||
normalizeCopilotConfigWithWarnings,
|
||||
} from '../copilot';
|
||||
import type { CopilotModel } from '../copilot';
|
||||
import { loadOrCreateUnifiedConfig, mutateUnifiedConfig } from '../config/unified-config-loader';
|
||||
|
||||
import { DEFAULT_COPILOT_CONFIG } from '../config/unified-config-types';
|
||||
import { ok, fail, info, color, warn } from '../utils/ui';
|
||||
import { normalizeCopilotSubcommand } from '../copilot/constants';
|
||||
import { loadOrCreateUnifiedConfig, mutateConfig } from '../config/config-loader-facade';
|
||||
|
||||
/**
|
||||
* Handle copilot subcommand.
|
||||
@@ -361,7 +362,7 @@ async function handleStop(): Promise<number> {
|
||||
* Handle enable subcommand.
|
||||
*/
|
||||
async function handleEnable(): Promise<number> {
|
||||
mutateUnifiedConfig((config) => {
|
||||
mutateConfig((config) => {
|
||||
if (!config.copilot) {
|
||||
config.copilot = { ...DEFAULT_COPILOT_CONFIG };
|
||||
}
|
||||
@@ -383,7 +384,7 @@ async function handleEnable(): Promise<number> {
|
||||
* Handle disable subcommand.
|
||||
*/
|
||||
async function handleDisable(): Promise<number> {
|
||||
mutateUnifiedConfig((config) => {
|
||||
mutateConfig((config) => {
|
||||
if (config.copilot) {
|
||||
config.copilot.enabled = false;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
getDefaultModel,
|
||||
probeCursorRuntime,
|
||||
} from '../cursor';
|
||||
import { getCursorConfig, mutateUnifiedConfig } from '../config/unified-config-loader';
|
||||
|
||||
import { DEFAULT_CURSOR_CONFIG } from '../config/unified-config-types';
|
||||
import {
|
||||
renderCursorHelp,
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
renderCursorStatus,
|
||||
} from './cursor-command-display';
|
||||
import { ok, fail, info } from '../utils/ui';
|
||||
import { getCursorConfig, mutateConfig } from '../config/config-loader-facade';
|
||||
|
||||
const LEGACY_CURSOR_COMMAND = 'ccs legacy cursor';
|
||||
const CLIPROXY_CURSOR_COMMAND = 'ccs cursor';
|
||||
@@ -286,7 +287,7 @@ async function handleStop(): Promise<number> {
|
||||
*/
|
||||
async function handleEnable(): Promise<number> {
|
||||
printLegacyCursorDeprecationNotice();
|
||||
mutateUnifiedConfig((config) => {
|
||||
mutateConfig((config) => {
|
||||
if (!config.cursor) {
|
||||
config.cursor = { ...DEFAULT_CURSOR_CONFIG };
|
||||
}
|
||||
@@ -310,7 +311,7 @@ async function handleEnable(): Promise<number> {
|
||||
*/
|
||||
async function handleDisable(): Promise<number> {
|
||||
printLegacyCursorDeprecationNotice();
|
||||
mutateUnifiedConfig((config) => {
|
||||
mutateConfig((config) => {
|
||||
if (config.cursor) {
|
||||
config.cursor.enabled = false;
|
||||
}
|
||||
|
||||
@@ -6,12 +6,13 @@
|
||||
*/
|
||||
|
||||
import { initUI, header, dim, color, subheader, fail, warn } from '../utils/ui';
|
||||
import { getCcsDir } from '../utils/config-manager';
|
||||
|
||||
import { CLAUDE_EXTENSION_HOSTS, type ClaudeExtensionHost } from '../shared/claude-extension-hosts';
|
||||
import {
|
||||
renderClaudeExtensionSettingsJson,
|
||||
resolveClaudeExtensionSetup,
|
||||
} from '../shared/claude-extension-setup';
|
||||
import { getCcsDir } from '../config/config-loader-facade';
|
||||
|
||||
type ShellType = 'bash' | 'fish' | 'powershell';
|
||||
type OutputFormat = 'openai' | 'anthropic' | 'raw' | 'claude-extension';
|
||||
|
||||
@@ -16,8 +16,9 @@ import {
|
||||
needsMigration,
|
||||
getBackupDirectories,
|
||||
} from '../config/migration-manager';
|
||||
import { hasUnifiedConfig } from '../config/unified-config-loader';
|
||||
|
||||
import { initUI, ok, fail, info, warn, infoBox, dim } from '../utils/ui';
|
||||
import { hasUnifiedConfig } from '../config/config-loader-facade';
|
||||
|
||||
export async function handleMigrateCommand(args: string[]): Promise<void> {
|
||||
await initUI();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { detectShell, formatExportLine } from './env-command';
|
||||
import { getSettingsPath, loadSettings } from '../utils/config-manager';
|
||||
import { getSettingsPath } from '../utils/config-manager';
|
||||
import { expandPath } from '../utils/helpers';
|
||||
import { fail, info, ok } from '../utils/ui';
|
||||
import {
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
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);
|
||||
|
||||
@@ -16,15 +16,17 @@ import * as readline from 'readline';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { initUI, header, ok, info, warn } from '../utils/ui';
|
||||
|
||||
import { DEFAULT_CLIPROXY_SERVER_CONFIG } from '../config/unified-config-types';
|
||||
|
||||
import { CLIPROXY_DEFAULT_PORT } from '../cliproxy/config/port-manager';
|
||||
import {
|
||||
getCcsDir,
|
||||
hasUnifiedConfig,
|
||||
loadOrCreateUnifiedConfig,
|
||||
loadUnifiedConfig,
|
||||
mutateUnifiedConfig,
|
||||
hasUnifiedConfig,
|
||||
} from '../config/unified-config-loader';
|
||||
import { DEFAULT_CLIPROXY_SERVER_CONFIG } from '../config/unified-config-types';
|
||||
import { getCcsDir } from '../utils/config-manager';
|
||||
import { CLIPROXY_DEFAULT_PORT } from '../cliproxy/config/port-manager';
|
||||
mutateConfig,
|
||||
} from '../config/config-loader-facade';
|
||||
|
||||
/** Custom error for user cancellation (Ctrl+C) */
|
||||
class UserCancelledError extends Error {
|
||||
@@ -377,7 +379,7 @@ async function runSetupWizard(force: boolean = false): Promise<void> {
|
||||
console.log(' After creating, edit the settings file to add your API key.');
|
||||
}
|
||||
|
||||
mutateUnifiedConfig((currentConfig) => {
|
||||
mutateConfig((currentConfig) => {
|
||||
currentConfig.setup_completed = true;
|
||||
currentConfig.cliproxy_server = config.cliproxy_server;
|
||||
});
|
||||
|
||||
@@ -7,9 +7,10 @@
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import { initUI, header, subheader, color, warn } from '../utils/ui';
|
||||
import { getActiveConfigPath, getCcsDir } from '../utils/config-manager';
|
||||
import { getActiveConfigPath } from '../utils/config-manager';
|
||||
import { getVersion } from '../utils/version';
|
||||
import { getProfileLookupCandidates } from '../utils/profile-compat';
|
||||
import { getCcsDir } from '../config/config-loader-facade';
|
||||
|
||||
/**
|
||||
* Handle version command
|
||||
|
||||
@@ -0,0 +1,339 @@
|
||||
/**
|
||||
* config-getters.ts
|
||||
*
|
||||
* Typed sub-config accessor functions extracted from unified-config-loader.ts
|
||||
* (Phase 5 split — issue #1164).
|
||||
*
|
||||
* All functions read the loaded config via loadOrCreateUnifiedConfig and
|
||||
* return typed sub-configs with defaults applied.
|
||||
*
|
||||
* No I/O beyond what loadOrCreateUnifiedConfig performs internally.
|
||||
*/
|
||||
|
||||
import {
|
||||
DEFAULT_CLIPROXY_SAFETY_CONFIG,
|
||||
DEFAULT_CURSOR_CONFIG,
|
||||
DEFAULT_GLOBAL_ENV,
|
||||
DEFAULT_IMAGE_ANALYSIS_CONFIG,
|
||||
DEFAULT_LOGGING_CONFIG,
|
||||
DEFAULT_OFFICIAL_CHANNELS_CONFIG,
|
||||
DEFAULT_THINKING_CONFIG,
|
||||
} from '../unified-config-types';
|
||||
import type {
|
||||
BrowserConfig,
|
||||
CLIProxySafetyConfig,
|
||||
CursorConfig,
|
||||
DashboardAuthConfig,
|
||||
GlobalEnvConfig,
|
||||
ImageAnalysisConfig,
|
||||
LoggingConfig,
|
||||
OfficialChannelsConfig,
|
||||
ThinkingConfig,
|
||||
} from '../unified-config-types';
|
||||
import { canonicalizeBrowserConfig } from './normalizers';
|
||||
import { canonicalizeImageAnalysisConfig } from '../../utils/hooks/image-analysis-backend-resolver';
|
||||
import { normalizeOfficialChannelIds } from '../../channels/official-channels-runtime';
|
||||
import { normalizeSearxngBaseUrl } from '../../utils/websearch/types';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Circular-import safety: loadOrCreateUnifiedConfig lives in
|
||||
// unified-config-loader.ts which imports this file. We break the cycle by
|
||||
// using a lazy require() inside getConfig() so the module is resolved at
|
||||
// call time (after both modules have finished loading) rather than at import
|
||||
// time. This also preserves spy/mock compatibility: test spies replace the
|
||||
// function on the module namespace object, and require() returns that live
|
||||
// namespace, so the spy is always picked up.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function getConfig(): import('../unified-config-types').UnifiedConfig {
|
||||
const loader = require('../unified-config-loader') as {
|
||||
loadOrCreateUnifiedConfig: () => import('../unified-config-types').UnifiedConfig;
|
||||
};
|
||||
return loader.loadOrCreateUnifiedConfig();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// GeminiWebSearchInfo interface
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Gemini CLI WebSearch configuration
|
||||
*/
|
||||
export interface GeminiWebSearchInfo {
|
||||
enabled: boolean;
|
||||
model: string;
|
||||
timeout: number;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Accessor functions
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Get websearch configuration.
|
||||
* Returns defaults if not configured.
|
||||
* Supports deterministic providers and optional Gemini/OpenCode/Grok CLI fallbacks.
|
||||
*/
|
||||
export function getWebSearchConfig(): {
|
||||
enabled: boolean;
|
||||
providers?: {
|
||||
exa?: { enabled?: boolean; max_results?: number };
|
||||
tavily?: { enabled?: boolean; max_results?: number };
|
||||
brave?: { enabled?: boolean; max_results?: number };
|
||||
searxng?: { enabled?: boolean; url?: string; max_results?: number };
|
||||
duckduckgo?: { enabled?: boolean; max_results?: number };
|
||||
gemini?: GeminiWebSearchInfo;
|
||||
opencode?: { enabled?: boolean; model?: string; timeout?: number };
|
||||
grok?: { enabled?: boolean; timeout?: number };
|
||||
};
|
||||
// Legacy fields (deprecated)
|
||||
gemini?: { enabled?: boolean; timeout?: number };
|
||||
} {
|
||||
const config = getConfig();
|
||||
|
||||
// Build provider configs
|
||||
const exaConfig = {
|
||||
enabled: config.websearch?.providers?.exa?.enabled ?? false,
|
||||
max_results: config.websearch?.providers?.exa?.max_results ?? 5,
|
||||
};
|
||||
|
||||
const tavilyConfig = {
|
||||
enabled: config.websearch?.providers?.tavily?.enabled ?? false,
|
||||
max_results: config.websearch?.providers?.tavily?.max_results ?? 5,
|
||||
};
|
||||
|
||||
const duckDuckGoConfig = {
|
||||
enabled: config.websearch?.providers?.duckduckgo?.enabled ?? true,
|
||||
max_results: config.websearch?.providers?.duckduckgo?.max_results ?? 5,
|
||||
};
|
||||
|
||||
const braveConfig = {
|
||||
enabled: config.websearch?.providers?.brave?.enabled ?? false,
|
||||
max_results: config.websearch?.providers?.brave?.max_results ?? 5,
|
||||
};
|
||||
|
||||
const searxngConfig = {
|
||||
enabled: config.websearch?.providers?.searxng?.enabled ?? false,
|
||||
url: normalizeSearxngBaseUrl(config.websearch?.providers?.searxng?.url) ?? '',
|
||||
max_results: config.websearch?.providers?.searxng?.max_results ?? 5,
|
||||
};
|
||||
|
||||
const geminiConfig: GeminiWebSearchInfo = {
|
||||
enabled:
|
||||
config.websearch?.providers?.gemini?.enabled ?? config.websearch?.gemini?.enabled ?? false,
|
||||
model: config.websearch?.providers?.gemini?.model ?? 'gemini-2.5-flash',
|
||||
timeout:
|
||||
config.websearch?.providers?.gemini?.timeout ?? config.websearch?.gemini?.timeout ?? 55,
|
||||
};
|
||||
|
||||
const opencodeConfig = {
|
||||
enabled: config.websearch?.providers?.opencode?.enabled ?? false,
|
||||
model: config.websearch?.providers?.opencode?.model ?? 'opencode/grok-code',
|
||||
timeout: config.websearch?.providers?.opencode?.timeout ?? 90,
|
||||
};
|
||||
|
||||
const grokConfig = {
|
||||
enabled: config.websearch?.providers?.grok?.enabled ?? false,
|
||||
timeout: config.websearch?.providers?.grok?.timeout ?? 55,
|
||||
};
|
||||
|
||||
// Auto-enable master switch if ANY provider is enabled
|
||||
const anyProviderEnabled =
|
||||
exaConfig.enabled ||
|
||||
tavilyConfig.enabled ||
|
||||
braveConfig.enabled ||
|
||||
searxngConfig.enabled ||
|
||||
duckDuckGoConfig.enabled ||
|
||||
geminiConfig.enabled ||
|
||||
opencodeConfig.enabled ||
|
||||
grokConfig.enabled;
|
||||
const enabled = anyProviderEnabled && (config.websearch?.enabled ?? true);
|
||||
|
||||
return {
|
||||
enabled,
|
||||
providers: {
|
||||
exa: exaConfig,
|
||||
tavily: tavilyConfig,
|
||||
brave: braveConfig,
|
||||
searxng: searxngConfig,
|
||||
duckduckgo: duckDuckGoConfig,
|
||||
gemini: geminiConfig,
|
||||
opencode: opencodeConfig,
|
||||
grok: grokConfig,
|
||||
},
|
||||
// Legacy field for backwards compatibility
|
||||
gemini: config.websearch?.gemini,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get global_env configuration.
|
||||
* Returns defaults if not configured.
|
||||
*/
|
||||
export function getGlobalEnvConfig(): GlobalEnvConfig {
|
||||
const config = getConfig();
|
||||
return {
|
||||
enabled: config.global_env?.enabled ?? true,
|
||||
env: config.global_env?.env ?? { ...DEFAULT_GLOBAL_ENV },
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get continuity inheritance mapping.
|
||||
* Returns empty mapping when not configured.
|
||||
*/
|
||||
export function getContinuityInheritanceMap(): Record<string, string> {
|
||||
const config = getConfig();
|
||||
return config.continuity?.inherit_from_account ?? {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cliproxy safety configuration.
|
||||
* Returns defaults if not configured.
|
||||
*/
|
||||
export function getCliproxySafetyConfig(): CLIProxySafetyConfig {
|
||||
const config = getConfig();
|
||||
return {
|
||||
antigravity_ack_bypass:
|
||||
config.cliproxy?.safety?.antigravity_ack_bypass ??
|
||||
DEFAULT_CLIPROXY_SAFETY_CONFIG.antigravity_ack_bypass,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get thinking configuration.
|
||||
* Returns defaults if not configured.
|
||||
*/
|
||||
export function getThinkingConfig(): ThinkingConfig {
|
||||
const config = getConfig();
|
||||
|
||||
// W2: Check for invalid thinking config (e.g., thinking: true instead of object)
|
||||
if (config.thinking !== undefined && typeof config.thinking !== 'object') {
|
||||
console.warn(
|
||||
`[!] Invalid thinking config: expected object, got ${typeof config.thinking}. Using defaults.`
|
||||
);
|
||||
console.warn(` Tip: Use 'thinking: { mode: auto }' instead of 'thinking: true'`);
|
||||
return DEFAULT_THINKING_CONFIG;
|
||||
}
|
||||
|
||||
return {
|
||||
mode: config.thinking?.mode ?? DEFAULT_THINKING_CONFIG.mode,
|
||||
override: config.thinking?.override,
|
||||
tier_defaults: {
|
||||
opus: config.thinking?.tier_defaults?.opus ?? DEFAULT_THINKING_CONFIG.tier_defaults.opus,
|
||||
sonnet:
|
||||
config.thinking?.tier_defaults?.sonnet ?? DEFAULT_THINKING_CONFIG.tier_defaults.sonnet,
|
||||
haiku: config.thinking?.tier_defaults?.haiku ?? DEFAULT_THINKING_CONFIG.tier_defaults.haiku,
|
||||
},
|
||||
provider_overrides: config.thinking?.provider_overrides,
|
||||
show_warnings: config.thinking?.show_warnings ?? DEFAULT_THINKING_CONFIG.show_warnings,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Official Channels configuration.
|
||||
* Returns defaults if not configured.
|
||||
*/
|
||||
export function getOfficialChannelsConfig(): OfficialChannelsConfig {
|
||||
const config = getConfig();
|
||||
|
||||
return {
|
||||
selected:
|
||||
config.channels?.selected && config.channels.selected.length > 0
|
||||
? normalizeOfficialChannelIds(config.channels.selected)
|
||||
: DEFAULT_OFFICIAL_CHANNELS_CONFIG.selected,
|
||||
unattended: config.channels?.unattended ?? DEFAULT_OFFICIAL_CHANNELS_CONFIG.unattended,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if dashboard auth is enabled.
|
||||
* Priority: ENV vars > config.yaml > defaults
|
||||
*/
|
||||
export function isDashboardAuthEnabled(): boolean {
|
||||
const envEnabled = process.env.CCS_DASHBOARD_AUTH_ENABLED;
|
||||
|
||||
if (envEnabled !== undefined) {
|
||||
return envEnabled === 'true' || envEnabled === '1';
|
||||
}
|
||||
|
||||
const config = getConfig();
|
||||
return config.dashboard_auth?.enabled ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get dashboard_auth configuration with ENV var override.
|
||||
* Priority: ENV vars > config.yaml > defaults
|
||||
*/
|
||||
export function getDashboardAuthConfig(): DashboardAuthConfig {
|
||||
const config = getConfig();
|
||||
|
||||
// ENV vars take precedence
|
||||
const envEnabled = process.env.CCS_DASHBOARD_AUTH_ENABLED;
|
||||
const envUsername = process.env.CCS_DASHBOARD_USERNAME;
|
||||
const envPasswordHash = process.env.CCS_DASHBOARD_PASSWORD_HASH;
|
||||
|
||||
return {
|
||||
enabled:
|
||||
envEnabled !== undefined
|
||||
? envEnabled === 'true' || envEnabled === '1'
|
||||
: (config.dashboard_auth?.enabled ?? false),
|
||||
username: envUsername ?? config.dashboard_auth?.username ?? '',
|
||||
password_hash: envPasswordHash ?? config.dashboard_auth?.password_hash ?? '',
|
||||
session_timeout_hours: config.dashboard_auth?.session_timeout_hours ?? 24,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get browser automation configuration.
|
||||
* Returns canonicalized defaults if not configured.
|
||||
*/
|
||||
export function getBrowserConfig(): BrowserConfig {
|
||||
const config = getConfig();
|
||||
return canonicalizeBrowserConfig(config.browser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get image_analysis configuration.
|
||||
* Returns defaults if not configured.
|
||||
*/
|
||||
export function getImageAnalysisConfig(): ImageAnalysisConfig {
|
||||
const config = getConfig();
|
||||
|
||||
return canonicalizeImageAnalysisConfig({
|
||||
enabled: config.image_analysis?.enabled ?? DEFAULT_IMAGE_ANALYSIS_CONFIG.enabled,
|
||||
timeout: config.image_analysis?.timeout ?? DEFAULT_IMAGE_ANALYSIS_CONFIG.timeout,
|
||||
provider_models:
|
||||
config.image_analysis?.provider_models ?? DEFAULT_IMAGE_ANALYSIS_CONFIG.provider_models,
|
||||
fallback_backend:
|
||||
config.image_analysis?.fallback_backend ?? DEFAULT_IMAGE_ANALYSIS_CONFIG.fallback_backend,
|
||||
profile_backends:
|
||||
config.image_analysis?.profile_backends ?? DEFAULT_IMAGE_ANALYSIS_CONFIG.profile_backends,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get logging configuration.
|
||||
* Returns defaults if not configured.
|
||||
*/
|
||||
export function getLoggingConfig(): LoggingConfig {
|
||||
const config = getConfig();
|
||||
|
||||
return {
|
||||
enabled: config.logging?.enabled ?? DEFAULT_LOGGING_CONFIG.enabled,
|
||||
level: config.logging?.level ?? DEFAULT_LOGGING_CONFIG.level,
|
||||
rotate_mb: config.logging?.rotate_mb ?? DEFAULT_LOGGING_CONFIG.rotate_mb,
|
||||
retain_days: config.logging?.retain_days ?? DEFAULT_LOGGING_CONFIG.retain_days,
|
||||
redact: config.logging?.redact ?? DEFAULT_LOGGING_CONFIG.redact,
|
||||
live_buffer_size: config.logging?.live_buffer_size ?? DEFAULT_LOGGING_CONFIG.live_buffer_size,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cursor configuration.
|
||||
* Returns defaults if not configured.
|
||||
*/
|
||||
export function getCursorConfig(): CursorConfig {
|
||||
const config = getConfig();
|
||||
return config.cursor ?? { ...DEFAULT_CURSOR_CONFIG };
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
/**
|
||||
* defaults-merger.ts
|
||||
*
|
||||
* mergeWithDefaults function extracted from unified-config-loader.ts
|
||||
* (Phase 4 split — issue #1164).
|
||||
*
|
||||
* Pure transform: no I/O, no side effects. Merges a partial UnifiedConfig
|
||||
* with defaults, filling in missing sections.
|
||||
*
|
||||
* Circular-import note: this module imports from normalizers.ts (Phase 2)
|
||||
* and io-locks.ts (Phase 1) is NOT imported here, so there is no cycle.
|
||||
* io-locks.ts callbacks (mergeWithDefaults, validateCompositeVariants) are
|
||||
* now replaced with direct imports in unified-config-loader.ts (Phase 6).
|
||||
*/
|
||||
|
||||
import {
|
||||
createEmptyUnifiedConfig,
|
||||
DEFAULT_COPILOT_CONFIG,
|
||||
DEFAULT_CURSOR_CONFIG,
|
||||
DEFAULT_GLOBAL_ENV,
|
||||
DEFAULT_CLIPROXY_SERVER_CONFIG,
|
||||
DEFAULT_CLIPROXY_SAFETY_CONFIG,
|
||||
DEFAULT_OPENAI_COMPAT_PROXY_CONFIG,
|
||||
DEFAULT_QUOTA_MANAGEMENT_CONFIG,
|
||||
DEFAULT_THINKING_CONFIG,
|
||||
DEFAULT_DASHBOARD_AUTH_CONFIG,
|
||||
DEFAULT_IMAGE_ANALYSIS_CONFIG,
|
||||
DEFAULT_LOGGING_CONFIG,
|
||||
} from '../unified-config-types';
|
||||
import type { UnifiedConfig } from '../unified-config-types';
|
||||
import { canonicalizeBrowserConfig, normalizeSessionAffinityTtl } from './normalizers';
|
||||
import { normalizeContinuityConfig, normalizeOfficialChannelsConfig } from './normalizers';
|
||||
import type { LegacyDiscordChannelsConfig } from './normalizers';
|
||||
import { canonicalizeImageAnalysisConfig } from '../../utils/hooks/image-analysis-backend-resolver';
|
||||
import { normalizeSearxngBaseUrl } from '../../utils/websearch/types';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// mergeWithDefaults
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Merge partial config with defaults.
|
||||
* Preserves existing data while filling in missing sections.
|
||||
*/
|
||||
export function mergeWithDefaults(partial: Partial<UnifiedConfig>): UnifiedConfig {
|
||||
const defaults = createEmptyUnifiedConfig();
|
||||
const continuity = normalizeContinuityConfig(partial);
|
||||
return {
|
||||
version: partial.version ?? defaults.version,
|
||||
setup_completed: partial.setup_completed,
|
||||
default: partial.default ?? defaults.default,
|
||||
accounts: partial.accounts ?? defaults.accounts,
|
||||
profiles: partial.profiles ?? defaults.profiles,
|
||||
cliproxy: {
|
||||
...partial.cliproxy,
|
||||
oauth_accounts: partial.cliproxy?.oauth_accounts ?? defaults.cliproxy.oauth_accounts,
|
||||
providers: defaults.cliproxy.providers, // Always use defaults for providers
|
||||
variants: partial.cliproxy?.variants ?? defaults.cliproxy.variants,
|
||||
logging: {
|
||||
enabled: partial.cliproxy?.logging?.enabled ?? defaults.cliproxy.logging?.enabled ?? false,
|
||||
request_log:
|
||||
partial.cliproxy?.logging?.request_log ?? defaults.cliproxy.logging?.request_log ?? false,
|
||||
},
|
||||
safety: {
|
||||
antigravity_ack_bypass:
|
||||
partial.cliproxy?.safety?.antigravity_ack_bypass ??
|
||||
DEFAULT_CLIPROXY_SAFETY_CONFIG.antigravity_ack_bypass,
|
||||
},
|
||||
// Kiro browser behavior setting (optional)
|
||||
kiro_no_incognito: partial.cliproxy?.kiro_no_incognito,
|
||||
// Auth config - preserve user values, no defaults (uses constants as fallback)
|
||||
auth: partial.cliproxy?.auth,
|
||||
// Background token refresh config (optional)
|
||||
token_refresh: partial.cliproxy?.token_refresh,
|
||||
// Backend selection - validate and preserve user choice (original vs plus)
|
||||
backend:
|
||||
partial.cliproxy?.backend === 'original' || partial.cliproxy?.backend === 'plus'
|
||||
? partial.cliproxy.backend
|
||||
: undefined, // Invalid values become undefined (defaults to 'original' at runtime)
|
||||
// Auto-sync - default to true
|
||||
auto_sync: partial.cliproxy?.auto_sync ?? defaults.cliproxy.auto_sync ?? true,
|
||||
routing: {
|
||||
strategy:
|
||||
partial.cliproxy?.routing?.strategy === 'fill-first' ||
|
||||
partial.cliproxy?.routing?.strategy === 'round-robin'
|
||||
? partial.cliproxy.routing.strategy
|
||||
: defaults.cliproxy.routing?.strategy,
|
||||
session_affinity:
|
||||
typeof partial.cliproxy?.routing?.session_affinity === 'boolean'
|
||||
? partial.cliproxy.routing.session_affinity
|
||||
: defaults.cliproxy.routing?.session_affinity,
|
||||
session_affinity_ttl: normalizeSessionAffinityTtl(
|
||||
partial.cliproxy?.routing?.session_affinity_ttl,
|
||||
defaults.cliproxy.routing?.session_affinity_ttl ?? '1h'
|
||||
),
|
||||
},
|
||||
},
|
||||
proxy: {
|
||||
port: partial.proxy?.port ?? DEFAULT_OPENAI_COMPAT_PROXY_CONFIG.port,
|
||||
profile_ports: partial.proxy?.profile_ports ?? {
|
||||
...DEFAULT_OPENAI_COMPAT_PROXY_CONFIG.profile_ports,
|
||||
},
|
||||
routing: {
|
||||
default: partial.proxy?.routing?.default ?? defaults.proxy?.routing?.default,
|
||||
background: partial.proxy?.routing?.background ?? defaults.proxy?.routing?.background,
|
||||
think: partial.proxy?.routing?.think ?? defaults.proxy?.routing?.think,
|
||||
longContext: partial.proxy?.routing?.longContext ?? defaults.proxy?.routing?.longContext,
|
||||
webSearch: partial.proxy?.routing?.webSearch ?? defaults.proxy?.routing?.webSearch,
|
||||
longContextThreshold:
|
||||
partial.proxy?.routing?.longContextThreshold ??
|
||||
defaults.proxy?.routing?.longContextThreshold,
|
||||
},
|
||||
},
|
||||
logging: {
|
||||
enabled: partial.logging?.enabled ?? DEFAULT_LOGGING_CONFIG.enabled,
|
||||
level: partial.logging?.level ?? DEFAULT_LOGGING_CONFIG.level,
|
||||
rotate_mb: partial.logging?.rotate_mb ?? DEFAULT_LOGGING_CONFIG.rotate_mb,
|
||||
retain_days: partial.logging?.retain_days ?? DEFAULT_LOGGING_CONFIG.retain_days,
|
||||
redact: partial.logging?.redact ?? DEFAULT_LOGGING_CONFIG.redact,
|
||||
live_buffer_size:
|
||||
partial.logging?.live_buffer_size ?? DEFAULT_LOGGING_CONFIG.live_buffer_size,
|
||||
},
|
||||
preferences: {
|
||||
...defaults.preferences,
|
||||
...partial.preferences,
|
||||
},
|
||||
websearch: {
|
||||
enabled: partial.websearch?.enabled ?? defaults.websearch?.enabled ?? true,
|
||||
providers: {
|
||||
exa: {
|
||||
enabled: partial.websearch?.providers?.exa?.enabled ?? false,
|
||||
max_results: partial.websearch?.providers?.exa?.max_results ?? 5,
|
||||
},
|
||||
tavily: {
|
||||
enabled: partial.websearch?.providers?.tavily?.enabled ?? false,
|
||||
max_results: partial.websearch?.providers?.tavily?.max_results ?? 5,
|
||||
},
|
||||
brave: {
|
||||
enabled: partial.websearch?.providers?.brave?.enabled ?? false,
|
||||
max_results: partial.websearch?.providers?.brave?.max_results ?? 5,
|
||||
},
|
||||
searxng: {
|
||||
enabled: partial.websearch?.providers?.searxng?.enabled ?? false,
|
||||
url: normalizeSearxngBaseUrl(partial.websearch?.providers?.searxng?.url) ?? '',
|
||||
max_results: partial.websearch?.providers?.searxng?.max_results ?? 5,
|
||||
},
|
||||
duckduckgo: {
|
||||
enabled: partial.websearch?.providers?.duckduckgo?.enabled ?? true,
|
||||
max_results: partial.websearch?.providers?.duckduckgo?.max_results ?? 5,
|
||||
},
|
||||
gemini: {
|
||||
enabled:
|
||||
partial.websearch?.providers?.gemini?.enabled ??
|
||||
partial.websearch?.gemini?.enabled ?? // Legacy fallback
|
||||
false,
|
||||
model: partial.websearch?.providers?.gemini?.model ?? 'gemini-2.5-flash',
|
||||
timeout:
|
||||
partial.websearch?.providers?.gemini?.timeout ??
|
||||
partial.websearch?.gemini?.timeout ?? // Legacy fallback
|
||||
55,
|
||||
},
|
||||
opencode: {
|
||||
enabled: partial.websearch?.providers?.opencode?.enabled ?? false,
|
||||
model: partial.websearch?.providers?.opencode?.model ?? 'opencode/grok-code',
|
||||
timeout: partial.websearch?.providers?.opencode?.timeout ?? 90,
|
||||
},
|
||||
grok: {
|
||||
enabled: partial.websearch?.providers?.grok?.enabled ?? false,
|
||||
timeout: partial.websearch?.providers?.grok?.timeout ?? 55,
|
||||
},
|
||||
},
|
||||
// Legacy fields (keep for backwards compatibility during read)
|
||||
gemini: partial.websearch?.gemini,
|
||||
},
|
||||
// Copilot config - strictly opt-in, merge with defaults
|
||||
copilot: {
|
||||
enabled: partial.copilot?.enabled ?? DEFAULT_COPILOT_CONFIG.enabled,
|
||||
auto_start: partial.copilot?.auto_start ?? DEFAULT_COPILOT_CONFIG.auto_start,
|
||||
port: partial.copilot?.port ?? DEFAULT_COPILOT_CONFIG.port,
|
||||
account_type: partial.copilot?.account_type ?? DEFAULT_COPILOT_CONFIG.account_type,
|
||||
rate_limit: partial.copilot?.rate_limit ?? DEFAULT_COPILOT_CONFIG.rate_limit,
|
||||
wait_on_limit: partial.copilot?.wait_on_limit ?? DEFAULT_COPILOT_CONFIG.wait_on_limit,
|
||||
model: partial.copilot?.model ?? DEFAULT_COPILOT_CONFIG.model,
|
||||
},
|
||||
// Cursor config - disabled by default, merge with defaults
|
||||
cursor: {
|
||||
enabled: partial.cursor?.enabled ?? DEFAULT_CURSOR_CONFIG.enabled,
|
||||
port: partial.cursor?.port ?? DEFAULT_CURSOR_CONFIG.port,
|
||||
auto_start: partial.cursor?.auto_start ?? DEFAULT_CURSOR_CONFIG.auto_start,
|
||||
ghost_mode: partial.cursor?.ghost_mode ?? DEFAULT_CURSOR_CONFIG.ghost_mode,
|
||||
model: partial.cursor?.model ?? DEFAULT_CURSOR_CONFIG.model,
|
||||
opus_model: partial.cursor?.opus_model,
|
||||
sonnet_model: partial.cursor?.sonnet_model,
|
||||
haiku_model: partial.cursor?.haiku_model,
|
||||
},
|
||||
// Global env - injected into all non-Claude subscription profiles
|
||||
global_env: {
|
||||
enabled: partial.global_env?.enabled ?? true,
|
||||
env: partial.global_env?.env ?? { ...DEFAULT_GLOBAL_ENV },
|
||||
},
|
||||
continuity,
|
||||
// CLIProxy server config - remote/local CLIProxyAPI settings
|
||||
cliproxy_server: {
|
||||
remote: {
|
||||
enabled:
|
||||
partial.cliproxy_server?.remote?.enabled ?? DEFAULT_CLIPROXY_SERVER_CONFIG.remote.enabled,
|
||||
host: partial.cliproxy_server?.remote?.host ?? DEFAULT_CLIPROXY_SERVER_CONFIG.remote.host,
|
||||
// Port is optional - undefined means use protocol default (443 for HTTPS, 8317 for HTTP)
|
||||
port: partial.cliproxy_server?.remote?.port,
|
||||
protocol:
|
||||
partial.cliproxy_server?.remote?.protocol ??
|
||||
DEFAULT_CLIPROXY_SERVER_CONFIG.remote.protocol,
|
||||
auth_token:
|
||||
partial.cliproxy_server?.remote?.auth_token ??
|
||||
DEFAULT_CLIPROXY_SERVER_CONFIG.remote.auth_token,
|
||||
// management_key is optional - falls back to auth_token when not set
|
||||
management_key: partial.cliproxy_server?.remote?.management_key,
|
||||
},
|
||||
fallback: {
|
||||
enabled:
|
||||
partial.cliproxy_server?.fallback?.enabled ??
|
||||
DEFAULT_CLIPROXY_SERVER_CONFIG.fallback.enabled,
|
||||
auto_start:
|
||||
partial.cliproxy_server?.fallback?.auto_start ??
|
||||
DEFAULT_CLIPROXY_SERVER_CONFIG.fallback.auto_start,
|
||||
},
|
||||
local: {
|
||||
port: partial.cliproxy_server?.local?.port ?? DEFAULT_CLIPROXY_SERVER_CONFIG.local.port,
|
||||
auto_start:
|
||||
partial.cliproxy_server?.local?.auto_start ??
|
||||
DEFAULT_CLIPROXY_SERVER_CONFIG.local.auto_start,
|
||||
},
|
||||
},
|
||||
// Quota management config - hybrid auto+manual account selection
|
||||
quota_management: {
|
||||
mode: partial.quota_management?.mode ?? DEFAULT_QUOTA_MANAGEMENT_CONFIG.mode,
|
||||
auto: {
|
||||
preflight_check:
|
||||
partial.quota_management?.auto?.preflight_check ??
|
||||
DEFAULT_QUOTA_MANAGEMENT_CONFIG.auto.preflight_check,
|
||||
exhaustion_threshold:
|
||||
partial.quota_management?.auto?.exhaustion_threshold ??
|
||||
DEFAULT_QUOTA_MANAGEMENT_CONFIG.auto.exhaustion_threshold,
|
||||
tier_priority:
|
||||
partial.quota_management?.auto?.tier_priority ??
|
||||
DEFAULT_QUOTA_MANAGEMENT_CONFIG.auto.tier_priority,
|
||||
cooldown_minutes:
|
||||
partial.quota_management?.auto?.cooldown_minutes ??
|
||||
DEFAULT_QUOTA_MANAGEMENT_CONFIG.auto.cooldown_minutes,
|
||||
},
|
||||
manual: {
|
||||
paused_accounts:
|
||||
partial.quota_management?.manual?.paused_accounts ??
|
||||
DEFAULT_QUOTA_MANAGEMENT_CONFIG.manual.paused_accounts,
|
||||
forced_default:
|
||||
partial.quota_management?.manual?.forced_default ??
|
||||
DEFAULT_QUOTA_MANAGEMENT_CONFIG.manual.forced_default,
|
||||
tier_lock:
|
||||
partial.quota_management?.manual?.tier_lock ??
|
||||
DEFAULT_QUOTA_MANAGEMENT_CONFIG.manual.tier_lock,
|
||||
},
|
||||
runtime_monitor: {
|
||||
enabled:
|
||||
partial.quota_management?.runtime_monitor?.enabled ??
|
||||
DEFAULT_QUOTA_MANAGEMENT_CONFIG.runtime_monitor.enabled,
|
||||
normal_interval_seconds:
|
||||
partial.quota_management?.runtime_monitor?.normal_interval_seconds ??
|
||||
DEFAULT_QUOTA_MANAGEMENT_CONFIG.runtime_monitor.normal_interval_seconds,
|
||||
critical_interval_seconds:
|
||||
partial.quota_management?.runtime_monitor?.critical_interval_seconds ??
|
||||
DEFAULT_QUOTA_MANAGEMENT_CONFIG.runtime_monitor.critical_interval_seconds,
|
||||
warn_threshold:
|
||||
partial.quota_management?.runtime_monitor?.warn_threshold ??
|
||||
DEFAULT_QUOTA_MANAGEMENT_CONFIG.runtime_monitor.warn_threshold,
|
||||
exhaustion_threshold:
|
||||
partial.quota_management?.runtime_monitor?.exhaustion_threshold ??
|
||||
DEFAULT_QUOTA_MANAGEMENT_CONFIG.runtime_monitor.exhaustion_threshold,
|
||||
cooldown_minutes:
|
||||
partial.quota_management?.runtime_monitor?.cooldown_minutes ??
|
||||
DEFAULT_QUOTA_MANAGEMENT_CONFIG.runtime_monitor.cooldown_minutes,
|
||||
},
|
||||
},
|
||||
// Thinking config - auto/manual/off control for reasoning budget
|
||||
thinking: {
|
||||
mode: partial.thinking?.mode ?? DEFAULT_THINKING_CONFIG.mode,
|
||||
override: partial.thinking?.override,
|
||||
tier_defaults: {
|
||||
opus: partial.thinking?.tier_defaults?.opus ?? DEFAULT_THINKING_CONFIG.tier_defaults.opus,
|
||||
sonnet:
|
||||
partial.thinking?.tier_defaults?.sonnet ?? DEFAULT_THINKING_CONFIG.tier_defaults.sonnet,
|
||||
haiku:
|
||||
partial.thinking?.tier_defaults?.haiku ?? DEFAULT_THINKING_CONFIG.tier_defaults.haiku,
|
||||
},
|
||||
provider_overrides: partial.thinking?.provider_overrides,
|
||||
show_warnings: partial.thinking?.show_warnings ?? DEFAULT_THINKING_CONFIG.show_warnings,
|
||||
},
|
||||
channels: normalizeOfficialChannelsConfig(
|
||||
partial as Partial<UnifiedConfig> & { discord_channels?: LegacyDiscordChannelsConfig }
|
||||
),
|
||||
// Dashboard auth config - disabled by default
|
||||
dashboard_auth: {
|
||||
enabled: partial.dashboard_auth?.enabled ?? DEFAULT_DASHBOARD_AUTH_CONFIG.enabled,
|
||||
username: partial.dashboard_auth?.username ?? DEFAULT_DASHBOARD_AUTH_CONFIG.username,
|
||||
password_hash:
|
||||
partial.dashboard_auth?.password_hash ?? DEFAULT_DASHBOARD_AUTH_CONFIG.password_hash,
|
||||
session_timeout_hours:
|
||||
partial.dashboard_auth?.session_timeout_hours ??
|
||||
DEFAULT_DASHBOARD_AUTH_CONFIG.session_timeout_hours,
|
||||
},
|
||||
browser: canonicalizeBrowserConfig(partial.browser),
|
||||
// Image analysis config - enabled by default for CLIProxy providers
|
||||
image_analysis: canonicalizeImageAnalysisConfig({
|
||||
enabled: partial.image_analysis?.enabled ?? DEFAULT_IMAGE_ANALYSIS_CONFIG.enabled,
|
||||
timeout: partial.image_analysis?.timeout ?? DEFAULT_IMAGE_ANALYSIS_CONFIG.timeout,
|
||||
provider_models:
|
||||
partial.image_analysis?.provider_models ?? DEFAULT_IMAGE_ANALYSIS_CONFIG.provider_models,
|
||||
fallback_backend:
|
||||
partial.image_analysis?.fallback_backend ?? DEFAULT_IMAGE_ANALYSIS_CONFIG.fallback_backend,
|
||||
profile_backends:
|
||||
partial.image_analysis?.profile_backends ?? DEFAULT_IMAGE_ANALYSIS_CONFIG.profile_backends,
|
||||
}),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
/**
|
||||
* io-locks.ts
|
||||
*
|
||||
* Path constants, lockfile management, and config I/O helpers extracted from
|
||||
* unified-config-loader.ts (Phase 1 split — issue #1164).
|
||||
*
|
||||
* Forward-reference note: loadUnifiedConfigWithLockHeld and
|
||||
* writeUnifiedConfigWithLockHeld depend on mergeWithDefaults,
|
||||
* validateCompositeVariants, generateYamlHeader, and generateYamlWithComments
|
||||
* which still live in unified-config-loader.ts (extracted in later phases).
|
||||
* Those are passed as callbacks to avoid a circular import. Once Phases 2–3
|
||||
* land in separate modules with no dependency on this file, they can be
|
||||
* imported directly.
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as crypto from 'crypto';
|
||||
import * as yaml from 'js-yaml';
|
||||
import { getCcsDir } from '../../utils/config-manager';
|
||||
import {
|
||||
isUnifiedConfig,
|
||||
createEmptyUnifiedConfig,
|
||||
UNIFIED_CONFIG_VERSION,
|
||||
} from '../unified-config-types';
|
||||
import type { UnifiedConfig } from '../unified-config-types';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Path constants
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const CONFIG_YAML = 'config.yaml';
|
||||
export const CONFIG_JSON = 'config.json';
|
||||
export const CONFIG_LOCK = 'config.yaml.lock';
|
||||
/** Lock is stale after this many milliseconds */
|
||||
export const LOCK_STALE_MS = 5000;
|
||||
export const GO_DURATION_SEGMENT = String.raw`(?:\d+(?:\.\d+)?(?:ns|us|µs|μs|ms|s|m|h))`;
|
||||
export const GO_DURATION_PATTERN = new RegExp(`^${GO_DURATION_SEGMENT}+$`);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Path helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Get path to unified config.yaml
|
||||
*/
|
||||
export function getConfigYamlPath(): string {
|
||||
return path.join(getCcsDir(), CONFIG_YAML);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get path to legacy config.json
|
||||
*/
|
||||
export function getConfigJsonPath(): string {
|
||||
return path.join(getCcsDir(), CONFIG_JSON);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get path to config lockfile (internal)
|
||||
*/
|
||||
function getLockFilePath(): string {
|
||||
return path.join(getCcsDir(), CONFIG_LOCK);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Process check
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function processExists(pid: number): boolean {
|
||||
try {
|
||||
process.kill(pid, 0);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Lockfile primitives
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Acquire lockfile for config write operations.
|
||||
* Returns a lock token if acquired, null if already locked by another process.
|
||||
* Cleans up stale locks (older than LOCK_STALE_MS).
|
||||
*/
|
||||
export function acquireLock(): string | null {
|
||||
const lockPath = getLockFilePath();
|
||||
const lockDir = path.dirname(lockPath);
|
||||
const lockToken = crypto.randomUUID();
|
||||
const lockData = `${process.pid}\n${Date.now()}\n${lockToken}`;
|
||||
|
||||
try {
|
||||
if (!fs.existsSync(lockDir)) {
|
||||
fs.mkdirSync(lockDir, { recursive: true, mode: 0o700 });
|
||||
}
|
||||
|
||||
// Check if lock exists
|
||||
if (fs.existsSync(lockPath)) {
|
||||
const content = fs.readFileSync(lockPath, 'utf8');
|
||||
const [pidStr, timestampStr] = content.trim().split('\n');
|
||||
const pid = Number.parseInt(pidStr, 10);
|
||||
const timestamp = Number.parseInt(timestampStr, 10);
|
||||
const hasLiveOwner = Number.isInteger(pid) && pid > 0 && processExists(pid);
|
||||
const isStale = !Number.isFinite(timestamp) || Date.now() - timestamp > LOCK_STALE_MS;
|
||||
|
||||
if (hasLiveOwner) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isStale || !hasLiveOwner) {
|
||||
fs.unlinkSync(lockPath);
|
||||
}
|
||||
}
|
||||
|
||||
// Acquire lock
|
||||
fs.writeFileSync(lockPath, lockData, { flag: 'wx', mode: 0o600 });
|
||||
return lockToken;
|
||||
} catch (error) {
|
||||
// EEXIST means another process acquired the lock between our check and write
|
||||
if ((error as NodeJS.ErrnoException).code === 'EEXIST') {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Release lockfile after config write operation.
|
||||
*/
|
||||
export function releaseLock(lockToken: string): void {
|
||||
const lockPath = getLockFilePath();
|
||||
try {
|
||||
if (fs.existsSync(lockPath)) {
|
||||
const content = fs.readFileSync(lockPath, 'utf8');
|
||||
const fileToken = content.trim().split('\n')[2];
|
||||
if (fileToken === lockToken) {
|
||||
fs.unlinkSync(lockPath);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Ignore cleanup errors
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Config format detection
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Check if unified config.yaml exists
|
||||
*/
|
||||
export function hasUnifiedConfig(): boolean {
|
||||
return fs.existsSync(getConfigYamlPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if legacy config.json exists
|
||||
*/
|
||||
export function hasLegacyConfig(): boolean {
|
||||
return fs.existsSync(getConfigJsonPath());
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Sync sleep
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Sync sleep helper for lock retry loops.
|
||||
* Uses Atomics.wait when available to avoid CPU-intensive busy-wait.
|
||||
*/
|
||||
export function sleepSync(ms: number): void {
|
||||
try {
|
||||
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
||||
} catch {
|
||||
const end = Date.now() + ms;
|
||||
while (Date.now() < end) {
|
||||
/* busy-wait */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Lock-aware execution wrapper
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Execute a callback while holding the config lock.
|
||||
*/
|
||||
export function withConfigWriteLock<T>(callback: () => T): T {
|
||||
// Acquire lock (retry for up to 1 second)
|
||||
const maxRetries = 10;
|
||||
const retryDelayMs = 100;
|
||||
let lockToken: string | null = null;
|
||||
for (let i = 0; i < maxRetries; i++) {
|
||||
const acquiredToken = acquireLock();
|
||||
if (acquiredToken) {
|
||||
lockToken = acquiredToken;
|
||||
break;
|
||||
}
|
||||
sleepSync(retryDelayMs);
|
||||
}
|
||||
|
||||
if (!lockToken) {
|
||||
throw new Error('Config file is locked by another process. Wait a moment and try again.');
|
||||
}
|
||||
|
||||
try {
|
||||
return callback();
|
||||
} finally {
|
||||
// Always release lock
|
||||
releaseLock(lockToken);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Lock-held read/write helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Load unified config directly from disk while lock is already held.
|
||||
* Falls back to empty config when file doesn't exist.
|
||||
*
|
||||
* Forward-reference: mergeWithDefaults and validateCompositeVariants are
|
||||
* passed as callbacks to avoid a circular dependency. They will be imported
|
||||
* directly once all phases are complete.
|
||||
*/
|
||||
export function loadUnifiedConfigWithLockHeld(
|
||||
mergeWithDefaults: (partial: Partial<UnifiedConfig>) => UnifiedConfig,
|
||||
validateCompositeVariants: (config: UnifiedConfig) => void
|
||||
): UnifiedConfig {
|
||||
const yamlPath = getConfigYamlPath();
|
||||
if (!fs.existsSync(yamlPath)) {
|
||||
return createEmptyUnifiedConfig();
|
||||
}
|
||||
|
||||
const content = fs.readFileSync(yamlPath, 'utf8');
|
||||
const parsed = yaml.load(content);
|
||||
|
||||
if (!isUnifiedConfig(parsed)) {
|
||||
throw new Error(`Invalid config format in ${yamlPath}`);
|
||||
}
|
||||
|
||||
const merged = mergeWithDefaults(parsed);
|
||||
validateCompositeVariants(merged);
|
||||
return merged;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write unified config to disk while lock is already held.
|
||||
* Uses atomic write (temp file + rename) to prevent corruption.
|
||||
*
|
||||
* Forward-reference: generateYamlHeader and generateYamlWithComments are
|
||||
* passed as callbacks to avoid a circular dependency. They will be imported
|
||||
* directly once all phases are complete.
|
||||
*/
|
||||
export function writeUnifiedConfigWithLockHeld(
|
||||
config: UnifiedConfig,
|
||||
generateYamlHeader: () => string,
|
||||
generateYamlWithComments: (config: UnifiedConfig) => string
|
||||
): void {
|
||||
const yamlPath = getConfigYamlPath();
|
||||
const dir = path.dirname(yamlPath);
|
||||
|
||||
// Ensure directory exists
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
||||
}
|
||||
|
||||
// Ensure version is set
|
||||
config.version = UNIFIED_CONFIG_VERSION;
|
||||
|
||||
// Generate YAML with section comments
|
||||
const yamlContent = generateYamlWithComments(config);
|
||||
const content = generateYamlHeader() + yamlContent;
|
||||
|
||||
// Atomic write: write to temp file, then rename
|
||||
const tempPath = `${yamlPath}.tmp.${process.pid}`;
|
||||
|
||||
try {
|
||||
fs.writeFileSync(tempPath, content, { mode: 0o600 });
|
||||
fs.renameSync(tempPath, yamlPath);
|
||||
} catch (error) {
|
||||
// Clean up temp file on error
|
||||
if (fs.existsSync(tempPath)) {
|
||||
try {
|
||||
fs.unlinkSync(tempPath);
|
||||
} catch {
|
||||
// Ignore cleanup errors
|
||||
}
|
||||
}
|
||||
// Classify filesystem errors
|
||||
const err = error as NodeJS.ErrnoException;
|
||||
if (err.code === 'ENOSPC') {
|
||||
throw new Error('Disk full - cannot save config. Free up space and try again.');
|
||||
} else if (err.code === 'EROFS' || err.code === 'EACCES') {
|
||||
throw new Error(`Cannot write config - check file permissions: ${err.message}`);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
/**
|
||||
* normalizers.ts
|
||||
*
|
||||
* Input normalization and validation helpers extracted from
|
||||
* unified-config-loader.ts (Phase 2 split — issue #1164).
|
||||
*
|
||||
* Contains: browser config normalizers, session affinity TTL normalizer,
|
||||
* composite variant validator, continuity config normalizer, and official
|
||||
* channels config normalizer.
|
||||
*/
|
||||
|
||||
import { DEFAULT_BROWSER_CONFIG, DEFAULT_OFFICIAL_CHANNELS_CONFIG } from '../unified-config-types';
|
||||
import type {
|
||||
UnifiedConfig,
|
||||
BrowserConfig,
|
||||
BrowserEvalMode,
|
||||
BrowserToolPolicy,
|
||||
OfficialChannelsConfig,
|
||||
OfficialChannelId,
|
||||
ContinuityConfig,
|
||||
} from '../unified-config-types';
|
||||
import { validateCompositeTiers } from '../../cliproxy/config/composite-validator';
|
||||
import {
|
||||
isOfficialChannelId,
|
||||
normalizeOfficialChannelIds,
|
||||
resolveLegacyDiscordSelection,
|
||||
} from '../../channels/official-channels-runtime';
|
||||
import { getRecommendedBrowserUserDataDir } from '../../utils/browser/browser-settings';
|
||||
import { GO_DURATION_PATTERN, GO_DURATION_SEGMENT } from './io-locks';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Browser normalizers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export function normalizeBrowserDevtoolsPort(value: number | undefined): number {
|
||||
if (!Number.isFinite(value)) {
|
||||
return DEFAULT_BROWSER_CONFIG.claude.devtools_port;
|
||||
}
|
||||
|
||||
const port = Math.floor(value as number);
|
||||
if (port < 1 || port > 65535) {
|
||||
return DEFAULT_BROWSER_CONFIG.claude.devtools_port;
|
||||
}
|
||||
|
||||
return port;
|
||||
}
|
||||
|
||||
export function normalizeBrowserPolicy(value: string | undefined): BrowserToolPolicy {
|
||||
return value === 'auto' || value === 'manual' ? value : DEFAULT_BROWSER_CONFIG.claude.policy;
|
||||
}
|
||||
|
||||
export function normalizeBrowserEvalMode(value: string | undefined): BrowserEvalMode {
|
||||
if (value === 'disabled' || value === 'readonly' || value === 'readwrite') {
|
||||
return value;
|
||||
}
|
||||
|
||||
return DEFAULT_BROWSER_CONFIG.claude.eval_mode ?? 'readonly';
|
||||
}
|
||||
|
||||
export function canonicalizeBrowserConfig(
|
||||
config?: BrowserConfig,
|
||||
fallback: BrowserConfig = DEFAULT_BROWSER_CONFIG
|
||||
): BrowserConfig {
|
||||
const claudeUserDataDir =
|
||||
config?.claude?.user_data_dir === undefined
|
||||
? fallback.claude.user_data_dir || getRecommendedBrowserUserDataDir()
|
||||
: config.claude.user_data_dir.trim() || getRecommendedBrowserUserDataDir();
|
||||
|
||||
return {
|
||||
claude: {
|
||||
enabled: config?.claude?.enabled ?? fallback.claude.enabled,
|
||||
policy: normalizeBrowserPolicy(config?.claude?.policy ?? fallback.claude.policy),
|
||||
user_data_dir: claudeUserDataDir,
|
||||
devtools_port: normalizeBrowserDevtoolsPort(
|
||||
config?.claude?.devtools_port ?? fallback.claude.devtools_port
|
||||
),
|
||||
eval_mode: normalizeBrowserEvalMode(config?.claude?.eval_mode ?? fallback.claude.eval_mode),
|
||||
},
|
||||
codex: {
|
||||
enabled: config?.codex?.enabled ?? fallback.codex.enabled,
|
||||
policy: normalizeBrowserPolicy(config?.codex?.policy ?? fallback.codex.policy),
|
||||
eval_mode: normalizeBrowserEvalMode(config?.codex?.eval_mode ?? fallback.codex.eval_mode),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Session affinity TTL normalizer
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export function normalizeSessionAffinityTtl(value: unknown, fallback: string): string {
|
||||
if (typeof value !== 'string') {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed || !GO_DURATION_PATTERN.test(trimmed) || !hasPositiveDuration(trimmed)) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
export function hasPositiveDuration(value: string): boolean {
|
||||
const segments = value.match(new RegExp(GO_DURATION_SEGMENT, 'g'));
|
||||
if (!segments) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return segments.some((segment) => {
|
||||
const numeric = parseFloat(segment);
|
||||
return Number.isFinite(numeric) && numeric > 0;
|
||||
});
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Composite variant validator
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Validate composite variant provider strings.
|
||||
* Warns about invalid providers in composite variant configurations.
|
||||
*/
|
||||
export function validateCompositeVariants(config: UnifiedConfig): void {
|
||||
const variants = config.cliproxy?.variants;
|
||||
if (!variants) return;
|
||||
|
||||
for (const [name, variant] of Object.entries(variants)) {
|
||||
if ('type' in variant && variant.type === 'composite') {
|
||||
const error = validateCompositeTiers(variant.tiers, {
|
||||
defaultTier: variant.default_tier,
|
||||
requireAllTiers: true,
|
||||
});
|
||||
if (error) {
|
||||
console.warn(`[!] Variant '${name}': invalid composite config (${error})`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Continuity config normalizer
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Normalize continuity inheritance mapping payload.
|
||||
* Keeps only non-empty string keys and values.
|
||||
*/
|
||||
export function normalizeContinuityInheritanceMap(
|
||||
value: unknown
|
||||
): Record<string, string> | undefined {
|
||||
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const normalized: Record<string, string> = {};
|
||||
for (const [profileName, accountName] of Object.entries(value as Record<string, unknown>)) {
|
||||
const normalizedProfile = profileName.trim();
|
||||
const normalizedAccount = typeof accountName === 'string' ? accountName.trim() : '';
|
||||
|
||||
if (!normalizedProfile || !normalizedAccount) {
|
||||
continue;
|
||||
}
|
||||
|
||||
normalized[normalizedProfile] = normalizedAccount;
|
||||
}
|
||||
|
||||
return Object.keys(normalized).length > 0 ? normalized : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize continuity section.
|
||||
* Supports legacy root key: continuity_inherit_from_account.
|
||||
*/
|
||||
export function normalizeContinuityConfig(
|
||||
partial: Partial<UnifiedConfig>
|
||||
): ContinuityConfig | undefined {
|
||||
const legacyMap = normalizeContinuityInheritanceMap(
|
||||
(partial as Partial<UnifiedConfig> & { continuity_inherit_from_account?: unknown })
|
||||
.continuity_inherit_from_account
|
||||
);
|
||||
const continuityMap = normalizeContinuityInheritanceMap(partial.continuity?.inherit_from_account);
|
||||
|
||||
if (!legacyMap && !continuityMap) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
inherit_from_account: {
|
||||
...(legacyMap ?? {}),
|
||||
...(continuityMap ?? {}),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Official channels config normalizer
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface LegacyDiscordChannelsConfig {
|
||||
enabled?: boolean;
|
||||
unattended?: boolean;
|
||||
}
|
||||
|
||||
export function normalizeOfficialChannelsConfig(
|
||||
partial: Partial<UnifiedConfig> & { discord_channels?: LegacyDiscordChannelsConfig }
|
||||
): OfficialChannelsConfig {
|
||||
const hasCanonicalChannelsSection = partial.channels !== undefined;
|
||||
const hasExplicitSelectedField =
|
||||
hasCanonicalChannelsSection &&
|
||||
Object.prototype.hasOwnProperty.call(partial.channels, 'selected');
|
||||
const rawSelected =
|
||||
hasExplicitSelectedField && Array.isArray(partial.channels?.selected)
|
||||
? partial.channels.selected.filter((value): value is OfficialChannelId =>
|
||||
isOfficialChannelId(value)
|
||||
)
|
||||
: [];
|
||||
|
||||
return {
|
||||
selected: hasCanonicalChannelsSection
|
||||
? normalizeOfficialChannelIds(rawSelected)
|
||||
: resolveLegacyDiscordSelection(partial.discord_channels?.enabled),
|
||||
unattended:
|
||||
partial.channels?.unattended ??
|
||||
partial.discord_channels?.unattended ??
|
||||
DEFAULT_OFFICIAL_CHANNELS_CONFIG.unattended,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,349 @@
|
||||
/**
|
||||
* yaml-serializer.ts
|
||||
*
|
||||
* YAML generation helpers extracted from unified-config-loader.ts
|
||||
* (Phase 3 split — issue #1164).
|
||||
*
|
||||
* Contains: generateYamlHeader, generateYamlWithComments.
|
||||
* These produce the commented YAML written to config.yaml on every save.
|
||||
*/
|
||||
|
||||
import * as yaml from 'js-yaml';
|
||||
import type { UnifiedConfig } from '../unified-config-types';
|
||||
|
||||
/**
|
||||
* Generate YAML header with helpful comments.
|
||||
*/
|
||||
export function generateYamlHeader(): string {
|
||||
return `# CCS Unified Configuration
|
||||
# Docs: https://github.com/kaitranntt/ccs
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate YAML content with section comments for better readability.
|
||||
*/
|
||||
export function generateYamlWithComments(config: UnifiedConfig): string {
|
||||
const lines: string[] = [];
|
||||
|
||||
// Version
|
||||
lines.push(`version: ${config.version}`);
|
||||
if (config.setup_completed !== undefined) {
|
||||
lines.push(`setup_completed: ${config.setup_completed}`);
|
||||
}
|
||||
lines.push('');
|
||||
|
||||
// Default
|
||||
if (config.default) {
|
||||
lines.push(`# Default profile used when running 'ccs' without arguments`);
|
||||
lines.push(`default: "${config.default}"`);
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
// Accounts section
|
||||
lines.push('# ----------------------------------------------------------------------------');
|
||||
lines.push('# Accounts: Isolated Claude instances (each with separate auth/sessions)');
|
||||
lines.push('# Manage with: ccs auth add <name>, ccs auth list, ccs auth remove <name>');
|
||||
lines.push('# ----------------------------------------------------------------------------');
|
||||
lines.push(
|
||||
yaml.dump({ accounts: config.accounts }, { indent: 2, lineWidth: -1, quotingType: '"' }).trim()
|
||||
);
|
||||
lines.push('');
|
||||
|
||||
// Profiles section
|
||||
lines.push('# ----------------------------------------------------------------------------');
|
||||
lines.push('# Profiles: API-based providers (GLM, Kimi, custom endpoints)');
|
||||
lines.push('# Each profile points to a *.settings.json file containing env vars.');
|
||||
lines.push('# Edit the settings file directly to customize (ANTHROPIC_MAX_TOKENS, etc.)');
|
||||
lines.push('# ----------------------------------------------------------------------------');
|
||||
lines.push(
|
||||
yaml.dump({ profiles: config.profiles }, { indent: 2, lineWidth: -1, quotingType: '"' }).trim()
|
||||
);
|
||||
lines.push('');
|
||||
|
||||
// CLIProxy section
|
||||
lines.push('# ----------------------------------------------------------------------------');
|
||||
lines.push('# CLIProxy: OAuth-based providers (gemini, codex, agy, qwen, iflow)');
|
||||
lines.push('# Each variant can reference a *.settings.json file for custom env vars.');
|
||||
lines.push('# Edit the settings file directly to customize model or other settings.');
|
||||
lines.push('# ----------------------------------------------------------------------------');
|
||||
lines.push(
|
||||
yaml.dump({ cliproxy: config.cliproxy }, { indent: 2, lineWidth: -1, quotingType: '"' }).trim()
|
||||
);
|
||||
lines.push('');
|
||||
|
||||
if (config.proxy?.routing) {
|
||||
lines.push('# ----------------------------------------------------------------------------');
|
||||
lines.push('# Proxy Routing: OpenAI-compatible local proxy model selection rules');
|
||||
lines.push('# Use profile:model selectors to force a target profile and upstream model.');
|
||||
lines.push('# ----------------------------------------------------------------------------');
|
||||
lines.push(
|
||||
yaml.dump({ proxy: config.proxy }, { indent: 2, lineWidth: -1, quotingType: '"' }).trim()
|
||||
);
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
if (config.logging) {
|
||||
lines.push('# ----------------------------------------------------------------------------');
|
||||
lines.push('# Logging: CCS-owned structured runtime logs');
|
||||
lines.push('# Current file: ~/.ccs/logs/current.jsonl');
|
||||
lines.push('# Archives rotate automatically and are pruned by retain_days.');
|
||||
lines.push('# This is separate from cliproxy.logging, which controls CLIProxy runtime files.');
|
||||
lines.push('# ----------------------------------------------------------------------------');
|
||||
lines.push(
|
||||
yaml.dump({ logging: config.logging }, { indent: 2, lineWidth: -1, quotingType: '"' }).trim()
|
||||
);
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
// CLIProxy Server section (remote proxy configuration) - placed right after cliproxy
|
||||
if (config.cliproxy_server) {
|
||||
lines.push('# ----------------------------------------------------------------------------');
|
||||
lines.push('# CLIProxy Server: Remote proxy connection settings');
|
||||
lines.push('# Configure via Dashboard (`ccs config`) > Proxy tab.');
|
||||
lines.push('#');
|
||||
lines.push('# remote: Connect to a remote CLIProxyAPI instance');
|
||||
lines.push('# fallback: Use local proxy if remote is unreachable');
|
||||
lines.push('# local: Local proxy settings (port, auto-start)');
|
||||
lines.push('# ----------------------------------------------------------------------------');
|
||||
lines.push(
|
||||
yaml
|
||||
.dump(
|
||||
{ cliproxy_server: config.cliproxy_server },
|
||||
{ indent: 2, lineWidth: -1, quotingType: '"' }
|
||||
)
|
||||
.trim()
|
||||
);
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
// Preferences section
|
||||
lines.push('# ----------------------------------------------------------------------------');
|
||||
lines.push('# Preferences: User settings');
|
||||
lines.push('# ----------------------------------------------------------------------------');
|
||||
lines.push(
|
||||
yaml
|
||||
.dump({ preferences: config.preferences }, { indent: 2, lineWidth: -1, quotingType: '"' })
|
||||
.trim()
|
||||
);
|
||||
lines.push('');
|
||||
|
||||
// WebSearch section
|
||||
if (config.websearch) {
|
||||
lines.push('# ----------------------------------------------------------------------------');
|
||||
lines.push('# WebSearch: real search backends for third-party profiles');
|
||||
lines.push('# Dashboard (`ccs config`) is the source of truth for provider selection.');
|
||||
lines.push('#');
|
||||
lines.push('# Third-party providers (gemini, codex, agy, etc.) do not have access to');
|
||||
lines.push("# Anthropic's WebSearch tool. CCS intercepts that tool and runs local search.");
|
||||
lines.push('#');
|
||||
lines.push(
|
||||
'# Priority: Exa -> Tavily -> Brave -> DuckDuckGo -> optional legacy AI CLI fallbacks'
|
||||
);
|
||||
lines.push('#');
|
||||
lines.push('# Exa requires EXA_API_KEY in your environment.');
|
||||
lines.push('# Tavily requires TAVILY_API_KEY in your environment.');
|
||||
lines.push('# Brave requires BRAVE_API_KEY in your environment.');
|
||||
lines.push('# DuckDuckGo works with zero extra setup and is enabled by default.');
|
||||
lines.push('#');
|
||||
lines.push('# Legacy LLM fallbacks remain optional if you still want them:');
|
||||
lines.push('# gemini: npm i -g @google/gemini-cli');
|
||||
lines.push('# opencode: curl -fsSL https://opencode.ai/install | bash');
|
||||
lines.push('# grok: npm i -g @vibe-kit/grok-cli');
|
||||
lines.push('# ----------------------------------------------------------------------------');
|
||||
lines.push(
|
||||
yaml
|
||||
.dump({ websearch: config.websearch }, { indent: 2, lineWidth: -1, quotingType: '"' })
|
||||
.trim()
|
||||
);
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
// Copilot section (GitHub Copilot proxy)
|
||||
if (config.copilot) {
|
||||
lines.push('# ----------------------------------------------------------------------------');
|
||||
lines.push('# Copilot: GitHub Copilot API proxy (via copilot-api)');
|
||||
lines.push('# Uses your existing GitHub Copilot subscription with Claude Code.');
|
||||
lines.push('#');
|
||||
lines.push('# !! DISCLAIMER - USE AT YOUR OWN RISK !!');
|
||||
lines.push('# This uses an UNOFFICIAL reverse-engineered API.');
|
||||
lines.push('# Excessive usage may trigger GitHub account restrictions.');
|
||||
lines.push('# CCS provides NO WARRANTY and accepts NO RESPONSIBILITY for consequences.');
|
||||
lines.push('#');
|
||||
lines.push('# Setup: npx copilot-api auth (authenticate with GitHub)');
|
||||
lines.push('# Usage: ccs copilot (switch to copilot profile)');
|
||||
lines.push('#');
|
||||
lines.push('# Models: claude-sonnet-4.5, claude-opus-4.5, gpt-5.1, gemini-2.5-pro');
|
||||
lines.push('# Account types: individual, business, enterprise');
|
||||
lines.push('# ----------------------------------------------------------------------------');
|
||||
lines.push(
|
||||
yaml.dump({ copilot: config.copilot }, { indent: 2, lineWidth: -1, quotingType: '"' }).trim()
|
||||
);
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
// Cursor section (Cursor IDE proxy daemon)
|
||||
if (config.cursor) {
|
||||
lines.push('# ----------------------------------------------------------------------------');
|
||||
lines.push('# Cursor: Cursor IDE proxy daemon');
|
||||
lines.push('# Enables Cursor IDE integration via local proxy daemon.');
|
||||
lines.push('#');
|
||||
lines.push('# enabled: Enable/disable Cursor integration (default: false)');
|
||||
lines.push('# port: Port for cursor proxy daemon (default: 20129)');
|
||||
lines.push('# auto_start: Auto-start daemon when CCS starts (default: false)');
|
||||
lines.push('# ghost_mode: Disable telemetry for privacy (default: true)');
|
||||
lines.push('# model: Default model ID (used for ANTHROPIC_MODEL)');
|
||||
lines.push('# opus_model/sonnet_model/haiku_model: Optional tier model mapping');
|
||||
lines.push('# ----------------------------------------------------------------------------');
|
||||
lines.push(
|
||||
yaml.dump({ cursor: config.cursor }, { indent: 2, lineWidth: -1, quotingType: '"' }).trim()
|
||||
);
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
// Global env section
|
||||
if (config.global_env) {
|
||||
lines.push('# ----------------------------------------------------------------------------');
|
||||
lines.push(
|
||||
'# Global Environment Variables: Injected into all non-Claude subscription profiles'
|
||||
);
|
||||
lines.push('# These env vars disable telemetry/reporting for third-party providers.');
|
||||
lines.push('# Configure via Dashboard (`ccs config`) > Global Env tab.');
|
||||
lines.push('#');
|
||||
lines.push('# Default variables:');
|
||||
lines.push('# DISABLE_BUG_COMMAND: Disables /bug command (not supported by proxy)');
|
||||
lines.push('# DISABLE_ERROR_REPORTING: Disables error reporting to Anthropic');
|
||||
lines.push('# DISABLE_TELEMETRY: Disables usage telemetry');
|
||||
lines.push('# ----------------------------------------------------------------------------');
|
||||
lines.push(
|
||||
yaml
|
||||
.dump({ global_env: config.global_env }, { indent: 2, lineWidth: -1, quotingType: '"' })
|
||||
.trim()
|
||||
);
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
// Continuity inheritance section
|
||||
if (config.continuity?.inherit_from_account) {
|
||||
lines.push('# ----------------------------------------------------------------------------');
|
||||
lines.push('# Continuity Inheritance: Reuse account continuity artifacts across profiles');
|
||||
lines.push('# Map execution profile names to source account profiles (CLAUDE_CONFIG_DIR).');
|
||||
lines.push('# Applies to Claude target only; credentials remain profile-specific.');
|
||||
lines.push('# Example: continuity.inherit_from_account.glm: pro');
|
||||
lines.push('# ----------------------------------------------------------------------------');
|
||||
lines.push(
|
||||
yaml
|
||||
.dump({ continuity: config.continuity }, { indent: 2, lineWidth: -1, quotingType: '"' })
|
||||
.trim()
|
||||
);
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
// Thinking section (extended thinking/reasoning configuration)
|
||||
if (config.thinking) {
|
||||
lines.push('# ----------------------------------------------------------------------------');
|
||||
lines.push('# Thinking: Extended thinking/reasoning budget configuration');
|
||||
lines.push('# Controls reasoning depth for supported providers (agy, gemini, codex).');
|
||||
lines.push('#');
|
||||
lines.push(
|
||||
'# Modes: auto (use tier_defaults), off (disable), manual (--thinking/--effort flags)'
|
||||
);
|
||||
lines.push(
|
||||
'# Levels: minimal (512), low (1K), medium (8K), high (24K), xhigh (32K), max (adaptive ceiling), auto'
|
||||
);
|
||||
lines.push('# Override: Set global override value (number or level name)');
|
||||
lines.push('# Provider overrides: Per-provider tier defaults');
|
||||
lines.push('# ----------------------------------------------------------------------------');
|
||||
lines.push(
|
||||
yaml
|
||||
.dump({ thinking: config.thinking }, { indent: 2, lineWidth: -1, quotingType: '"' })
|
||||
.trim()
|
||||
);
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
// Official Channels section
|
||||
if (config.channels) {
|
||||
lines.push('# ----------------------------------------------------------------------------');
|
||||
lines.push('# Official Channels: Runtime auto-enable for Anthropic official channel plugins');
|
||||
lines.push('# Supported channels: telegram, discord, imessage');
|
||||
lines.push('# Runtime-only: CCS injects --channels at launch for compatible Claude sessions.');
|
||||
lines.push('# Bot tokens live in Claude channel env files, not in config.yaml.');
|
||||
lines.push('# Use selected: [telegram, discord, imessage] to choose channels.');
|
||||
lines.push(
|
||||
'# unattended adds --dangerously-skip-permissions only when channel auto-enable is active.'
|
||||
);
|
||||
lines.push('# Compatible sessions: native Claude default/account profiles only.');
|
||||
lines.push('# Configure via: ccs config channels or the Settings > Channels dashboard tab.');
|
||||
lines.push('# ----------------------------------------------------------------------------');
|
||||
lines.push(
|
||||
yaml
|
||||
.dump({ channels: config.channels }, { indent: 2, lineWidth: -1, quotingType: '"' })
|
||||
.trim()
|
||||
);
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
// Dashboard auth section (only if configured)
|
||||
if (config.dashboard_auth?.enabled) {
|
||||
lines.push('# ----------------------------------------------------------------------------');
|
||||
lines.push('# Dashboard Auth: Optional login protection for CCS dashboard');
|
||||
lines.push('# Generate password hash: npx bcrypt-cli hash "your-password"');
|
||||
lines.push(
|
||||
'# ENV override: CCS_DASHBOARD_AUTH_ENABLED, CCS_DASHBOARD_USERNAME, CCS_DASHBOARD_PASSWORD_HASH'
|
||||
);
|
||||
lines.push('# ----------------------------------------------------------------------------');
|
||||
lines.push(
|
||||
yaml
|
||||
.dump(
|
||||
{ dashboard_auth: config.dashboard_auth },
|
||||
{ indent: 2, lineWidth: -1, quotingType: '"' }
|
||||
)
|
||||
.trim()
|
||||
);
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
// Browser automation section
|
||||
if (config.browser) {
|
||||
lines.push('# ----------------------------------------------------------------------------');
|
||||
lines.push('# Browser Automation: Claude browser attach and Codex browser tooling');
|
||||
lines.push('# Claude attach reuses a running Chrome/Chromium session with remote debugging.');
|
||||
lines.push('# Codex tooling controls whether CCS injects Playwright MCP overrides.');
|
||||
lines.push('#');
|
||||
lines.push('# claude.user_data_dir should point at the Chrome user-data directory for the');
|
||||
lines.push('# dedicated attach session. claude.devtools_port is the expected debugging port.');
|
||||
lines.push('# Configure via: Settings > Browser or `ccs browser ...`.');
|
||||
lines.push('# ----------------------------------------------------------------------------');
|
||||
lines.push(
|
||||
yaml.dump({ browser: config.browser }, { indent: 2, lineWidth: -1, quotingType: '"' }).trim()
|
||||
);
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
// Image analysis section
|
||||
if (config.image_analysis) {
|
||||
lines.push('# ----------------------------------------------------------------------------');
|
||||
lines.push('# Image Analysis: Vision-based analysis for images and PDFs');
|
||||
lines.push('# Routes Read tool requests for images/PDFs through CLIProxy vision API.');
|
||||
lines.push('#');
|
||||
lines.push('# When enabled: Image files trigger vision analysis instead of raw file read');
|
||||
lines.push('# Provider models: Vision model used for each CLIProxy provider');
|
||||
lines.push('# Timeout: Maximum seconds to wait for analysis (10-600)');
|
||||
lines.push('#');
|
||||
lines.push('# Supported formats: .jpg, .jpeg, .png, .gif, .webp, .heic, .bmp, .tiff, .pdf');
|
||||
lines.push('# Configure via: ccs config image-analysis');
|
||||
lines.push('# ----------------------------------------------------------------------------');
|
||||
lines.push(
|
||||
yaml
|
||||
.dump(
|
||||
{ image_analysis: config.image_analysis },
|
||||
{ indent: 2, lineWidth: -1, quotingType: '"' }
|
||||
)
|
||||
.trim()
|
||||
);
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
+103
-1343
File diff suppressed because it is too large
Load Diff
@@ -11,10 +11,11 @@ import * as path from 'path';
|
||||
import * as http from 'http';
|
||||
import { CopilotDaemonStatus } from './types';
|
||||
import { CopilotConfig, DEFAULT_COPILOT_CONFIG } from '../config/unified-config-types';
|
||||
import { loadOrCreateUnifiedConfig } from '../config/unified-config-loader';
|
||||
|
||||
import { getCopilotDir, getCopilotApiBinPath } from './copilot-package-manager';
|
||||
import { verifyProcessOwnership } from '../cursor/daemon-process-ownership';
|
||||
import { createLogger } from '../services/logging';
|
||||
import { loadOrCreateUnifiedConfig } from '../config/config-loader-facade';
|
||||
|
||||
const logger = createLogger('copilot:daemon');
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
import { spawn } from 'child_process';
|
||||
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';
|
||||
@@ -36,6 +36,7 @@ import {
|
||||
} from '../utils/hooks';
|
||||
import { stripClaudeCodeEnv } from '../utils/shell-executor';
|
||||
import { createLogger } from '../services/logging';
|
||||
import { getGlobalEnvConfig } from '../config/config-loader-facade';
|
||||
|
||||
const logger = createLogger('copilot:executor');
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import * as path from 'path';
|
||||
import { spawn, spawnSync } from 'child_process';
|
||||
import { ProgressIndicator } from '../utils/progress-indicator';
|
||||
import { ok, info } from '../utils/ui';
|
||||
import { getCcsDir } from '../utils/config-manager';
|
||||
import { getCcsDir } from '../config/config-loader-facade';
|
||||
|
||||
/** Cache duration for version check (1 hour in milliseconds) */
|
||||
const VERSION_CACHE_DURATION_MS = 60 * 60 * 1000;
|
||||
|
||||
@@ -19,7 +19,7 @@ import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
import type { CursorCredentials, CursorAuthStatus, AutoDetectResult } from './types';
|
||||
import { getCcsDir } from '../utils/config-manager';
|
||||
import { getCcsDir } from '../config/config-loader-facade';
|
||||
|
||||
const ACCESS_TOKEN_KEYS = ['cursorAuth/accessToken', 'cursorAuth/token'] as const;
|
||||
const MACHINE_ID_KEYS = [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { getCcsDir } from '../utils/config-manager';
|
||||
import { getCcsDir } from '../config/config-loader-facade';
|
||||
|
||||
function getCursorDir(): string {
|
||||
return path.join(getCcsDir(), 'cursor');
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { spawn } from 'child_process';
|
||||
|
||||
import type { CursorConfig } from '../config/unified-config-types';
|
||||
import { getGlobalEnvConfig } from '../config/unified-config-loader';
|
||||
|
||||
import { ensureCliproxyService } from '../cliproxy';
|
||||
import { CLIPROXY_DEFAULT_PORT } from '../cliproxy/config/port-manager';
|
||||
import { fail, info, ok } from '../utils/ui';
|
||||
@@ -15,6 +15,7 @@ import { getImageAnalysisHookEnv, resolveImageAnalysisRuntimeStatus } from '../u
|
||||
import { stripClaudeCodeEnv } from '../utils/shell-executor';
|
||||
import { checkAuthStatus } from './cursor-auth';
|
||||
import { isDaemonRunning, startDaemon } from './cursor-daemon';
|
||||
import { getGlobalEnvConfig } from '../config/config-loader-facade';
|
||||
|
||||
interface CursorImageAnalysisResolution {
|
||||
env: Record<string, string>;
|
||||
|
||||
@@ -6,7 +6,7 @@ import { ResultFormatter } from './result-formatter';
|
||||
import { DelegationValidator } from '../utils/delegation-validator';
|
||||
import { SettingsParser } from './settings-parser';
|
||||
import { fail, warn } from '../utils/ui';
|
||||
import { getCcsDir } from '../utils/config-manager';
|
||||
import { getCcsDir } from '../config/config-loader-facade';
|
||||
|
||||
const PROFILE_FLAGS_WITH_VALUE = new Set(['-p', '--prompt', '--effort']);
|
||||
|
||||
|
||||
@@ -15,8 +15,8 @@ import { ui, warn, info } from '../utils/ui';
|
||||
import { type ExecutionOptions, type ExecutionResult, type StreamMessage } from './executor/types';
|
||||
import { StreamBuffer, formatToolVerbose } from './executor/stream-parser';
|
||||
import { buildExecutionResult } from './executor/result-aggregator';
|
||||
import { getCcsDir, getModelDisplayName, loadSettings } from '../utils/config-manager';
|
||||
import { getGlobalEnvConfig } from '../config/unified-config-loader';
|
||||
import { getModelDisplayName } from '../utils/config-manager';
|
||||
|
||||
import { getProfileLookupCandidates } from '../utils/profile-compat';
|
||||
import {
|
||||
getClaudeLaunchEnvOverrides,
|
||||
@@ -58,6 +58,7 @@ import {
|
||||
readWebSearchTraceRecords,
|
||||
syncWebSearchMcpToConfigDir,
|
||||
} from '../utils/websearch-manager';
|
||||
import { getCcsDir, getGlobalEnvConfig, loadSettings } from '../config/config-loader-facade';
|
||||
|
||||
// Re-export types for consumers
|
||||
export type { ExecutionOptions, ExecutionResult, StreamMessage } from './executor/types';
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { getCcsDir } from '../utils/config-manager';
|
||||
import { getCcsDir } from '../config/config-loader-facade';
|
||||
|
||||
interface SessionData {
|
||||
sessionId: string;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as path from 'path';
|
||||
import { getCcsDir } from '../utils/config-manager';
|
||||
|
||||
import type { DockerConfigSummary } from './docker-types';
|
||||
import { getCcsDir } from '../config/config-loader-facade';
|
||||
|
||||
export const DOCKER_REMOTE_DIR = '~/.ccs/docker';
|
||||
export const DOCKER_COMPOSE_SERVICE = 'ccs-cliproxy';
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { DeltaAccumulator } from './delta-accumulator';
|
||||
import { getCcsDir } from '../utils/config-manager';
|
||||
|
||||
import { createLogger } from '../services/logging';
|
||||
import {
|
||||
RequestTransformer,
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
type ThinkingSignature,
|
||||
type ValidationResult,
|
||||
} from './pipeline';
|
||||
import { getCcsDir } from '../config/config-loader-facade';
|
||||
|
||||
export class GlmtTransformer {
|
||||
private verbose: boolean;
|
||||
|
||||
@@ -6,8 +6,9 @@ import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { ok, fail, warn, info } from '../../utils/ui';
|
||||
import { HealthCheck, IHealthChecker, createSpinner } from './types';
|
||||
import { getCcsDir } from '../../utils/config-manager';
|
||||
|
||||
import { getClaudeConfigDir } from '../../utils/claude-config-path';
|
||||
import { getCcsDir } from '../../config/config-loader-facade';
|
||||
|
||||
const ora = createSpinner();
|
||||
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
|
||||
import { getEnvironmentDiagnostics } from '../environment-diagnostics';
|
||||
import { ok, warn } from '../../utils/ui';
|
||||
import { getCcsDir, getCcsDirSource } from '../../utils/config-manager';
|
||||
import { getCcsDirSource } from '../../utils/config-manager';
|
||||
import { HealthCheck, IHealthChecker, createSpinner } from './types';
|
||||
import { getCcsDir } from '../../config/config-loader-facade';
|
||||
|
||||
const ora = createSpinner();
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
* Checks: enabled status, provider_models, timeout, CLIProxy availability.
|
||||
*/
|
||||
|
||||
import { getImageAnalysisConfig } from '../../config/unified-config-loader';
|
||||
import { DEFAULT_IMAGE_ANALYSIS_CONFIG } from '../../config/unified-config-types';
|
||||
import {
|
||||
countManagedImageAnalysisHookFiles,
|
||||
@@ -17,6 +16,7 @@ import { isCliproxyRunning } from '../../cliproxy/services/stats-fetcher';
|
||||
import { CLIPROXY_DEFAULT_PORT } from '../../cliproxy/config/config-generator';
|
||||
import type { HealthCheck } from './types';
|
||||
import { hasImageAnalyzerHook } from '../../utils/hooks/image-analyzer-hook-installer';
|
||||
import { getImageAnalysisConfig } from '../../config/config-loader-facade';
|
||||
|
||||
/**
|
||||
* Run image analysis configuration check
|
||||
@@ -112,8 +112,8 @@ export async function runImageAnalysisCheck(results: HealthCheck): Promise<void>
|
||||
* Fix image analysis configuration issues
|
||||
*/
|
||||
export async function fixImageAnalysisConfig(): Promise<boolean> {
|
||||
const { updateUnifiedConfig, loadOrCreateUnifiedConfig } = await import(
|
||||
'../../config/unified-config-loader'
|
||||
const { updateConfig, loadOrCreateUnifiedConfig } = await import(
|
||||
'../../config/config-loader-facade'
|
||||
);
|
||||
|
||||
const config = loadOrCreateUnifiedConfig();
|
||||
@@ -145,7 +145,7 @@ export async function fixImageAnalysisConfig(): Promise<boolean> {
|
||||
}
|
||||
|
||||
if (fixed) {
|
||||
updateUnifiedConfig({ image_analysis: config.image_analysis });
|
||||
updateConfig({ image_analysis: config.image_analysis });
|
||||
}
|
||||
|
||||
const repairStats = repairImageAnalysisRuntimeState();
|
||||
|
||||
@@ -6,7 +6,7 @@ import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { ok, fail, warn, info } from '../../utils/ui';
|
||||
import { HealthCheck, IHealthChecker, createSpinner } from './types';
|
||||
import { getCcsDir } from '../../utils/config-manager';
|
||||
import { getCcsDir } from '../../config/config-loader-facade';
|
||||
|
||||
const ora = createSpinner();
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
import { ok, fail, warn } from '../../utils/ui';
|
||||
import { HealthCheck, IHealthChecker, createSpinner } from './types';
|
||||
import { getCcsDir } from '../../utils/config-manager';
|
||||
import { getCcsDir } from '../../config/config-loader-facade';
|
||||
|
||||
const ora = createSpinner();
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { getClaudeCliInfo } from '../../utils/claude-detector';
|
||||
import { escapeShellArg, stripClaudeCodeEnv } from '../../utils/shell-executor';
|
||||
import { ok, fail } from '../../utils/ui';
|
||||
import { HealthCheck, IHealthChecker, createSpinner } from './types';
|
||||
import { getCcsDir } from '../../utils/config-manager';
|
||||
import { getCcsDir } from '../../config/config-loader-facade';
|
||||
|
||||
const ora = createSpinner();
|
||||
|
||||
|
||||
@@ -12,8 +12,9 @@ import SharedManager from './shared-manager';
|
||||
import ProfileContextSyncLock from './profile-context-sync-lock';
|
||||
import { DEFAULT_ACCOUNT_CONTEXT_MODE } from '../auth/account-context';
|
||||
import type { AccountContextPolicy } from '../auth/account-context';
|
||||
import { getCcsDir, getCcsHome } from '../utils/config-manager';
|
||||
import { getCcsHome } from '../utils/config-manager';
|
||||
import { createLogger } from '../services/logging';
|
||||
import { getCcsDir } from '../config/config-loader-facade';
|
||||
|
||||
const logger = createLogger('management:instance-manager');
|
||||
|
||||
|
||||
@@ -7,13 +7,14 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { info } from '../utils/ui';
|
||||
import { getCcsHome, getCcsDir } from '../utils/config-manager';
|
||||
import { getCcsHome } from '../utils/config-manager';
|
||||
import { createEmptyUnifiedConfig, UNIFIED_CONFIG_VERSION } from '../config/unified-config-types';
|
||||
import {
|
||||
saveUnifiedConfig,
|
||||
getCcsDir,
|
||||
hasUnifiedConfig,
|
||||
loadUnifiedConfig,
|
||||
} from '../config/unified-config-loader';
|
||||
saveConfig,
|
||||
} from '../config/config-loader-facade';
|
||||
|
||||
/**
|
||||
* Recovery Manager Class
|
||||
@@ -137,7 +138,7 @@ class RecoveryManager {
|
||||
config.version = UNIFIED_CONFIG_VERSION;
|
||||
|
||||
try {
|
||||
saveUnifiedConfig(config);
|
||||
saveConfig(config);
|
||||
this.recovered.push(`Created ${this.ccsDir}/config.yaml`);
|
||||
return true;
|
||||
} catch (_saveErr) {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { ok, warn, fail, info, header, color } from '../../utils/ui';
|
||||
import { getCcsDir } from '../../utils/config-manager';
|
||||
|
||||
import {
|
||||
CLIPROXY_DEFAULT_PORT,
|
||||
configNeedsRegeneration,
|
||||
@@ -16,6 +16,7 @@ import { getPortProcess, isCLIProxyProcess } from '../../utils/port-utils';
|
||||
import { killProcessOnPort, getPlatformName } from '../../utils/platform-commands';
|
||||
import { createSpinner } from '../checks/types';
|
||||
import { fixImageAnalysisConfig } from '../checks/image-analysis-check';
|
||||
import { getCcsDir } from '../../config/config-loader-facade';
|
||||
|
||||
const ora = createSpinner();
|
||||
|
||||
|
||||
@@ -13,11 +13,12 @@ import ProfileContextSyncLock from './profile-context-sync-lock';
|
||||
import { ok, info, warn } from '../utils/ui';
|
||||
import { DEFAULT_ACCOUNT_CONTEXT_GROUP } from '../auth/account-context';
|
||||
import type { AccountContextPolicy } from '../auth/account-context';
|
||||
import { getCcsDir } from '../utils/config-manager';
|
||||
|
||||
import {
|
||||
normalizePluginMetadataContent,
|
||||
normalizePluginMetadataValue,
|
||||
} from './plugin-path-normalizer';
|
||||
import { getCcsDir } from '../config/config-loader-facade';
|
||||
export {
|
||||
normalizePluginMetadataContent,
|
||||
normalizePluginMetadataPathString,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { loadSettings } from '../utils/config-manager';
|
||||
import { resolveOpenAICompatProfileConfig } from './profile-router';
|
||||
import { OPENAI_COMPAT_PROXY_DEFAULT_PORT } from './proxy-daemon-paths';
|
||||
import { startOpenAICompatProxyServer } from './server/proxy-server';
|
||||
import { loadSettings } from '../config/config-loader-facade';
|
||||
|
||||
interface RuntimeOptions {
|
||||
port: number;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as path from 'path';
|
||||
import { getCcsDir } from '../utils/config-manager';
|
||||
import { getCcsDir } from '../config/config-loader-facade';
|
||||
|
||||
export const OPENAI_COMPAT_PROXY_LEGACY_DEFAULT_PORT = 3456;
|
||||
export const OPENAI_COMPAT_PROXY_ADAPTIVE_PORT_START = 43_456;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { loadOrCreateUnifiedConfig } from '../config/unified-config-loader';
|
||||
import {
|
||||
OPENAI_COMPAT_PROXY_ADAPTIVE_PORT_END,
|
||||
OPENAI_COMPAT_PROXY_ADAPTIVE_PORT_START,
|
||||
OPENAI_COMPAT_PROXY_LEGACY_DEFAULT_PORT,
|
||||
} from './proxy-daemon-paths';
|
||||
import { loadOrCreateUnifiedConfig } from '../config/config-loader-facade';
|
||||
|
||||
export interface OpenAICompatProxyPortPreference {
|
||||
port: number;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { loadConfigSafe, loadSettings } from '../utils/config-manager';
|
||||
import { expandPath } from '../utils/helpers';
|
||||
import type { ProxyOpenAIRequest } from './transformers/request-transformer';
|
||||
import {
|
||||
@@ -6,6 +5,7 @@ import {
|
||||
type OpenAICompatProxyRoutingConfig,
|
||||
} from './routing-config';
|
||||
import { resolveOpenAICompatProfileConfig, type OpenAICompatProfileConfig } from './profile-router';
|
||||
import { loadConfigSafe, loadSettings } from '../config/config-loader-facade';
|
||||
|
||||
export type ProxyRoutingScenario = 'default' | 'background' | 'think' | 'longContext' | 'webSearch';
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import * as fs from 'fs';
|
||||
import { DEFAULT_LOGGING_CONFIG } from '../../config/unified-config-types';
|
||||
|
||||
import type { LoggingConfig } from './log-types';
|
||||
import {
|
||||
getConfigYamlPath,
|
||||
getLoggingConfig as getUnifiedLoggingConfig,
|
||||
} from '../../config/unified-config-loader';
|
||||
import type { LoggingConfig } from './log-types';
|
||||
} from '../../config/config-loader-facade';
|
||||
|
||||
const CACHE_RECHECK_MS = 1000;
|
||||
let cachedConfig: LoggingConfig = { ...DEFAULT_LOGGING_CONFIG };
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { getCcsDir } from '../../utils/config-manager';
|
||||
import { getCcsDir } from '../../config/config-loader-facade';
|
||||
|
||||
const LOGS_DIR = 'logs';
|
||||
const ARCHIVE_DIR = 'archive';
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { getBrowserConfig, mutateUnifiedConfig } from '../../config/unified-config-loader';
|
||||
import type { BrowserConfig } from '../../config/unified-config-types';
|
||||
import { getNodePlatformKey } from './platform';
|
||||
import { type BrowserStatusPayload, getBrowserStatus } from './browser-status';
|
||||
@@ -9,6 +8,7 @@ import {
|
||||
getRecommendedBrowserUserDataDir,
|
||||
isManagedClaudeBrowserAttachConfig,
|
||||
} from './browser-settings';
|
||||
import { getBrowserConfig, mutateConfig } from '../../config/config-loader-facade';
|
||||
|
||||
export interface BrowserSetupResult {
|
||||
configUpdated: boolean;
|
||||
@@ -23,14 +23,14 @@ export interface BrowserSetupResult {
|
||||
|
||||
export interface BrowserSetupDeps {
|
||||
getBrowserConfig: typeof getBrowserConfig;
|
||||
mutateUnifiedConfig: typeof mutateUnifiedConfig;
|
||||
mutateConfig: typeof mutateConfig;
|
||||
ensureBrowserMcp: typeof ensureBrowserMcp;
|
||||
getBrowserStatus: typeof getBrowserStatus;
|
||||
}
|
||||
|
||||
const defaultBrowserSetupDeps: BrowserSetupDeps = {
|
||||
getBrowserConfig,
|
||||
mutateUnifiedConfig,
|
||||
mutateConfig,
|
||||
ensureBrowserMcp,
|
||||
getBrowserStatus,
|
||||
};
|
||||
@@ -81,7 +81,7 @@ export async function runBrowserSetup(
|
||||
function persistBrowserSetupConfig(deps: BrowserSetupDeps, currentConfig: BrowserConfig): boolean {
|
||||
const before = JSON.stringify(currentConfig);
|
||||
|
||||
deps.mutateUnifiedConfig((config) => {
|
||||
deps.mutateConfig((config) => {
|
||||
const existingBrowser = config.browser ?? currentConfig;
|
||||
const currentUserDataDir = existingBrowser.claude.user_data_dir?.trim();
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import type {
|
||||
BrowserEvalMode,
|
||||
BrowserToolPolicy,
|
||||
} from '../../config/unified-config-types';
|
||||
import { getBrowserConfig, loadUnifiedConfig } from '../../config/unified-config-loader';
|
||||
|
||||
import { getCcsPathDisplay } from '../config-manager';
|
||||
import { getCodexBinaryInfo } from '../../targets/codex-detector';
|
||||
import { type BrowserRuntimeEnv, resolveBrowserRuntimeEnv } from './chrome-reuse';
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
getEffectiveClaudeBrowserAttachConfig,
|
||||
getRecommendedBrowserUserDataDir,
|
||||
} from './browser-settings';
|
||||
import { getBrowserConfig, loadUnifiedConfig } from '../../config/config-loader-facade';
|
||||
|
||||
export interface ClaudeBrowserStatus {
|
||||
enabled: boolean;
|
||||
|
||||
@@ -6,7 +6,7 @@ import { isConfig, isSettings } from '../types';
|
||||
import type { Config, Settings, CLIProxyVariantsConfig, CLIProxyVariantConfig } from '../types';
|
||||
import { expandPath, error } from './helpers';
|
||||
import { info } from './ui';
|
||||
import { isUnifiedMode, loadOrCreateUnifiedConfig } from '../config/unified-config-loader';
|
||||
import { isUnifiedMode, loadOrCreateUnifiedConfig } from '../config/config-loader-facade';
|
||||
|
||||
// TODO: Replace with proper imports after converting these files
|
||||
// const { ErrorManager } = require('./error-manager');
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
* @module utils/hooks/image-analysis-hook-env
|
||||
*/
|
||||
|
||||
import { getImageAnalysisConfig } from '../../config/unified-config-loader';
|
||||
import { resolveCliproxyBridgeProfile } from '../../api/services/cliproxy-profile-bridge';
|
||||
import { getEffectiveApiKey } from '../../cliproxy/auth/auth-token-manager';
|
||||
import { mapExternalProviderName } from '../../cliproxy/provider-capabilities';
|
||||
@@ -21,6 +20,7 @@ import {
|
||||
resolveImageAnalysisStatus,
|
||||
type ImageAnalysisResolutionContext,
|
||||
} from './image-analysis-backend-resolver';
|
||||
import { getImageAnalysisConfig } from '../../config/config-loader-facade';
|
||||
|
||||
/**
|
||||
* Serialize provider_models map to env var format: provider:model,provider:model
|
||||
|
||||
@@ -7,8 +7,9 @@
|
||||
*/
|
||||
|
||||
import * as path from 'path';
|
||||
import { getImageAnalysisConfig } from '../../config/unified-config-loader';
|
||||
|
||||
import { getCcsHooksDir } from '../config-manager';
|
||||
import { getImageAnalysisConfig } from '../../config/config-loader-facade';
|
||||
|
||||
// Hook file name
|
||||
const IMAGE_ANALYZER_HOOK = 'image-analyzer-transformer.cjs';
|
||||
|
||||
@@ -12,9 +12,10 @@ import * as path from 'path';
|
||||
import { info, warn } from '../ui';
|
||||
import { getImageAnalyzerHookPath } from './image-analyzer-hook-configuration';
|
||||
import { getCcsHooksDir } from '../config-manager';
|
||||
import { getImageAnalysisConfig } from '../../config/unified-config-loader';
|
||||
|
||||
import { removeMigrationMarker } from './image-analyzer-profile-hook-injector';
|
||||
import { installImageAnalysisPrompts } from '../image-analysis/hook-installer';
|
||||
import { getImageAnalysisConfig } from '../../config/config-loader-facade';
|
||||
|
||||
// Re-export from hook-configuration for backward compatibility
|
||||
export {
|
||||
|
||||
@@ -21,12 +21,13 @@ import {
|
||||
isCcsImageAnalyzerHook,
|
||||
removeCcsImageAnalyzerHooks,
|
||||
} from './image-analyzer-hook-utils';
|
||||
import { getImageAnalysisConfig } from '../../config/unified-config-loader';
|
||||
|
||||
import { getCcsDir } from '../config-manager';
|
||||
import {
|
||||
resolveImageAnalysisStatus,
|
||||
type ImageAnalysisResolutionContext,
|
||||
} from './image-analysis-backend-resolver';
|
||||
import { getImageAnalysisConfig } from '../../config/config-loader-facade';
|
||||
|
||||
// Valid profile name pattern (alphanumeric, dot, dash, underscore only)
|
||||
const VALID_PROFILE_NAME = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
|
||||
|
||||
@@ -5,12 +5,13 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as lockfile from 'proper-lockfile';
|
||||
import { getImageAnalysisConfig } from '../../config/unified-config-loader';
|
||||
|
||||
import { getCcsDir } from '../config-manager';
|
||||
import { getClaudeUserConfigPath } from '../claude-config-path';
|
||||
import { info, warn } from '../ui';
|
||||
import { InstanceManager } from '../../management/instance-manager';
|
||||
import { installImageAnalysisPrompts } from './hook-installer';
|
||||
import { getImageAnalysisConfig } from '../../config/config-loader-facade';
|
||||
|
||||
const IMAGE_ANALYSIS_MCP_SERVER = 'ccs-image-analysis-server.cjs';
|
||||
const IMAGE_ANALYSIS_MCP_RUNTIME = 'image-analysis-runtime.cjs';
|
||||
|
||||
@@ -8,8 +8,9 @@ import { spawn, spawnSync, ChildProcess, type SpawnOptions } from 'child_process
|
||||
import { ErrorManager } from './error-manager';
|
||||
import { getWebSearchHookEnv } from './websearch-manager';
|
||||
import { wireChildProcessSignals } from './signal-forwarder';
|
||||
import { loadOrCreateUnifiedConfig } from '../config/unified-config-loader';
|
||||
|
||||
import SharedManager from '../management/shared-manager';
|
||||
import { loadOrCreateUnifiedConfig } from '../config/config-loader-facade';
|
||||
|
||||
/**
|
||||
* Strip ANTHROPIC_* env vars from an environment object.
|
||||
|
||||
@@ -9,10 +9,11 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { info, warn } from '../ui';
|
||||
import { getWebSearchConfig } from '../../config/unified-config-loader';
|
||||
|
||||
import { getCcsHooksDir } from '../config-manager';
|
||||
import { getClaudeSettingsPath } from '../claude-config-path';
|
||||
import { isCcsWebSearchHook, deduplicateCcsHooks } from './hook-utils';
|
||||
import { getWebSearchConfig } from '../../config/config-loader-facade';
|
||||
|
||||
// Hook file name
|
||||
const WEBSEARCH_HOOK = 'websearch-transformer.cjs';
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
* @module utils/websearch/hook-env
|
||||
*/
|
||||
|
||||
import { getWebSearchConfig } from '../../config/unified-config-loader';
|
||||
import { normalizeSearxngBaseUrl } from './types';
|
||||
import { resolveAllowedWebSearchTraceFile } from './trace';
|
||||
import { getWebSearchConfig } from '../../config/config-loader-facade';
|
||||
|
||||
/**
|
||||
* Get environment variables for WebSearch hook configuration.
|
||||
|
||||
@@ -9,9 +9,10 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { info, warn } from '../ui';
|
||||
import { getWebSearchConfig } from '../../config/unified-config-loader';
|
||||
|
||||
import { getCcsDir, getCcsHooksDir } from '../config-manager';
|
||||
import { getHookPath } from './hook-config';
|
||||
import { getWebSearchConfig } from '../../config/config-loader-facade';
|
||||
|
||||
// Re-export from hook-config for backward compatibility
|
||||
export { getHookPath, getWebSearchHookConfig } from './hook-config';
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user