Files
ccs/src/utils/delegation-validator.ts
T

165 lines
4.8 KiB
JavaScript

#!/usr/bin/env node
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import { Settings } from '../types';
interface ValidationResult {
valid: boolean;
error?: string;
suggestion?: string;
settingsPath?: string;
apiKey?: string;
}
/**
* Validates delegation profiles for CCS delegation system
* Ensures profiles exist and have valid API keys configured
*/
export class DelegationValidator {
/**
* Validate a delegation profile
* @param profileName - Name of profile to validate (e.g., 'glm', 'kimi')
* @returns Validation result { valid: boolean, error?: string, settingsPath?: string }
*/
static validate(profileName: string): ValidationResult {
const homeDir = os.homedir();
const settingsPath = path.join(homeDir, '.ccs', `${profileName}.settings.json`);
// Check if profile directory exists
if (!fs.existsSync(settingsPath)) {
return {
valid: false,
error: `Profile not found: ${profileName}`,
suggestion:
`Profile settings missing at: ${settingsPath}\n\n` +
`To set up ${profileName} profile:\n` +
` 1. Copy base settings: cp config/base-${profileName}.settings.json ~/.ccs/${profileName}.settings.json\n` +
` 2. Edit settings: Edit ~/.ccs/${profileName}.settings.json\n` +
` 3. Set your API key in ANTHROPIC_AUTH_TOKEN field`,
};
}
// Read and parse settings.json
let settings: Settings;
try {
const settingsContent = fs.readFileSync(settingsPath, 'utf8');
settings = JSON.parse(settingsContent);
} catch (error) {
return {
valid: false,
error: `Failed to parse settings.json for ${profileName}`,
suggestion:
`Settings file is corrupted or invalid JSON.\n\n` +
`Location: ${settingsPath}\n` +
`Parse error: ${(error as Error).message}\n\n` +
`Fix: Restore from base config:\n` +
` cp config/base-${profileName}.settings.json ~/.ccs/${profileName}.settings.json`,
};
}
// Validate API key exists and is not default
const apiKey = settings.env?.ANTHROPIC_AUTH_TOKEN;
if (!apiKey) {
return {
valid: false,
error: `API key not configured for ${profileName}`,
suggestion:
`Missing ANTHROPIC_AUTH_TOKEN in settings.\n\n` +
`Edit: ${settingsPath}\n` +
`Set: env.ANTHROPIC_AUTH_TOKEN to your API key`,
};
}
// Check for default placeholder values
const defaultPlaceholders = [
'YOUR_GLM_API_KEY_HERE',
'YOUR_KIMI_API_KEY_HERE',
'YOUR_API_KEY_HERE',
'your-api-key-here',
'PLACEHOLDER',
];
if (defaultPlaceholders.some((placeholder) => apiKey.includes(placeholder))) {
return {
valid: false,
error: `Default API key placeholder detected for ${profileName}`,
suggestion:
`API key is still set to default placeholder.\n\n` +
`To configure your profile:\n` +
` 1. Edit: ${settingsPath}\n` +
` 2. Replace ANTHROPIC_AUTH_TOKEN with your actual API key\n\n` +
`Get API key:\n` +
` GLM: https://z.ai/manage-apikey/apikey-list\n` +
` Kimi: https://platform.moonshot.cn/console/api-keys`,
};
}
// Validation passed
return {
valid: true,
settingsPath,
apiKey: apiKey.substring(0, 8) + '...', // Show first 8 chars for verification
};
}
/**
* Format validation error for display
* @param result - Validation result from validate()
* @returns Formatted error message
*/
static formatError(result: ValidationResult): string {
if (result.valid) {
return '';
}
let message = `\n[X] ${result.error}\n\n`;
if (result.suggestion) {
message += `${result.suggestion}\n`;
}
return message;
}
/**
* Check if profile is delegation-ready (shorthand)
* @param profileName - Profile to check
* @returns True if ready for delegation
*/
static isReady(profileName: string): boolean {
const result = this.validate(profileName);
return result.valid;
}
/**
* Get all delegation-ready profiles
* @returns List of profile names ready for delegation
*/
static getReadyProfiles(): string[] {
const homeDir = os.homedir();
const ccsDir = path.join(homeDir, '.ccs');
if (!fs.existsSync(ccsDir)) {
return [];
}
const profiles: string[] = [];
const entries = fs.readdirSync(ccsDir, { withFileTypes: true });
// Look for *.settings.json files
for (const entry of entries) {
if (entry.isFile() && entry.name.endsWith('.settings.json')) {
const profileName = entry.name.replace('.settings.json', '');
if (this.isReady(profileName)) {
profiles.push(profileName);
}
}
}
return profiles;
}
}