mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 14:16:43 +00:00
feat(ui): enhance section headers with gradient and rename profile to api
- Add sectionHeader() with gradient + bold styling - Rename profile-command.ts to api-command.ts - Update help command layout - Change command color from italic to bold
This commit is contained in:
+4
-4
@@ -253,10 +253,10 @@ async function main(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
// Special case: profile command
|
||||
if (firstArg === 'profile') {
|
||||
const { handleProfileCommand } = await import('./commands/profile-command');
|
||||
await handleProfileCommand(args.slice(1));
|
||||
// Special case: api command (manages API profiles)
|
||||
if (firstArg === 'api') {
|
||||
const { handleApiCommand } = await import('./commands/api-command');
|
||||
await handleApiCommand(args.slice(1));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/**
|
||||
* Profile Command Handler
|
||||
* API Command Handler
|
||||
*
|
||||
* Manages CCS profiles for custom API providers.
|
||||
* Commands: create, list
|
||||
* Manages CCS API profiles for custom API providers.
|
||||
* Commands: create, list, remove
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
import { InteractivePrompt } from '../utils/prompt';
|
||||
import { getCcsDir, getConfigPath, loadConfig } from '../utils/config-manager';
|
||||
|
||||
interface ProfileCommandArgs {
|
||||
interface ApiCommandArgs {
|
||||
name?: string;
|
||||
baseUrl?: string;
|
||||
apiKey?: string;
|
||||
@@ -33,10 +33,10 @@ interface ProfileCommandArgs {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse command line arguments for profile commands
|
||||
* Parse command line arguments for api commands
|
||||
*/
|
||||
function parseArgs(args: string[]): ProfileCommandArgs {
|
||||
const result: ProfileCommandArgs = {};
|
||||
function parseArgs(args: string[]): ApiCommandArgs {
|
||||
const result: ApiCommandArgs = {};
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i];
|
||||
@@ -60,20 +60,20 @@ function parseArgs(args: string[]): ProfileCommandArgs {
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate profile name
|
||||
* Validate API profile name
|
||||
*/
|
||||
function validateProfileName(name: string): string | null {
|
||||
function validateApiName(name: string): string | null {
|
||||
if (!name) {
|
||||
return 'Profile name is required';
|
||||
return 'API name is required';
|
||||
}
|
||||
if (!/^[a-zA-Z][a-zA-Z0-9._-]*$/.test(name)) {
|
||||
return 'Profile name must start with letter, contain only letters, numbers, dot, dash, underscore';
|
||||
return 'API name must start with letter, contain only letters, numbers, dot, dash, underscore';
|
||||
}
|
||||
if (name.length > 32) {
|
||||
return 'Profile name must be 32 characters or less';
|
||||
return 'API name must be 32 characters or less';
|
||||
}
|
||||
// Reserved names
|
||||
const reserved = ['default', 'auth', 'profile', 'doctor', 'sync', 'update', 'help', 'version'];
|
||||
const reserved = ['default', 'auth', 'api', 'doctor', 'sync', 'update', 'help', 'version'];
|
||||
if (reserved.includes(name.toLowerCase())) {
|
||||
return `'${name}' is a reserved name`;
|
||||
}
|
||||
@@ -96,9 +96,9 @@ function validateUrl(url: string): string | null {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if profile already exists in config.json
|
||||
* Check if API profile already exists in config.json
|
||||
*/
|
||||
function profileExists(name: string): boolean {
|
||||
function apiExists(name: string): boolean {
|
||||
try {
|
||||
const config = loadConfig();
|
||||
return name in config.profiles;
|
||||
@@ -108,7 +108,7 @@ function profileExists(name: string): boolean {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create settings.json file for profile
|
||||
* Create settings.json file for API profile
|
||||
*/
|
||||
function createSettingsFile(name: string, baseUrl: string, apiKey: string, model: string): string {
|
||||
const ccsDir = getCcsDir();
|
||||
@@ -127,7 +127,7 @@ function createSettingsFile(name: string, baseUrl: string, apiKey: string, model
|
||||
}
|
||||
|
||||
/**
|
||||
* Update config.json with new profile
|
||||
* Update config.json with new API profile
|
||||
*/
|
||||
function updateConfig(name: string, _settingsPath: string): void {
|
||||
const configPath = getConfigPath();
|
||||
@@ -157,7 +157,7 @@ function updateConfig(name: string, _settingsPath: string): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle 'ccs profile create' command
|
||||
* Handle 'ccs api create' command
|
||||
*/
|
||||
async function handleCreate(args: string[]): Promise<void> {
|
||||
await initUI();
|
||||
@@ -166,14 +166,14 @@ async function handleCreate(args: string[]): Promise<void> {
|
||||
console.log(header('Create API Profile'));
|
||||
console.log('');
|
||||
|
||||
// Step 1: Profile name
|
||||
// Step 1: API name
|
||||
let name = parsedArgs.name;
|
||||
if (!name) {
|
||||
name = await InteractivePrompt.input('Profile name', {
|
||||
validate: validateProfileName,
|
||||
name = await InteractivePrompt.input('API name', {
|
||||
validate: validateApiName,
|
||||
});
|
||||
} else {
|
||||
const error = validateProfileName(name);
|
||||
const error = validateApiName(name);
|
||||
if (error) {
|
||||
console.log(fail(error));
|
||||
process.exit(1);
|
||||
@@ -181,8 +181,8 @@ async function handleCreate(args: string[]): Promise<void> {
|
||||
}
|
||||
|
||||
// Check if exists
|
||||
if (profileExists(name) && !parsedArgs.force) {
|
||||
console.log(fail(`Profile '${name}' already exists`));
|
||||
if (apiExists(name) && !parsedArgs.force) {
|
||||
console.log(fail(`API '${name}' already exists`));
|
||||
console.log(` Use ${color('--force', 'command')} to overwrite`);
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -223,7 +223,7 @@ async function handleCreate(args: string[]): Promise<void> {
|
||||
|
||||
// Create files
|
||||
console.log('');
|
||||
console.log(info('Creating profile...'));
|
||||
console.log(info('Creating API profile...'));
|
||||
|
||||
try {
|
||||
const settingsPath = createSettingsFile(name, baseUrl, apiKey, model);
|
||||
@@ -232,11 +232,11 @@ async function handleCreate(args: string[]): Promise<void> {
|
||||
console.log('');
|
||||
console.log(
|
||||
infoBox(
|
||||
`Profile: ${name}\n` +
|
||||
`API: ${name}\n` +
|
||||
`Settings: ~/.ccs/${name}.settings.json\n` +
|
||||
`Base URL: ${baseUrl}\n` +
|
||||
`Model: ${model}`,
|
||||
'Profile Created'
|
||||
'API Profile Created'
|
||||
)
|
||||
);
|
||||
console.log('');
|
||||
@@ -244,18 +244,18 @@ async function handleCreate(args: string[]): Promise<void> {
|
||||
console.log(` ${color(`ccs ${name} "your prompt"`, 'command')}`);
|
||||
console.log('');
|
||||
} catch (error) {
|
||||
console.log(fail(`Failed to create profile: ${(error as Error).message}`));
|
||||
console.log(fail(`Failed to create API profile: ${(error as Error).message}`));
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if profile has real API key (not placeholder)
|
||||
* Check if API profile has real API key (not placeholder)
|
||||
*/
|
||||
function isProfileConfigured(profileName: string): boolean {
|
||||
function isApiConfigured(apiName: string): boolean {
|
||||
try {
|
||||
const ccsDir = getCcsDir();
|
||||
const settingsPath = path.join(ccsDir, `${profileName}.settings.json`);
|
||||
const settingsPath = path.join(ccsDir, `${apiName}.settings.json`);
|
||||
if (!fs.existsSync(settingsPath)) return false;
|
||||
|
||||
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
||||
@@ -268,31 +268,31 @@ function isProfileConfigured(profileName: string): boolean {
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle 'ccs profile list' command
|
||||
* Handle 'ccs api list' command
|
||||
*/
|
||||
async function handleList(): Promise<void> {
|
||||
await initUI();
|
||||
|
||||
console.log(header('CCS Profiles'));
|
||||
console.log(header('CCS API Profiles'));
|
||||
console.log('');
|
||||
|
||||
try {
|
||||
const config = loadConfig();
|
||||
const profiles = Object.keys(config.profiles);
|
||||
const apis = Object.keys(config.profiles);
|
||||
|
||||
if (profiles.length === 0) {
|
||||
console.log(warn('No profiles configured'));
|
||||
if (apis.length === 0) {
|
||||
console.log(warn('No API profiles configured'));
|
||||
console.log('');
|
||||
console.log('To create a profile:');
|
||||
console.log(` ${color('ccs profile create', 'command')}`);
|
||||
console.log('To create an API profile:');
|
||||
console.log(` ${color('ccs api create', 'command')}`);
|
||||
console.log('');
|
||||
return;
|
||||
}
|
||||
|
||||
// Build table data with status indicators
|
||||
const rows: string[][] = profiles.map((name) => {
|
||||
const rows: string[][] = apis.map((name) => {
|
||||
const settingsPath = config.profiles[name];
|
||||
const status = isProfileConfigured(name) ? color('[OK]', 'success') : color('[!]', 'warning');
|
||||
const status = isApiConfigured(name) ? color('[OK]', 'success') : color('[!]', 'warning');
|
||||
|
||||
return [name, settingsPath, status];
|
||||
});
|
||||
@@ -300,7 +300,7 @@ async function handleList(): Promise<void> {
|
||||
// Print table
|
||||
console.log(
|
||||
table(rows, {
|
||||
head: ['Profile', 'Settings File', 'Status'],
|
||||
head: ['API', 'Settings File', 'Status'],
|
||||
colWidths: [15, 35, 10],
|
||||
})
|
||||
);
|
||||
@@ -323,22 +323,22 @@ async function handleList(): Promise<void> {
|
||||
console.log('');
|
||||
}
|
||||
|
||||
console.log(dim(`Total: ${profiles.length} profile(s)`));
|
||||
console.log(dim(`Total: ${apis.length} API profile(s)`));
|
||||
console.log('');
|
||||
} catch (error) {
|
||||
console.log(fail(`Failed to list profiles: ${(error as Error).message}`));
|
||||
console.log(fail(`Failed to list API profiles: ${(error as Error).message}`));
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle 'ccs profile remove' command
|
||||
* Handle 'ccs api remove' command
|
||||
*/
|
||||
async function handleRemove(args: string[]): Promise<void> {
|
||||
await initUI();
|
||||
const parsedArgs = parseArgs(args);
|
||||
|
||||
// Load config first to get available profiles
|
||||
// Load config first to get available APIs
|
||||
let config: { profiles: Record<string, string>; cliproxy?: Record<string, unknown> };
|
||||
try {
|
||||
config = loadConfig();
|
||||
@@ -347,35 +347,35 @@ async function handleRemove(args: string[]): Promise<void> {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const profiles = Object.keys(config.profiles);
|
||||
if (profiles.length === 0) {
|
||||
console.log(warn('No profiles to remove'));
|
||||
const apis = Object.keys(config.profiles);
|
||||
if (apis.length === 0) {
|
||||
console.log(warn('No API profiles to remove'));
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Interactive profile selection if not provided
|
||||
// Interactive API selection if not provided
|
||||
let name = parsedArgs.name;
|
||||
if (!name) {
|
||||
console.log(header('Remove Profile'));
|
||||
console.log(header('Remove API Profile'));
|
||||
console.log('');
|
||||
console.log('Available profiles:');
|
||||
profiles.forEach((p, i) => console.log(` ${i + 1}. ${p}`));
|
||||
console.log('Available APIs:');
|
||||
apis.forEach((p, i) => console.log(` ${i + 1}. ${p}`));
|
||||
console.log('');
|
||||
|
||||
name = await InteractivePrompt.input('Profile name to remove', {
|
||||
name = await InteractivePrompt.input('API name to remove', {
|
||||
validate: (val) => {
|
||||
if (!val) return 'Profile name is required';
|
||||
if (!profiles.includes(val)) return `Profile '${val}' not found`;
|
||||
if (!val) return 'API name is required';
|
||||
if (!apis.includes(val)) return `API '${val}' not found`;
|
||||
return null;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (!(name in config.profiles)) {
|
||||
console.log(fail(`Profile '${name}' not found`));
|
||||
console.log(fail(`API '${name}' not found`));
|
||||
console.log('');
|
||||
console.log('Available profiles:');
|
||||
profiles.forEach((p) => console.log(` - ${p}`));
|
||||
console.log('Available APIs:');
|
||||
apis.forEach((p) => console.log(` - ${p}`));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -384,12 +384,13 @@ async function handleRemove(args: string[]): Promise<void> {
|
||||
|
||||
// Confirm deletion
|
||||
console.log('');
|
||||
console.log(`Profile '${color(name, 'command')}' will be removed.`);
|
||||
console.log(`API '${color(name, 'command')}' will be removed.`);
|
||||
console.log(` Settings: ${settingsPath}`);
|
||||
console.log('');
|
||||
|
||||
const confirmed =
|
||||
parsedArgs.yes || (await InteractivePrompt.confirm('Delete this profile?', { default: false }));
|
||||
parsedArgs.yes ||
|
||||
(await InteractivePrompt.confirm('Delete this API profile?', { default: false }));
|
||||
|
||||
if (!confirmed) {
|
||||
console.log(info('Cancelled'));
|
||||
@@ -408,25 +409,25 @@ async function handleRemove(args: string[]): Promise<void> {
|
||||
fs.unlinkSync(expandedPath);
|
||||
}
|
||||
|
||||
console.log(ok(`Profile removed: ${name}`));
|
||||
console.log(ok(`API profile removed: ${name}`));
|
||||
console.log('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show help for profile commands
|
||||
* Show help for api commands
|
||||
*/
|
||||
async function showHelp(): Promise<void> {
|
||||
await initUI();
|
||||
|
||||
console.log(header('CCS Profile Management'));
|
||||
console.log(header('CCS API Management'));
|
||||
console.log('');
|
||||
console.log(subheader('Usage'));
|
||||
console.log(` ${color('ccs profile', 'command')} <command> [options]`);
|
||||
console.log(` ${color('ccs api', 'command')} <command> [options]`);
|
||||
console.log('');
|
||||
console.log(subheader('Commands'));
|
||||
console.log(` ${color('create [name]', 'command')} Create new API profile (interactive)`);
|
||||
console.log(` ${color('list', 'command')} List all profiles`);
|
||||
console.log(` ${color('remove <name>', 'command')} Remove a profile`);
|
||||
console.log(` ${color('list', 'command')} List all API profiles`);
|
||||
console.log(` ${color('remove <name>', 'command')} Remove an API profile`);
|
||||
console.log('');
|
||||
console.log(subheader('Options'));
|
||||
console.log(` ${color('--base-url <url>', 'command')} API base URL (create)`);
|
||||
@@ -437,23 +438,23 @@ async function showHelp(): Promise<void> {
|
||||
console.log('');
|
||||
console.log(subheader('Examples'));
|
||||
console.log(` ${dim('# Interactive wizard')}`);
|
||||
console.log(` ${color('ccs profile create', 'command')}`);
|
||||
console.log(` ${color('ccs api create', 'command')}`);
|
||||
console.log('');
|
||||
console.log(` ${dim('# Create with name')}`);
|
||||
console.log(` ${color('ccs profile create myapi', 'command')}`);
|
||||
console.log(` ${color('ccs api create myapi', 'command')}`);
|
||||
console.log('');
|
||||
console.log(` ${dim('# Remove profile')}`);
|
||||
console.log(` ${color('ccs profile remove myapi', 'command')}`);
|
||||
console.log(` ${dim('# Remove API profile')}`);
|
||||
console.log(` ${color('ccs api remove myapi', 'command')}`);
|
||||
console.log('');
|
||||
console.log(` ${dim('# Show all profiles')}`);
|
||||
console.log(` ${color('ccs profile list', 'command')}`);
|
||||
console.log(` ${dim('# Show all API profiles')}`);
|
||||
console.log(` ${color('ccs api list', 'command')}`);
|
||||
console.log('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Main profile command router
|
||||
* Main api command router
|
||||
*/
|
||||
export async function handleProfileCommand(args: string[]): Promise<void> {
|
||||
export async function handleApiCommand(args: string[]): Promise<void> {
|
||||
const command = args[0];
|
||||
|
||||
if (!command || command === '--help' || command === '-h' || command === 'help') {
|
||||
@@ -478,7 +479,7 @@ export async function handleProfileCommand(args: string[]): Promise<void> {
|
||||
console.log(fail(`Unknown command: ${command}`));
|
||||
console.log('');
|
||||
console.log('Run for help:');
|
||||
console.log(` ${color('ccs profile --help', 'command')}`);
|
||||
console.log(` ${color('ccs api --help', 'command')}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
+180
-87
@@ -1,15 +1,28 @@
|
||||
import { initUI, box, header, color, dim } from '../utils/ui';
|
||||
import { initUI, box, color, dim, sectionHeader, subheader } from '../utils/ui';
|
||||
|
||||
// Version is read from VERSION file during build
|
||||
const VERSION = '5.3.0';
|
||||
|
||||
/**
|
||||
* Print a section with header and items
|
||||
* Print a major section with ═══ borders (only for 3 main sections)
|
||||
* Format:
|
||||
* ═══ TITLE ═══
|
||||
* Subtitle line 1
|
||||
* Subtitle line 2
|
||||
*
|
||||
* command Description
|
||||
*/
|
||||
function printSection(title: string, subtitle: string, items: [string, string][]): void {
|
||||
// Header with optional subtitle
|
||||
const headerText = subtitle ? `${title} ${dim(subtitle)}` : title;
|
||||
console.log(header(headerText));
|
||||
function printMajorSection(title: string, subtitles: string[], items: [string, string][]): void {
|
||||
// Section header with ═══ borders
|
||||
console.log(sectionHeader(title));
|
||||
|
||||
// Subtitles on separate lines (dim)
|
||||
for (const subtitle of subtitles) {
|
||||
console.log(` ${dim(subtitle)}`);
|
||||
}
|
||||
|
||||
// Empty line before items
|
||||
console.log('');
|
||||
|
||||
// Calculate max command length for alignment
|
||||
const maxCmdLen = Math.max(...items.map(([cmd]) => cmd.length));
|
||||
@@ -19,6 +32,49 @@ function printSection(title: string, subtitle: string, items: [string, string][]
|
||||
console.log(` ${color(paddedCmd, 'command')} ${desc}`);
|
||||
}
|
||||
|
||||
// Extra spacing after section
|
||||
console.log('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Print a sub-section with colored title
|
||||
* Format:
|
||||
* Title (context):
|
||||
* command Description
|
||||
*/
|
||||
function printSubSection(title: string, items: [string, string][]): void {
|
||||
// Sub-section header (colored, no borders)
|
||||
console.log(subheader(`${title}:`));
|
||||
|
||||
// Calculate max command length for alignment
|
||||
const maxCmdLen = Math.max(...items.map(([cmd]) => cmd.length));
|
||||
|
||||
for (const [cmd, desc] of items) {
|
||||
const paddedCmd = cmd.padEnd(maxCmdLen + 2);
|
||||
console.log(` ${color(paddedCmd, 'command')} ${desc}`);
|
||||
}
|
||||
|
||||
// Spacing after section
|
||||
console.log('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Print a config/paths section
|
||||
* Format:
|
||||
* Title:
|
||||
* Label: path
|
||||
*/
|
||||
function printConfigSection(title: string, items: [string, string][]): void {
|
||||
console.log(subheader(`${title}:`));
|
||||
|
||||
// Calculate max label length for alignment
|
||||
const maxLabelLen = Math.max(...items.map(([label]) => label.length));
|
||||
|
||||
for (const [label, path] of items) {
|
||||
const paddedLabel = label.padEnd(maxLabelLen);
|
||||
console.log(` ${paddedLabel} ${color(path, 'path')}`);
|
||||
}
|
||||
|
||||
console.log('');
|
||||
}
|
||||
|
||||
@@ -29,9 +85,17 @@ export async function handleHelpCommand(): Promise<void> {
|
||||
// Initialize UI (if not already)
|
||||
await initUI();
|
||||
|
||||
// Hero box with title
|
||||
// Hero box with ASCII art logo
|
||||
// Each letter: C=╔═╗/║ /╚═╝, C=╔═╗/║ /╚═╝, S=╔═╗/╚═╗/╚═╝
|
||||
const logo = `
|
||||
╔═╗ ╔═╗ ╔═╗
|
||||
║ ║ ╚═╗ v${VERSION}
|
||||
╚═╝ ╚═╝ ╚═╝
|
||||
|
||||
Claude Code Profile & Model Switcher`.trim();
|
||||
|
||||
console.log(
|
||||
box(`CCS v${VERSION}\nClaude Code Profile & Model Switcher`, {
|
||||
box(logo, {
|
||||
padding: 1,
|
||||
borderStyle: 'round',
|
||||
titleAlignment: 'center',
|
||||
@@ -40,106 +104,135 @@ export async function handleHelpCommand(): Promise<void> {
|
||||
console.log('');
|
||||
|
||||
// Usage section
|
||||
console.log(header('USAGE'));
|
||||
console.log(` $ ${color('ccs', 'command')} <profile> [flags] [-- claude-args...]`);
|
||||
console.log(` $ ${color('ccs', 'command')} [flags]`);
|
||||
console.log(subheader('Usage:'));
|
||||
console.log(` ${color('ccs', 'command')} [profile] [claude-args...]`);
|
||||
console.log(` ${color('ccs', 'command')} [flags]`);
|
||||
console.log('');
|
||||
|
||||
// API Key Profiles section
|
||||
printSection('API KEY PROFILES', 'Configure: ~/.ccs/*.settings.json', [
|
||||
['ccs', 'Use default Claude account'],
|
||||
['ccs glm', 'GLM-4.6 via Zhipu AI'],
|
||||
['ccs glmt', 'GLM-4.6 (Turbo mode)'],
|
||||
['ccs kimi', 'Kimi via Moonshot AI'],
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// MAJOR SECTION 1: API Key Profiles
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
printMajorSection(
|
||||
'API Key Profiles',
|
||||
['Configure in ~/.ccs/*.settings.json'],
|
||||
[
|
||||
['ccs', 'Use default Claude account'],
|
||||
['ccs glm', 'GLM 4.6 (API key required)'],
|
||||
['ccs glmt', 'GLM with thinking mode'],
|
||||
['ccs kimi', 'Kimi for Coding (API key)'],
|
||||
['', ''], // Spacer
|
||||
['ccs api create', 'Create custom API profile'],
|
||||
['ccs api remove', 'Remove an API profile'],
|
||||
['ccs api list', 'List all API profiles'],
|
||||
]
|
||||
);
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// MAJOR SECTION 2: Account Management
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
printMajorSection(
|
||||
'Account Management',
|
||||
['Run multiple Claude accounts concurrently'],
|
||||
[
|
||||
['ccs auth --help', 'Show account management commands'],
|
||||
['ccs auth create <name>', 'Create new account profile'],
|
||||
['ccs auth list', 'List all account profiles'],
|
||||
]
|
||||
);
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// MAJOR SECTION 3: CLI Proxy (OAuth Providers)
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
printMajorSection(
|
||||
'CLI Proxy (OAuth Providers)',
|
||||
[
|
||||
'Zero-config OAuth authentication via CLIProxyAPI',
|
||||
'First run: Browser opens for authentication',
|
||||
'Settings: ~/.ccs/{provider}.settings.json (created after auth)',
|
||||
],
|
||||
[
|
||||
['ccs gemini', 'Google Gemini (gemini-2.5-pro)'],
|
||||
['ccs codex', 'OpenAI Codex (gpt-5.1-codex-max)'],
|
||||
['ccs agy', 'Antigravity (gemini-3-pro-preview)'],
|
||||
['ccs qwen', 'Qwen Code (qwen3-coder)'],
|
||||
['', ''], // Spacer
|
||||
['ccs <provider> --auth', 'Authenticate only'],
|
||||
['ccs <provider> --logout', 'Clear authentication'],
|
||||
['ccs <provider> --headless', 'Headless auth (for SSH)'],
|
||||
['ccs codex "explain code"', 'Use with prompt'],
|
||||
]
|
||||
);
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// SUB-SECTIONS (simpler styling)
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
// Delegation
|
||||
printSubSection('Delegation (inside Claude Code CLI)', [
|
||||
['/ccs "task"', 'Delegate task (auto-selects profile)'],
|
||||
['/ccs --glm "task"', 'Force GLM-4.6 for simple tasks'],
|
||||
['/ccs --kimi "task"', 'Force Kimi for long context'],
|
||||
['/ccs:continue "follow-up"', 'Continue last delegation session'],
|
||||
]);
|
||||
|
||||
// Profile management section
|
||||
printSection('PROFILE MANAGEMENT', '', [
|
||||
['ccs profile create', 'Create custom API profile'],
|
||||
['ccs profile list', 'List all profiles'],
|
||||
['ccs profile remove', 'Remove a profile'],
|
||||
// Diagnostics
|
||||
printSubSection('Diagnostics', [
|
||||
['ccs doctor', 'Run health check and diagnostics'],
|
||||
['ccs sync', 'Sync delegation commands and skills'],
|
||||
['ccs update', 'Update CCS to latest version'],
|
||||
]);
|
||||
|
||||
// Account management section
|
||||
printSection('ACCOUNT MANAGEMENT', 'Multiple Claude accounts', [
|
||||
['ccs auth create <name>', 'Create new account'],
|
||||
['ccs auth list', 'List all accounts'],
|
||||
['ccs auth default <name>', 'Set default account'],
|
||||
// Flags
|
||||
printSubSection('Flags', [
|
||||
['-h, --help', 'Show this help message'],
|
||||
['-v, --version', 'Show version and installation info'],
|
||||
['-sc, --shell-completion', 'Install shell auto-completion'],
|
||||
]);
|
||||
|
||||
// OAuth section
|
||||
printSection('OAUTH PROVIDERS', 'Zero config, browser auth', [
|
||||
['ccs gemini', 'Google Gemini (gemini-2.5-pro)'],
|
||||
['ccs codex', 'OpenAI Codex (gpt-5.1-codex-max)'],
|
||||
['ccs agy', 'Antigravity (gemini-3-pro-preview)'],
|
||||
['ccs qwen', 'Qwen Code (qwen3-coder)'],
|
||||
// Configuration
|
||||
printConfigSection('Configuration', [
|
||||
['Config File:', '~/.ccs/config.json'],
|
||||
['Profiles:', '~/.ccs/profiles.json'],
|
||||
['Instances:', '~/.ccs/instances/'],
|
||||
['Settings:', '~/.ccs/*.settings.json'],
|
||||
]);
|
||||
|
||||
// OAuth flags
|
||||
console.log(header('OAUTH FLAGS'));
|
||||
console.log(` ${color('ccs <provider> --auth', 'command')} Authenticate only`);
|
||||
console.log(` ${color('ccs <provider> --logout', 'command')} Clear authentication`);
|
||||
console.log(` ${color('ccs <provider> --headless', 'command')} Headless auth (for SSH)`);
|
||||
console.log('');
|
||||
|
||||
// Delegation section
|
||||
printSection('DELEGATION', 'Inside Claude Code CLI', [
|
||||
['/ccs "task"', 'Delegate (auto-select profile)'],
|
||||
['/ccs --glm "task"', 'Force GLM-4.6'],
|
||||
['/ccs --kimi "task"', 'Force Kimi'],
|
||||
['/ccs:continue', 'Continue last delegation'],
|
||||
]);
|
||||
|
||||
// Diagnostics section
|
||||
printSection('DIAGNOSTICS', '', [
|
||||
['ccs doctor', 'Run health check'],
|
||||
['ccs sync', 'Sync delegation commands'],
|
||||
['ccs update', 'Update to latest version'],
|
||||
]);
|
||||
|
||||
// Flags section
|
||||
console.log(header('FLAGS'));
|
||||
console.log(` ${color('-h, --help', 'command')} Show this help`);
|
||||
console.log(` ${color('-v, --version', 'command')} Show version`);
|
||||
console.log(` ${color('-sc, --shell-completion', 'command')} Install shell completion`);
|
||||
console.log('');
|
||||
|
||||
// Configuration paths
|
||||
console.log(header('CONFIGURATION'));
|
||||
console.log(` Config: ${color('~/.ccs/config.json', 'path')}`);
|
||||
console.log(` Profiles: ${color('~/.ccs/profiles.json', 'path')}`);
|
||||
console.log(` Settings: ${color('~/.ccs/*.settings.json', 'path')}`);
|
||||
console.log(` CLIProxy: ${color('~/.ccs/cliproxy/', 'path')}`);
|
||||
// CLI Proxy paths
|
||||
console.log(subheader('CLI Proxy:'));
|
||||
console.log(` Binary: ${color('~/.ccs/cliproxy/bin/cli-proxy-api', 'path')}`);
|
||||
console.log(` Config: ${color('~/.ccs/cliproxy/config.yaml', 'path')}`);
|
||||
console.log(` Auth: ${color('~/.ccs/cliproxy/auth/', 'path')}`);
|
||||
console.log(` ${dim('Port: 8317 (default)')}`);
|
||||
console.log('');
|
||||
|
||||
// Shared Data
|
||||
console.log(header('SHARED DATA'));
|
||||
console.log(` Commands: ${color('~/.ccs/shared/commands/', 'path')}`);
|
||||
console.log(` Skills: ${color('~/.ccs/shared/skills/', 'path')}`);
|
||||
console.log(` Agents: ${color('~/.ccs/shared/agents/', 'path')}`);
|
||||
console.log(subheader('Shared Data:'));
|
||||
console.log(` Commands: ${color('~/.ccs/shared/commands/', 'path')}`);
|
||||
console.log(` Skills: ${color('~/.ccs/shared/skills/', 'path')}`);
|
||||
console.log(` Agents: ${color('~/.ccs/shared/agents/', 'path')}`);
|
||||
console.log(` ${dim('Note: Symlinked across all profiles')}`);
|
||||
console.log('');
|
||||
|
||||
// Examples
|
||||
console.log(header('EXAMPLES'));
|
||||
console.log(` ${dim('# Use default account')}`);
|
||||
console.log(` $ ${color('ccs', 'command')}`);
|
||||
console.log('');
|
||||
console.log(` ${dim('# OAuth provider (browser auth first time)')}`);
|
||||
console.log(` $ ${color('ccs gemini', 'command')}`);
|
||||
console.log('');
|
||||
console.log(` ${dim('# API key model with prompt')}`);
|
||||
console.log(` $ ${color('ccs glm "implement the API"', 'command')}`);
|
||||
// Examples (aligned with consistent spacing)
|
||||
console.log(subheader('Examples:'));
|
||||
console.log(` $ ${color('ccs', 'command')} ${dim('# Use default account')}`);
|
||||
console.log(
|
||||
` $ ${color('ccs gemini', 'command')} ${dim('# OAuth (browser opens first time)')}`
|
||||
);
|
||||
console.log(` $ ${color('ccs glm "implement API"', 'command')} ${dim('# API key model')}`);
|
||||
console.log('');
|
||||
|
||||
// Footer
|
||||
console.log(dim('Docs: https://github.com/kaitranntt/ccs'));
|
||||
console.log(dim('License: MIT'));
|
||||
// Docs link
|
||||
console.log(` ${dim('Docs: https://github.com/kaitranntt/ccs')}`);
|
||||
console.log('');
|
||||
|
||||
// Uninstall
|
||||
console.log(color('Uninstall:', 'warning'));
|
||||
console.log(' npm uninstall -g @kaitranntt/ccs');
|
||||
console.log(subheader('Uninstall:'));
|
||||
console.log(` ${color('npm uninstall -g @kaitranntt/ccs', 'command')}`);
|
||||
console.log('');
|
||||
|
||||
// License
|
||||
console.log(dim('License: MIT'));
|
||||
console.log('');
|
||||
|
||||
process.exit(0);
|
||||
|
||||
+20
-1
@@ -139,7 +139,7 @@ export function color(text: string, semantic: SemanticColor): string {
|
||||
case 'secondary':
|
||||
return chalkModule.hex(COLORS.secondary)(text);
|
||||
case 'command':
|
||||
return chalkModule.yellow.italic(text);
|
||||
return chalkModule.yellow.bold(text);
|
||||
case 'path':
|
||||
return chalkModule.cyan.underline(text);
|
||||
default:
|
||||
@@ -458,6 +458,24 @@ export function hr(char = '─', width = 60): string {
|
||||
return dim(char.repeat(width));
|
||||
}
|
||||
|
||||
/**
|
||||
* Print section header with ═══ borders
|
||||
* Format: ═══ Title ═══
|
||||
*/
|
||||
export function sectionHeader(title: string): string {
|
||||
const border = '═══';
|
||||
const headerText = `${border} ${title} ${border}`;
|
||||
// Use gradient + bold for visual appeal
|
||||
if (gradientModule && chalkModule && useColors()) {
|
||||
return chalkModule.bold(gradientModule([COLORS.primary, COLORS.secondary])(headerText));
|
||||
}
|
||||
// Fallback to bold primary color
|
||||
if (useColors() && chalkModule) {
|
||||
return chalkModule.hex(COLORS.primary).bold(headerText);
|
||||
}
|
||||
return headerText;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// TASK LISTS (Listr2 Integration)
|
||||
// =============================================================================
|
||||
@@ -604,6 +622,7 @@ export const ui = {
|
||||
// Headers
|
||||
header,
|
||||
subheader,
|
||||
sectionHeader,
|
||||
hr,
|
||||
} as const;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user