mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-02 18:18:43 +00:00
refactor(config): use settings file references instead of inline env
Changes config.yaml to store references to *.settings.json files instead of inlining env vars. This matches Claude's ~/.claude/settings.json pattern and gives users familiar editing experience. Changes: - ProfileConfig now stores 'settings' path instead of 'env' object - CLIProxyVariantConfig uses 'settings' instead of 'model'/'env' - Migration preserves existing *.settings.json files, only stores references - Updated config.yaml comments to explain settings file pattern - Create/remove commands now manage settings files alongside config.yaml Benefits: - Users edit familiar *.settings.json format - config.yaml stays clean (just references) - No confusion about where to make changes
This commit is contained in:
@@ -52,6 +52,22 @@ export interface ProfileNotFoundError extends Error {
|
||||
/**
|
||||
* Profile Detector Class
|
||||
*/
|
||||
/**
|
||||
* Load env vars from a settings file (*.settings.json).
|
||||
* Expands ~ to home directory. Returns empty object on error.
|
||||
*/
|
||||
function loadSettingsFromFile(settingsPath: string): Record<string, string> {
|
||||
const expandedPath = settingsPath.replace(/^~/, os.homedir());
|
||||
try {
|
||||
if (!fs.existsSync(expandedPath)) return {};
|
||||
const content = fs.readFileSync(expandedPath, 'utf8');
|
||||
const settings = JSON.parse(content) as { env?: Record<string, string> };
|
||||
return settings.env || {};
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
class ProfileDetector {
|
||||
private readonly configPath: string;
|
||||
private readonly profilesPath: string;
|
||||
@@ -99,12 +115,14 @@ class ProfileDetector {
|
||||
// Check API profiles
|
||||
if (config.profiles?.[profileName]) {
|
||||
const profile = config.profiles[profileName];
|
||||
// Merge with secrets
|
||||
// Load env from settings file
|
||||
const settingsEnv = loadSettingsFromFile(profile.settings);
|
||||
// Merge with secrets (for backward compat with any extracted secrets)
|
||||
const secrets = getProfileSecrets(profileName);
|
||||
return {
|
||||
type: 'settings',
|
||||
name: profileName,
|
||||
env: { ...profile.env, ...secrets },
|
||||
env: { ...settingsEnv, ...secrets },
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+39
-16
@@ -10,6 +10,7 @@
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import {
|
||||
initUI,
|
||||
@@ -32,7 +33,7 @@ import {
|
||||
loadOrCreateUnifiedConfig,
|
||||
saveUnifiedConfig,
|
||||
} from '../config/unified-config-loader';
|
||||
import { setProfileSecrets, deleteAllProfileSecrets } from '../config/secrets-manager';
|
||||
import { deleteAllProfileSecrets } from '../config/secrets-manager';
|
||||
import { isUnifiedConfigEnabled } from '../config/feature-flags';
|
||||
|
||||
interface ApiCommandArgs {
|
||||
@@ -183,7 +184,9 @@ function updateConfig(name: string, _settingsPath: string): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create API profile in unified config (config.yaml + secrets.yaml)
|
||||
* Create API profile in unified config
|
||||
* Creates *.settings.json file and stores reference in config.yaml
|
||||
* (matching Claude's ~/.claude/settings.json pattern)
|
||||
*/
|
||||
function createApiProfileUnified(
|
||||
name: string,
|
||||
@@ -191,13 +194,15 @@ function createApiProfileUnified(
|
||||
apiKey: string,
|
||||
model: string
|
||||
): void {
|
||||
const config = loadOrCreateUnifiedConfig();
|
||||
const ccsDir = path.join(os.homedir(), '.ccs');
|
||||
const settingsFile = `${name}.settings.json`;
|
||||
const settingsPath = path.join(ccsDir, settingsFile);
|
||||
|
||||
// Add profile to config.yaml (non-sensitive data only)
|
||||
config.profiles[name] = {
|
||||
type: 'api',
|
||||
// Create settings file with all env vars (matching Claude's pattern)
|
||||
const settings = {
|
||||
env: {
|
||||
ANTHROPIC_BASE_URL: baseUrl,
|
||||
ANTHROPIC_AUTH_TOKEN: apiKey,
|
||||
ANTHROPIC_MODEL: model,
|
||||
ANTHROPIC_DEFAULT_OPUS_MODEL: model,
|
||||
ANTHROPIC_DEFAULT_SONNET_MODEL: model,
|
||||
@@ -205,12 +210,21 @@ function createApiProfileUnified(
|
||||
},
|
||||
};
|
||||
|
||||
saveUnifiedConfig(config);
|
||||
// Ensure directory exists
|
||||
if (!fs.existsSync(ccsDir)) {
|
||||
fs.mkdirSync(ccsDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Store API key in secrets.yaml (sensitive data)
|
||||
setProfileSecrets(name, {
|
||||
ANTHROPIC_AUTH_TOKEN: apiKey,
|
||||
});
|
||||
// Write settings file
|
||||
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf8');
|
||||
|
||||
// Store reference in config.yaml
|
||||
const config = loadOrCreateUnifiedConfig();
|
||||
config.profiles[name] = {
|
||||
type: 'api',
|
||||
settings: `~/.ccs/${settingsFile}`,
|
||||
};
|
||||
saveUnifiedConfig(config);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -218,11 +232,20 @@ function createApiProfileUnified(
|
||||
*/
|
||||
function removeApiProfileUnified(name: string): void {
|
||||
const config = loadOrCreateUnifiedConfig();
|
||||
const profile = config.profiles[name];
|
||||
|
||||
if (!config.profiles[name]) {
|
||||
if (!profile) {
|
||||
throw new Error(`API profile not found: ${name}`);
|
||||
}
|
||||
|
||||
// Delete the settings file if it exists
|
||||
if (profile.settings) {
|
||||
const settingsPath = profile.settings.replace(/^~/, os.homedir());
|
||||
if (fs.existsSync(settingsPath)) {
|
||||
fs.unlinkSync(settingsPath);
|
||||
}
|
||||
}
|
||||
|
||||
delete config.profiles[name];
|
||||
|
||||
// Clear default if it was the deleted profile
|
||||
@@ -232,7 +255,7 @@ function removeApiProfileUnified(name: string): void {
|
||||
|
||||
saveUnifiedConfig(config);
|
||||
|
||||
// Remove secrets
|
||||
// Remove any legacy secrets (backward compat)
|
||||
deleteAllProfileSecrets(name);
|
||||
}
|
||||
|
||||
@@ -416,13 +439,13 @@ async function handleList(): Promise<void> {
|
||||
console.log(subheader('CLIProxy Variants'));
|
||||
const cliproxyRows = variants.map((name) => {
|
||||
const variant = unifiedConfig.cliproxy?.variants[name];
|
||||
return [name, variant?.provider || 'unknown', variant?.model || '-'];
|
||||
return [name, variant?.provider || 'unknown', variant?.settings || '-'];
|
||||
});
|
||||
|
||||
console.log(
|
||||
table(cliproxyRows, {
|
||||
head: ['Variant', 'Provider', 'Model'],
|
||||
colWidths: [15, 15, 20],
|
||||
head: ['Variant', 'Provider', 'Settings'],
|
||||
colWidths: [15, 15, 30],
|
||||
})
|
||||
);
|
||||
console.log('');
|
||||
|
||||
@@ -254,6 +254,7 @@ function removeCliproxyVariant(name: string): { provider: string; settings: stri
|
||||
|
||||
/**
|
||||
* Add CLIProxy variant to unified config (config.yaml)
|
||||
* Creates *.settings.json file and stores reference in config.yaml
|
||||
*/
|
||||
function addCliproxyVariantUnified(
|
||||
name: string,
|
||||
@@ -261,9 +262,37 @@ function addCliproxyVariantUnified(
|
||||
model: string,
|
||||
account?: string
|
||||
): void {
|
||||
const ccsDir = path.join(require('os').homedir(), '.ccs');
|
||||
const settingsFile = `${provider}-${name}.settings.json`;
|
||||
const settingsPath = path.join(ccsDir, settingsFile);
|
||||
|
||||
// Get base env vars from provider config
|
||||
const baseEnv = getClaudeEnvVars(provider as CLIProxyProvider, CLIPROXY_DEFAULT_PORT);
|
||||
|
||||
// Create settings file with model override
|
||||
const settings = {
|
||||
env: {
|
||||
ANTHROPIC_BASE_URL: baseEnv.ANTHROPIC_BASE_URL || '',
|
||||
ANTHROPIC_AUTH_TOKEN: baseEnv.ANTHROPIC_AUTH_TOKEN || '',
|
||||
ANTHROPIC_MODEL: model,
|
||||
ANTHROPIC_DEFAULT_OPUS_MODEL: model,
|
||||
ANTHROPIC_DEFAULT_SONNET_MODEL: model,
|
||||
ANTHROPIC_DEFAULT_HAIKU_MODEL: baseEnv.ANTHROPIC_DEFAULT_HAIKU_MODEL || model,
|
||||
},
|
||||
};
|
||||
|
||||
// Ensure directory exists
|
||||
if (!fs.existsSync(ccsDir)) {
|
||||
fs.mkdirSync(ccsDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Write settings file
|
||||
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf8');
|
||||
|
||||
// Update config.yaml with reference
|
||||
const config = loadOrCreateUnifiedConfig();
|
||||
|
||||
// Ensure cliproxy.variants section exists (preserving other fields)
|
||||
// Ensure cliproxy.variants section exists
|
||||
if (!config.cliproxy) {
|
||||
config.cliproxy = {
|
||||
oauth_accounts: {},
|
||||
@@ -275,11 +304,11 @@ function addCliproxyVariantUnified(
|
||||
config.cliproxy.variants = {};
|
||||
}
|
||||
|
||||
// Add variant
|
||||
// Add variant with settings file reference
|
||||
config.cliproxy.variants[name] = {
|
||||
provider: provider as CLIProxyProvider,
|
||||
model,
|
||||
account,
|
||||
settings: `~/.ccs/${settingsFile}`,
|
||||
};
|
||||
|
||||
saveUnifiedConfig(config);
|
||||
@@ -288,7 +317,9 @@ function addCliproxyVariantUnified(
|
||||
/**
|
||||
* Remove CLIProxy variant from unified config
|
||||
*/
|
||||
function removeCliproxyVariantUnified(name: string): { provider: string; model?: string } | null {
|
||||
function removeCliproxyVariantUnified(
|
||||
name: string
|
||||
): { provider: string; settings?: string } | null {
|
||||
const config = loadOrCreateUnifiedConfig();
|
||||
|
||||
if (!config.cliproxy?.variants || !(name in config.cliproxy.variants)) {
|
||||
@@ -296,11 +327,19 @@ function removeCliproxyVariantUnified(name: string): { provider: string; model?:
|
||||
}
|
||||
|
||||
const variant = config.cliproxy.variants[name];
|
||||
delete config.cliproxy.variants[name];
|
||||
|
||||
// Delete the settings file if it exists
|
||||
if (variant.settings) {
|
||||
const settingsPath = variant.settings.replace(/^~/, require('os').homedir());
|
||||
if (fs.existsSync(settingsPath)) {
|
||||
fs.unlinkSync(settingsPath);
|
||||
}
|
||||
}
|
||||
|
||||
delete config.cliproxy.variants[name];
|
||||
saveUnifiedConfig(config);
|
||||
|
||||
return { provider: variant.provider, model: variant.model };
|
||||
return { provider: variant.provider, settings: variant.settings };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -577,7 +616,7 @@ async function handleList(): Promise<void> {
|
||||
variantData = {};
|
||||
for (const name of variantNames) {
|
||||
const v = variants[name];
|
||||
variantData[name] = { provider: v.provider, model: v.model };
|
||||
variantData[name] = { provider: v.provider, settings: v.settings };
|
||||
}
|
||||
} else {
|
||||
const config = loadConfig();
|
||||
@@ -596,17 +635,13 @@ async function handleList(): Promise<void> {
|
||||
// Build table data
|
||||
const rows: string[][] = variantNames.map((name) => {
|
||||
const variant = variantData[name];
|
||||
const thirdCol = variant.model || variant.settings || '-';
|
||||
return [name, variant.provider, thirdCol];
|
||||
return [name, variant.provider, variant.settings || '-'];
|
||||
});
|
||||
|
||||
// Print table
|
||||
const headLabels = isUnifiedMode()
|
||||
? ['Variant', 'Provider', 'Model']
|
||||
: ['Variant', 'Provider', 'Settings'];
|
||||
console.log(
|
||||
table(rows, {
|
||||
head: headLabels,
|
||||
head: ['Variant', 'Provider', 'Settings'],
|
||||
colWidths: [15, 12, 35],
|
||||
})
|
||||
);
|
||||
@@ -642,7 +677,7 @@ async function handleRemove(args: string[]): Promise<void> {
|
||||
variantData = {};
|
||||
for (const name of variantNames) {
|
||||
const v = variants[name];
|
||||
variantData[name] = { provider: v.provider, model: v.model };
|
||||
variantData[name] = { provider: v.provider, settings: v.settings };
|
||||
}
|
||||
} else {
|
||||
const config = loadConfig();
|
||||
@@ -695,12 +730,7 @@ async function handleRemove(args: string[]): Promise<void> {
|
||||
console.log('');
|
||||
console.log(`Variant '${color(name, 'command')}' will be removed.`);
|
||||
console.log(` Provider: ${variant.provider}`);
|
||||
if (isUnifiedMode()) {
|
||||
console.log(` Model: ${variant.model || '-'}`);
|
||||
console.log(' Config: ~/.ccs/config.yaml');
|
||||
} else {
|
||||
console.log(` Settings: ${variant.settings}`);
|
||||
}
|
||||
console.log(` Settings: ${variant.settings || '-'}`);
|
||||
console.log('');
|
||||
|
||||
const confirmed =
|
||||
|
||||
@@ -5,17 +5,19 @@
|
||||
* Features:
|
||||
* - Automatic backup before migration
|
||||
* - Rollback support
|
||||
* - Secret extraction and separation
|
||||
* - Settings file reference preservation (*.settings.json)
|
||||
* - Cache file restructuring
|
||||
*
|
||||
* Design: Settings remain in *.settings.json files (matching Claude's pattern)
|
||||
* while config.yaml stores references to these files.
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { getCcsDir } from '../utils/config-manager';
|
||||
import type { ProfileConfig, AccountConfig, CLIProxyVariantConfig } from './unified-config-types';
|
||||
import { createEmptyUnifiedConfig, createEmptySecretsConfig } from './unified-config-types';
|
||||
import { createEmptyUnifiedConfig } from './unified-config-types';
|
||||
import { saveUnifiedConfig, hasUnifiedConfig } from './unified-config-loader';
|
||||
import { saveSecrets, isSecretKey } from './secrets-manager';
|
||||
|
||||
const BACKUP_DIR_PREFIX = 'backup-v1-';
|
||||
|
||||
@@ -79,7 +81,6 @@ export async function migrate(dryRun = false): Promise<MigrationResult> {
|
||||
|
||||
// 3. Build unified config
|
||||
const unifiedConfig = createEmptyUnifiedConfig();
|
||||
const secrets = createEmptySecretsConfig();
|
||||
|
||||
// Set default if exists
|
||||
if (oldProfiles?.default && typeof oldProfiles.default === 'string') {
|
||||
@@ -111,14 +112,9 @@ export async function migrate(dryRun = false): Promise<MigrationResult> {
|
||||
variant.account = oldVariant.account as string;
|
||||
}
|
||||
|
||||
// Extract model from settings file if exists
|
||||
// Keep reference to existing settings file
|
||||
if (oldVariant.settings) {
|
||||
const settingsPath = expandPath(oldVariant.settings as string);
|
||||
const settings = readJsonSafe(settingsPath);
|
||||
const env = settings?.env as Record<string, string> | undefined;
|
||||
if (env?.ANTHROPIC_MODEL) {
|
||||
variant.model = env.ANTHROPIC_MODEL;
|
||||
}
|
||||
variant.settings = oldVariant.settings as string;
|
||||
}
|
||||
|
||||
unifiedConfig.cliproxy.variants[name] = variant;
|
||||
@@ -126,96 +122,47 @@ export async function migrate(dryRun = false): Promise<MigrationResult> {
|
||||
migratedFiles.push('config.json.cliproxy → config.yaml.cliproxy.variants');
|
||||
}
|
||||
|
||||
// 6. Migrate API profiles from config.json + settings files
|
||||
// 6. Migrate API profiles from config.json
|
||||
// Keep settings in *.settings.json files (matching Claude's ~/.claude/settings.json pattern)
|
||||
// config.yaml only stores reference to the settings file
|
||||
if (oldConfig?.profiles) {
|
||||
for (const [name, settingsPath] of Object.entries(oldConfig.profiles)) {
|
||||
const expandedPath = expandPath(settingsPath as string);
|
||||
const settings = readJsonSafe(expandedPath);
|
||||
const pathStr = settingsPath as string;
|
||||
const expandedPath = expandPath(pathStr);
|
||||
|
||||
if (settings?.env) {
|
||||
// Split env into config (non-secret) and secrets
|
||||
const envConfig: Record<string, string> = {};
|
||||
const envSecrets: Record<string, string> = {};
|
||||
|
||||
for (const [key, value] of Object.entries(settings.env)) {
|
||||
if (isSecretKey(key)) {
|
||||
envSecrets[key] = value as string;
|
||||
} else {
|
||||
envConfig[key] = value as string;
|
||||
}
|
||||
}
|
||||
|
||||
const profile: ProfileConfig = {
|
||||
type: 'api',
|
||||
env: envConfig,
|
||||
};
|
||||
unifiedConfig.profiles[name] = profile;
|
||||
|
||||
if (Object.keys(envSecrets).length > 0) {
|
||||
secrets.profiles[name] = envSecrets;
|
||||
}
|
||||
|
||||
migratedFiles.push(`${name}.settings.json → config.yaml.profiles.${name}`);
|
||||
} else {
|
||||
warnings.push(`Skipped ${name}: no env vars found in ${expandedPath}`);
|
||||
// Verify settings file exists
|
||||
if (!fs.existsSync(expandedPath)) {
|
||||
warnings.push(`Skipped ${name}: settings file not found at ${pathStr}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Store reference to settings file (keep using ~ for portability)
|
||||
const profile: ProfileConfig = {
|
||||
type: 'api',
|
||||
settings: pathStr,
|
||||
};
|
||||
unifiedConfig.profiles[name] = profile;
|
||||
migratedFiles.push(`config.json.profiles.${name} → config.yaml (settings: ${pathStr})`);
|
||||
}
|
||||
}
|
||||
|
||||
// 6b. Migrate built-in CLIProxy OAuth profile settings (gemini, codex, agy, qwen, iflow)
|
||||
// These files contain user overrides like custom models or env vars (ANTHROPIC_MAX_TOKENS, etc.)
|
||||
// Keep settings in *.settings.json files - only record reference in config.yaml
|
||||
// This matches Claude's ~/.claude/settings.json pattern for user familiarity
|
||||
const builtInProviders = ['gemini', 'codex', 'agy', 'qwen', 'iflow'];
|
||||
for (const provider of builtInProviders) {
|
||||
const settingsPath = path.join(ccsDir, `${provider}.settings.json`);
|
||||
const settingsFile = `${provider}.settings.json`;
|
||||
const settingsPath = path.join(ccsDir, settingsFile);
|
||||
|
||||
if (fs.existsSync(settingsPath)) {
|
||||
const settings = readJsonSafe(settingsPath);
|
||||
const env = settings?.env as Record<string, string> | undefined;
|
||||
if (env) {
|
||||
// Extract user-configurable values (skip CCS-internal values)
|
||||
const userEnv: Record<string, string> = {};
|
||||
let userModel: string | undefined;
|
||||
// Create variant with reference to settings file
|
||||
const variant: CLIProxyVariantConfig = {
|
||||
provider: provider as CLIProxyVariantConfig['provider'],
|
||||
settings: `~/.ccs/${settingsFile}`,
|
||||
};
|
||||
|
||||
for (const [key, value] of Object.entries(env)) {
|
||||
// Skip internal CCS-managed values (these are auto-generated at runtime)
|
||||
if (key === 'ANTHROPIC_AUTH_TOKEN' && value === 'ccs-internal-managed') continue;
|
||||
if (key === 'ANTHROPIC_BASE_URL' && value.includes('127.0.0.1')) continue;
|
||||
// Skip model tier settings that are derived from primary model
|
||||
if (key === 'ANTHROPIC_DEFAULT_OPUS_MODEL') continue;
|
||||
if (key === 'ANTHROPIC_DEFAULT_SONNET_MODEL') continue;
|
||||
if (key === 'ANTHROPIC_DEFAULT_HAIKU_MODEL') continue;
|
||||
// Skip secrets (should not be in CLIProxy settings anyway)
|
||||
if (isSecretKey(key)) continue;
|
||||
|
||||
// Extract model separately
|
||||
if (key === 'ANTHROPIC_MODEL') {
|
||||
userModel = value;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Keep other user-configurable env vars
|
||||
userEnv[key] = value;
|
||||
}
|
||||
|
||||
// Only create variant if there's user customization
|
||||
if (userModel || Object.keys(userEnv).length > 0) {
|
||||
const variant: CLIProxyVariantConfig = {
|
||||
provider: provider as CLIProxyVariantConfig['provider'],
|
||||
};
|
||||
|
||||
if (userModel) {
|
||||
variant.model = userModel;
|
||||
}
|
||||
|
||||
if (Object.keys(userEnv).length > 0) {
|
||||
variant.env = userEnv;
|
||||
}
|
||||
|
||||
unifiedConfig.cliproxy.variants[provider] = variant;
|
||||
migratedFiles.push(
|
||||
`${provider}.settings.json → config.yaml.cliproxy.variants.${provider}`
|
||||
);
|
||||
}
|
||||
}
|
||||
unifiedConfig.cliproxy.variants[provider] = variant;
|
||||
migratedFiles.push(`${settingsFile} → config.yaml.cliproxy.variants.${provider}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,14 +187,10 @@ export async function migrate(dryRun = false): Promise<MigrationResult> {
|
||||
}
|
||||
}
|
||||
|
||||
// 8. Write new configs (unless dry run)
|
||||
// 8. Write new config (unless dry run)
|
||||
// Note: Settings remain in *.settings.json files, config.yaml only stores references
|
||||
if (!dryRun) {
|
||||
saveUnifiedConfig(unifiedConfig);
|
||||
|
||||
if (Object.keys(secrets.profiles).length > 0) {
|
||||
saveSecrets(secrets);
|
||||
migratedFiles.push('secrets extracted → secrets.yaml');
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -112,16 +112,18 @@ function generateYamlHeader(): string {
|
||||
# Generated by: ccs migrate
|
||||
# Documentation: https://github.com/kaitranntt/ccs
|
||||
#
|
||||
# This file consolidates all CCS configuration in a single, human-readable format.
|
||||
# Secrets (API keys, tokens) are stored separately in secrets.yaml (chmod 600).
|
||||
# This file references your settings - actual env vars are in *.settings.json
|
||||
# files (matching Claude's ~/.claude/settings.json pattern).
|
||||
#
|
||||
# To customize a profile:
|
||||
# 1. Edit the *.settings.json file directly (e.g., ~/.ccs/glm.settings.json)
|
||||
# 2. The file format matches Claude's settings.json: { "env": { ... } }
|
||||
#
|
||||
# Structure:
|
||||
# ┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
# │ version - Config format version (do not modify) │
|
||||
# │ default - Default profile name when running 'ccs' without args │
|
||||
# │ profiles - References to *.settings.json files for API providers │
|
||||
# │ cliproxy - References to *.settings.json files for OAuth providers │
|
||||
# │ accounts - Isolated Claude instances (managed by 'ccs auth') │
|
||||
# │ profiles - API providers like GLM, GLMT, Kimi (managed by 'ccs api') │
|
||||
# │ cliproxy - OAuth providers: gemini, codex, agy (managed by 'ccs') │
|
||||
# │ preferences - User preferences (theme, telemetry, auto-update) │
|
||||
# └─────────────────────────────────────────────────────────────────────────────┘
|
||||
#
|
||||
@@ -164,8 +166,8 @@ function generateYamlWithComments(config: UnifiedConfig): string {
|
||||
// Profiles section
|
||||
lines.push('# ----------------------------------------------------------------------------');
|
||||
lines.push('# Profiles: API-based providers (GLM, GLMT, Kimi, custom endpoints)');
|
||||
lines.push('# Manage with: ccs api add <name>, ccs api list, ccs api remove <name>');
|
||||
lines.push('# Note: env vars like ANTHROPIC_MAX_TOKENS go here, secrets in secrets.yaml');
|
||||
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()
|
||||
@@ -175,8 +177,8 @@ function generateYamlWithComments(config: UnifiedConfig): string {
|
||||
// CLIProxy section
|
||||
lines.push('# ----------------------------------------------------------------------------');
|
||||
lines.push('# CLIProxy: OAuth-based providers (gemini, codex, agy, qwen, iflow)');
|
||||
lines.push('# Manage with: ccs cliproxy create, ccs cliproxy list, ccs cliproxy remove');
|
||||
lines.push('# Built-in providers require no config - just run: ccs gemini --auth');
|
||||
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()
|
||||
|
||||
@@ -29,12 +29,15 @@ export interface AccountConfig {
|
||||
/**
|
||||
* API-based profile configuration.
|
||||
* Injects environment variables for alternative providers (GLM, Kimi, etc.).
|
||||
*
|
||||
* Settings are stored in separate *.settings.json files (matching Claude's pattern)
|
||||
* to allow users to edit them directly without touching config.yaml.
|
||||
*/
|
||||
export interface ProfileConfig {
|
||||
/** Profile type - currently only 'api' */
|
||||
type: 'api';
|
||||
/** Environment variables (non-secret values only) */
|
||||
env: Record<string, string>;
|
||||
/** Path to settings file (e.g., "~/.ccs/glm.settings.json") */
|
||||
settings: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -46,16 +49,17 @@ export type OAuthAccounts = Record<string, string>;
|
||||
/**
|
||||
* CLIProxy variant configuration.
|
||||
* User-defined variants of built-in OAuth providers.
|
||||
*
|
||||
* Settings are stored in separate *.settings.json files (matching Claude's pattern)
|
||||
* to allow users to edit them directly without touching config.yaml.
|
||||
*/
|
||||
export interface CLIProxyVariantConfig {
|
||||
/** Base provider to use */
|
||||
provider: 'gemini' | 'codex' | 'agy' | 'qwen' | 'iflow';
|
||||
/** Account nickname (references oauth_accounts) */
|
||||
account?: string;
|
||||
/** Model override (inline, no separate settings file) */
|
||||
model?: string;
|
||||
/** Additional env var overrides (ANTHROPIC_MAX_TOKENS, MAX_THINKING_TOKENS, etc.) */
|
||||
env?: Record<string, string>;
|
||||
/** Path to settings file (e.g., "~/.ccs/gemini-custom.settings.json") */
|
||||
settings?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user