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

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

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

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

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

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

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

179 lines
5.5 KiB
TypeScript

/**
* API Profile Reader Service
*
* Read operations for API profiles.
* Supports both unified YAML config and legacy JSON config.
*/
import * as fs from 'fs';
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)) {
return target as TargetType;
}
return 'claude';
}
/**
* Check if API profile exists in config
*/
export function apiProfileExists(name: string): boolean {
try {
if (isUnifiedMode()) {
const config = loadOrCreateUnifiedConfig();
return name in config.profiles;
}
const config = loadConfigSafe();
return name in config.profiles;
} catch {
return false;
}
}
/**
* Load settings file from a config reference such as ~/.ccs/name.settings.json.
*/
function loadProfileSettings(settingsReference: string | undefined): Settings | null {
if (!settingsReference || settingsReference === 'config.yaml') {
return null;
}
try {
const settingsPath = expandPath(settingsReference);
if (!fs.existsSync(settingsPath)) return null;
return JSON.parse(fs.readFileSync(settingsPath, 'utf8')) as Settings;
} catch {
return null;
}
}
function isConfiguredFromSettings(settings: Settings | null): boolean {
const token = settings?.env?.ANTHROPIC_AUTH_TOKEN || settings?.env?.ANTHROPIC_API_KEY || '';
return token.length > 0 && !token.includes('YOUR_') && !token.includes('your-');
}
function resolveProfileSettingsReference(name: string): string | undefined {
if (isUnifiedMode()) {
const config = loadOrCreateUnifiedConfig();
return config.profiles[name]?.settings;
}
const config = loadConfigSafe();
return config.profiles[name];
}
/**
* Check if API profile has real API key (not placeholder)
*/
export function isApiProfileConfigured(apiName: string): boolean {
return isConfiguredFromSettings(loadProfileSettings(resolveProfileSettingsReference(apiName)));
}
/**
* List all API profiles
*
* Note: The 'default' profile (pointing to ~/.claude/settings.json) is excluded
* as it represents the user's native Claude subscription, not an API profile.
*/
export function listApiProfiles(): ApiListResult {
const profiles: ApiProfileInfo[] = [];
const variants: CliproxyVariantInfo[] = [];
if (isUnifiedMode()) {
const unifiedConfig = loadOrCreateUnifiedConfig();
for (const [name, profile] of Object.entries(unifiedConfig.profiles)) {
// Skip 'default' profile - it's the user's native Claude settings
if (name === 'default' && profile.settings?.includes('.claude/settings.json')) {
continue;
}
const settingsPath = profile.settings || 'config.yaml';
const settings = loadProfileSettings(settingsPath);
profiles.push({
name,
settingsPath,
isConfigured: isConfiguredFromSettings(settings),
configSource: 'unified',
target: sanitizeTarget(profile.target),
cliproxyBridge: resolveCliproxyBridgeMetadata(settings),
});
}
// CLIProxy variants
for (const [name, variant] of Object.entries(unifiedConfig.cliproxy?.variants || {})) {
const provider =
variant && 'type' in variant && variant.type === 'composite'
? 'composite'
: (variant as { provider?: string })?.provider || 'unknown';
variants.push({
name,
provider,
settings: variant?.settings || '-',
target: sanitizeTarget(variant?.target),
});
}
} else {
const config = loadConfigSafe();
const legacyTargetMap = (config as { profile_targets?: Record<string, unknown> })
.profile_targets;
for (const [name, settingsPath] of Object.entries(config.profiles)) {
// Skip 'default' profile - it's the user's native Claude settings
if (name === 'default' && (settingsPath as string).includes('.claude/settings.json')) {
continue;
}
const settings = loadProfileSettings(settingsPath as string);
profiles.push({
name,
settingsPath: settingsPath as string,
isConfigured: isConfiguredFromSettings(settings),
configSource: 'legacy',
target: sanitizeTarget(legacyTargetMap?.[name]),
cliproxyBridge: resolveCliproxyBridgeMetadata(settings),
});
}
// CLIProxy variants
if (config.cliproxy) {
for (const [name, v] of Object.entries(config.cliproxy)) {
const variant = v as { provider: string; settings: string; target?: unknown };
variants.push({
name,
provider: variant.provider,
settings: variant.settings,
target: sanitizeTarget(variant.target),
});
}
}
}
return { profiles, variants };
}
/**
* Get list of available API profile names
*/
export function getApiProfileNames(): string[] {
if (isUnifiedMode()) {
const config = loadOrCreateUnifiedConfig();
return Object.keys(config.profiles);
}
const config = loadConfigSafe();
return Object.keys(config.profiles);
}
/**
* Check if using unified config mode
*/
export function isUsingUnifiedConfig(): boolean {
return isUnifiedMode();
}