mirror of
https://github.com/tiennm99/ccs.git
synced 2026-08-14 14:25:20 +00:00
feat(cliproxy): disable logging by default and add cleanup command
- Disable CLIProxy logging by default to prevent 5-12GB disk bloat - Add cliproxy.logging config option for user opt-in via ~/.ccs/config.yaml - Add `ccs cleanup` command to remove old CLIProxy logs - Bump CLIProxy config version to v3 to trigger config regeneration Closes #96
This commit is contained in:
@@ -252,6 +252,13 @@ async function main(): Promise<void> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Special case: cleanup command
|
||||||
|
if (firstArg === 'cleanup' || firstArg === '--cleanup') {
|
||||||
|
const { handleCleanupCommand } = await import('./commands/cleanup-command');
|
||||||
|
await handleCleanupCommand(args.slice(1));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Special case: migrate command
|
// Special case: migrate command
|
||||||
if (firstArg === 'migrate' || firstArg === '--migrate') {
|
if (firstArg === 'migrate' || firstArg === '--migrate') {
|
||||||
const { handleMigrateCommand, printMigrateHelp } = await import('./commands/migrate-command');
|
const { handleMigrateCommand, printMigrateHelp } = await import('./commands/migrate-command');
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { getCcsDir } from '../utils/config-manager';
|
|||||||
import { warn } from '../utils/ui';
|
import { warn } from '../utils/ui';
|
||||||
import { CLIProxyProvider, ProviderConfig, ProviderModelMapping } from './types';
|
import { CLIProxyProvider, ProviderConfig, ProviderModelMapping } from './types';
|
||||||
import { getModelMappingFromConfig, getEnvVarsFromConfig } from './base-config-loader';
|
import { getModelMappingFromConfig, getEnvVarsFromConfig } from './base-config-loader';
|
||||||
|
import { loadOrCreateUnifiedConfig } from '../config/unified-config-loader';
|
||||||
|
|
||||||
/** Settings file structure for user overrides */
|
/** Settings file structure for user overrides */
|
||||||
interface ProviderSettings {
|
interface ProviderSettings {
|
||||||
@@ -33,8 +34,9 @@ export const CCS_CONTROL_PANEL_SECRET = 'ccs';
|
|||||||
* Config version - bump when config format changes to trigger regeneration
|
* Config version - bump when config format changes to trigger regeneration
|
||||||
* v1: Initial config (port, auth-dir, api-keys only)
|
* v1: Initial config (port, auth-dir, api-keys only)
|
||||||
* v2: Full-featured config with dashboard, quota mgmt, simplified key
|
* v2: Full-featured config with dashboard, quota mgmt, simplified key
|
||||||
|
* v3: Logging disabled by default (user opt-in via ~/.ccs/config.yaml)
|
||||||
*/
|
*/
|
||||||
export const CLIPROXY_CONFIG_VERSION = 2;
|
export const CLIPROXY_CONFIG_VERSION = 3;
|
||||||
|
|
||||||
/** Provider display names (static metadata) */
|
/** Provider display names (static metadata) */
|
||||||
const PROVIDER_DISPLAY_NAMES: Record<CLIProxyProvider, string> = {
|
const PROVIDER_DISPLAY_NAMES: Record<CLIProxyProvider, string> = {
|
||||||
@@ -115,6 +117,18 @@ export function getBinDir(): string {
|
|||||||
return path.join(getCliproxyDir(), 'bin');
|
return path.join(getCliproxyDir(), 'bin');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get CLIProxy logging settings from user config.
|
||||||
|
* Defaults to disabled to prevent disk bloat.
|
||||||
|
*/
|
||||||
|
function getLoggingSettings(): { loggingToFile: boolean; requestLog: boolean } {
|
||||||
|
const config = loadOrCreateUnifiedConfig();
|
||||||
|
return {
|
||||||
|
loggingToFile: config.cliproxy.logging?.enabled ?? false,
|
||||||
|
requestLog: config.cliproxy.logging?.request_log ?? false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate UNIFIED config.yaml content for ALL providers
|
* Generate UNIFIED config.yaml content for ALL providers
|
||||||
* This enables concurrent usage of gemini/codex/agy without config conflicts.
|
* This enables concurrent usage of gemini/codex/agy without config conflicts.
|
||||||
@@ -125,6 +139,9 @@ function generateUnifiedConfigContent(port: number = CLIPROXY_DEFAULT_PORT): str
|
|||||||
// Convert Windows backslashes to forward slashes for YAML compatibility
|
// Convert Windows backslashes to forward slashes for YAML compatibility
|
||||||
const authDirNormalized = authDir.split(path.sep).join('/');
|
const authDirNormalized = authDir.split(path.sep).join('/');
|
||||||
|
|
||||||
|
// Get logging settings from user config (disabled by default)
|
||||||
|
const { loggingToFile, requestLog } = getLoggingSettings();
|
||||||
|
|
||||||
// Unified config with enhanced CLIProxyAPI features
|
// Unified config with enhanced CLIProxyAPI features
|
||||||
const config = `# CLIProxyAPI config generated by CCS v${CLIPROXY_CONFIG_VERSION}
|
const config = `# CLIProxyAPI config generated by CCS v${CLIPROXY_CONFIG_VERSION}
|
||||||
# Supports: gemini, codex, agy, qwen, iflow (concurrent usage)
|
# Supports: gemini, codex, agy, qwen, iflow (concurrent usage)
|
||||||
@@ -143,12 +160,20 @@ debug: false
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Logging
|
# Logging
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
# WARNING: Logs can grow to several GB if enabled!
|
||||||
|
# To enable logging, edit ~/.ccs/config.yaml:
|
||||||
|
# cliproxy:
|
||||||
|
# logging:
|
||||||
|
# enabled: true
|
||||||
|
# request_log: true
|
||||||
|
# Then run 'ccs doctor --fix' to regenerate this config.
|
||||||
|
# Use 'ccs cleanup' to remove old logs.
|
||||||
|
|
||||||
# Write logs to file (stored in ~/.ccs/cliproxy/logs/)
|
# Write logs to file (stored in ~/.ccs/cliproxy/logs/)
|
||||||
logging-to-file: true
|
logging-to-file: ${loggingToFile}
|
||||||
|
|
||||||
# Log individual API requests for debugging/analytics
|
# Log individual API requests for debugging/analytics
|
||||||
request-log: true
|
request-log: ${requestLog}
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Dashboard & Management
|
# Dashboard & Management
|
||||||
|
|||||||
@@ -0,0 +1,193 @@
|
|||||||
|
/**
|
||||||
|
* Cleanup Command Handler
|
||||||
|
*
|
||||||
|
* Removes old CLIProxy logs to free up disk space.
|
||||||
|
* Logs can accumulate to several GB without user awareness.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
import { getCliproxyDir } from '../cliproxy/config-generator';
|
||||||
|
import { info, ok, warn } from '../utils/ui';
|
||||||
|
|
||||||
|
/** Get the CLIProxy logs directory */
|
||||||
|
function getLogsDir(): string {
|
||||||
|
return path.join(getCliproxyDir(), 'logs');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Format bytes to human-readable size */
|
||||||
|
function formatBytes(bytes: number): string {
|
||||||
|
if (bytes === 0) return '0 B';
|
||||||
|
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
||||||
|
return `${(bytes / Math.pow(1024, i)).toFixed(2)} ${units[i]}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Calculate total size of a directory */
|
||||||
|
function getDirSize(dirPath: string): number {
|
||||||
|
if (!fs.existsSync(dirPath)) return 0;
|
||||||
|
|
||||||
|
let totalSize = 0;
|
||||||
|
const files = fs.readdirSync(dirPath);
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
const filePath = path.join(dirPath, file);
|
||||||
|
try {
|
||||||
|
const stats = fs.lstatSync(filePath); // Use lstat to detect symlinks
|
||||||
|
if (stats.isFile() && !stats.isSymbolicLink()) {
|
||||||
|
totalSize += stats.size;
|
||||||
|
} else if (stats.isDirectory() && !stats.isSymbolicLink()) {
|
||||||
|
totalSize += getDirSize(filePath);
|
||||||
|
}
|
||||||
|
// Skip symlinks for safety
|
||||||
|
} catch {
|
||||||
|
// File may have been deleted between readdir and stat - skip
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return totalSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Count files in a directory */
|
||||||
|
function countFiles(dirPath: string): number {
|
||||||
|
if (!fs.existsSync(dirPath)) return 0;
|
||||||
|
let count = 0;
|
||||||
|
const entries = fs.readdirSync(dirPath);
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
const filePath = path.join(dirPath, entry);
|
||||||
|
try {
|
||||||
|
const stats = fs.lstatSync(filePath);
|
||||||
|
if (stats.isFile() && !stats.isSymbolicLink()) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// File may have been deleted - skip
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Delete all regular files in a directory (skips symlinks for safety) */
|
||||||
|
function cleanDirectory(dirPath: string): { deleted: number; freedBytes: number } {
|
||||||
|
if (!fs.existsSync(dirPath)) return { deleted: 0, freedBytes: 0 };
|
||||||
|
|
||||||
|
let deleted = 0;
|
||||||
|
let freedBytes = 0;
|
||||||
|
const files = fs.readdirSync(dirPath);
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
const filePath = path.join(dirPath, file);
|
||||||
|
try {
|
||||||
|
const stats = fs.lstatSync(filePath);
|
||||||
|
|
||||||
|
// Only delete regular files, skip symlinks for security
|
||||||
|
if (stats.isFile() && !stats.isSymbolicLink()) {
|
||||||
|
freedBytes += stats.size;
|
||||||
|
fs.unlinkSync(filePath);
|
||||||
|
deleted++;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// File may have been deleted or inaccessible - skip
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { deleted, freedBytes };
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Print help for cleanup command */
|
||||||
|
function printHelp(): void {
|
||||||
|
console.log('');
|
||||||
|
console.log('Usage: ccs cleanup [options]');
|
||||||
|
console.log('');
|
||||||
|
console.log('Remove old CLIProxy logs to free up disk space.');
|
||||||
|
console.log('');
|
||||||
|
console.log('Options:');
|
||||||
|
console.log(' --dry-run Show what would be deleted without deleting');
|
||||||
|
console.log(' --force Skip confirmation prompt');
|
||||||
|
console.log(' --help, -h Show this help message');
|
||||||
|
console.log('');
|
||||||
|
console.log('Examples:');
|
||||||
|
console.log(' ccs cleanup Interactive cleanup with confirmation');
|
||||||
|
console.log(' ccs cleanup --dry-run Preview cleanup without deleting');
|
||||||
|
console.log(' ccs cleanup --force Clean without confirmation');
|
||||||
|
console.log('');
|
||||||
|
console.log('Note: CLIProxy logging is disabled by default.');
|
||||||
|
console.log('To enable logging, edit ~/.ccs/config.yaml:');
|
||||||
|
console.log(' cliproxy:');
|
||||||
|
console.log(' logging:');
|
||||||
|
console.log(' enabled: true');
|
||||||
|
console.log('');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle cleanup command
|
||||||
|
*/
|
||||||
|
export async function handleCleanupCommand(args: string[]): Promise<void> {
|
||||||
|
// Handle help
|
||||||
|
if (args.includes('--help') || args.includes('-h')) {
|
||||||
|
printHelp();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dryRun = args.includes('--dry-run');
|
||||||
|
const force = args.includes('--force');
|
||||||
|
const logsDir = getLogsDir();
|
||||||
|
|
||||||
|
// Check if logs directory exists
|
||||||
|
if (!fs.existsSync(logsDir)) {
|
||||||
|
console.log(info('No CLIProxy logs found.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate current size
|
||||||
|
const currentSize = getDirSize(logsDir);
|
||||||
|
const fileCount = countFiles(logsDir);
|
||||||
|
|
||||||
|
if (fileCount === 0) {
|
||||||
|
console.log(info('No log files to clean.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('');
|
||||||
|
console.log(`CLIProxy Logs: ${logsDir}`);
|
||||||
|
console.log(` Files: ${fileCount}`);
|
||||||
|
console.log(` Size: ${formatBytes(currentSize)}`);
|
||||||
|
console.log('');
|
||||||
|
|
||||||
|
if (dryRun) {
|
||||||
|
console.log(info('Dry run - no files deleted.'));
|
||||||
|
console.log(`Would delete ${fileCount} files (${formatBytes(currentSize)})`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Confirm unless --force
|
||||||
|
if (!force) {
|
||||||
|
const readline = await import('readline');
|
||||||
|
const rl = readline.createInterface({
|
||||||
|
input: process.stdin,
|
||||||
|
output: process.stdout,
|
||||||
|
});
|
||||||
|
|
||||||
|
const answer = await new Promise<string>((resolve) => {
|
||||||
|
rl.question(`Delete ${fileCount} log files (${formatBytes(currentSize)})? [y/N] `, resolve);
|
||||||
|
});
|
||||||
|
rl.close();
|
||||||
|
|
||||||
|
if (answer.toLowerCase() !== 'y') {
|
||||||
|
console.log('Cancelled.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform cleanup
|
||||||
|
const { deleted, freedBytes } = cleanDirectory(logsDir);
|
||||||
|
console.log(ok(`Deleted ${deleted} files, freed ${formatBytes(freedBytes)}`));
|
||||||
|
|
||||||
|
// Suggest disabling logging if it was enabled
|
||||||
|
if (deleted > 0) {
|
||||||
|
console.log('');
|
||||||
|
console.log(warn('Tip: CLIProxy logging is now disabled by default.'));
|
||||||
|
console.log(' Run `ccs doctor --fix` to update your config.');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -186,6 +186,7 @@ Claude Code Profile & Model Switcher`.trim();
|
|||||||
// Diagnostics
|
// Diagnostics
|
||||||
printSubSection('Diagnostics', [
|
printSubSection('Diagnostics', [
|
||||||
['ccs doctor', 'Run health check and diagnostics'],
|
['ccs doctor', 'Run health check and diagnostics'],
|
||||||
|
['ccs cleanup', 'Remove old CLIProxy logs'],
|
||||||
['ccs config', 'Open web configuration dashboard'],
|
['ccs config', 'Open web configuration dashboard'],
|
||||||
['ccs config --port 3000', 'Use specific port'],
|
['ccs config --port 3000', 'Use specific port'],
|
||||||
['ccs sync', 'Sync delegation commands and skills'],
|
['ccs sync', 'Sync delegation commands and skills'],
|
||||||
|
|||||||
@@ -105,6 +105,11 @@ function mergeWithDefaults(partial: Partial<UnifiedConfig>): UnifiedConfig {
|
|||||||
oauth_accounts: partial.cliproxy?.oauth_accounts ?? defaults.cliproxy.oauth_accounts,
|
oauth_accounts: partial.cliproxy?.oauth_accounts ?? defaults.cliproxy.oauth_accounts,
|
||||||
providers: defaults.cliproxy.providers, // Always use defaults for providers
|
providers: defaults.cliproxy.providers, // Always use defaults for providers
|
||||||
variants: partial.cliproxy?.variants ?? defaults.cliproxy.variants,
|
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,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
preferences: {
|
preferences: {
|
||||||
...defaults.preferences,
|
...defaults.preferences,
|
||||||
|
|||||||
@@ -62,6 +62,18 @@ export interface CLIProxyVariantConfig {
|
|||||||
settings?: string;
|
settings?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CLIProxy logging configuration.
|
||||||
|
* Controls whether CLIProxyAPI writes logs to disk.
|
||||||
|
* Logs can grow to several GB if left enabled.
|
||||||
|
*/
|
||||||
|
export interface CLIProxyLoggingConfig {
|
||||||
|
/** Enable logging to file (default: false to prevent disk bloat) */
|
||||||
|
enabled?: boolean;
|
||||||
|
/** Enable request logging for debugging (default: false) */
|
||||||
|
request_log?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CLIProxy configuration section.
|
* CLIProxy configuration section.
|
||||||
*/
|
*/
|
||||||
@@ -72,6 +84,8 @@ export interface CLIProxyConfig {
|
|||||||
providers: readonly string[];
|
providers: readonly string[];
|
||||||
/** User-defined provider variants */
|
/** User-defined provider variants */
|
||||||
variants: Record<string, CLIProxyVariantConfig>;
|
variants: Record<string, CLIProxyVariantConfig>;
|
||||||
|
/** Logging configuration (disabled by default) */
|
||||||
|
logging?: CLIProxyLoggingConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -130,6 +144,10 @@ export function createEmptyUnifiedConfig(): UnifiedConfig {
|
|||||||
oauth_accounts: {},
|
oauth_accounts: {},
|
||||||
providers: ['gemini', 'codex', 'agy', 'qwen', 'iflow'],
|
providers: ['gemini', 'codex', 'agy', 'qwen', 'iflow'],
|
||||||
variants: {},
|
variants: {},
|
||||||
|
logging: {
|
||||||
|
enabled: false,
|
||||||
|
request_log: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
preferences: {
|
preferences: {
|
||||||
theme: 'system',
|
theme: 'system',
|
||||||
|
|||||||
Reference in New Issue
Block a user