mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-02 20:24:21 +00:00
fix(tests): stabilize command help CI validation
This commit is contained in:
@@ -491,7 +491,9 @@ export async function handleApiCreateCommand(args: string[]): Promise<void> {
|
|||||||
|
|
||||||
if (hasClaudeMappings) {
|
if (hasClaudeMappings) {
|
||||||
details += `\nLongCtx: ${
|
details += `\nLongCtx: ${
|
||||||
shouldEnableClaudeLongContext ? 'compatible Claude mappings use [1m]' : 'standard Claude context'
|
shouldEnableClaudeLongContext
|
||||||
|
? 'compatible Claude mappings use [1m]'
|
||||||
|
: 'standard Claude context'
|
||||||
}`;
|
}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import {
|
|||||||
import { color, dim, fail, header, initUI, subheader } from '../../utils/ui';
|
import { color, dim, fail, header, initUI, subheader } from '../../utils/ui';
|
||||||
import { sanitizeHelpText } from './shared';
|
import { sanitizeHelpText } from './shared';
|
||||||
|
|
||||||
|
type HelpWriter = (line: string) => void;
|
||||||
|
|
||||||
function renderPresetHelpLine(preset: ProviderPreset, idWidth: number): string {
|
function renderPresetHelpLine(preset: ProviderPreset, idWidth: number): string {
|
||||||
const presetId = sanitizeHelpText(preset.id) || 'unknown';
|
const presetId = sanitizeHelpText(preset.id) || 'unknown';
|
||||||
const paddedId = presetId.padEnd(idWidth);
|
const paddedId = presetId.padEnd(idWidth);
|
||||||
@@ -16,7 +18,7 @@ function renderPresetHelpLine(preset: ProviderPreset, idWidth: number): string {
|
|||||||
return ` ${color(paddedId, 'command')} ${presetName} - ${presetDescription}`;
|
return ` ${color(paddedId, 'command')} ${presetName} - ${presetDescription}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function showApiCommandHelp(): Promise<void> {
|
export async function showApiCommandHelp(writeLine: HelpWriter = console.log): Promise<void> {
|
||||||
await initUI();
|
await initUI();
|
||||||
const presetIds = getPresetIds()
|
const presetIds = getPresetIds()
|
||||||
.map((id) => sanitizeHelpText(id))
|
.map((id) => sanitizeHelpText(id))
|
||||||
@@ -25,112 +27,108 @@ export async function showApiCommandHelp(): Promise<void> {
|
|||||||
const presetAliases = getPresetAliases();
|
const presetAliases = getPresetAliases();
|
||||||
const presetIdWidth = Math.max(0, ...presetIds.map((id) => id.length)) + 2;
|
const presetIdWidth = Math.max(0, ...presetIds.map((id) => id.length)) + 2;
|
||||||
|
|
||||||
console.log(header('CCS API Management'));
|
writeLine(header('CCS API Management'));
|
||||||
console.log('');
|
writeLine('');
|
||||||
console.log(subheader('Usage'));
|
writeLine(subheader('Usage'));
|
||||||
console.log(` ${color('ccs api', 'command')} <command> [options]`);
|
writeLine(` ${color('ccs api', 'command')} <command> [options]`);
|
||||||
console.log('');
|
writeLine('');
|
||||||
console.log(subheader('Commands'));
|
writeLine(subheader('Commands'));
|
||||||
console.log(` ${color('create [name]', 'command')} Create new API profile (interactive)`);
|
writeLine(` ${color('create [name]', 'command')} Create new API profile (interactive)`);
|
||||||
console.log(` ${color('list', 'command')} List all API profiles`);
|
writeLine(` ${color('list', 'command')} List all API profiles`);
|
||||||
console.log(
|
writeLine(
|
||||||
` ${color('discover', 'command')} Discover orphan *.settings.json and register`
|
` ${color('discover', 'command')} Discover orphan *.settings.json and register`
|
||||||
);
|
);
|
||||||
console.log(` ${color('copy <src> <dest>', 'command')} Duplicate API profile settings + config`);
|
writeLine(` ${color('copy <src> <dest>', 'command')} Duplicate API profile settings + config`);
|
||||||
console.log(
|
writeLine(
|
||||||
` ${color('export <name>', 'command')} Export profile bundle for cross-device transfer`
|
` ${color('export <name>', 'command')} Export profile bundle for cross-device transfer`
|
||||||
);
|
);
|
||||||
console.log(
|
writeLine(` ${color('import <file>', 'command')} Import profile bundle and register profile`);
|
||||||
` ${color('import <file>', 'command')} Import profile bundle and register profile`
|
writeLine(` ${color('remove <name>', 'command')} Remove an API profile`);
|
||||||
);
|
writeLine('');
|
||||||
console.log(` ${color('remove <name>', 'command')} Remove an API profile`);
|
writeLine(subheader('Options'));
|
||||||
console.log('');
|
writeLine(
|
||||||
console.log(subheader('Options'));
|
|
||||||
console.log(
|
|
||||||
` ${color('--preset <id>', 'command')} Use provider preset (${presetIds.join(', ')})`
|
` ${color('--preset <id>', 'command')} Use provider preset (${presetIds.join(', ')})`
|
||||||
);
|
);
|
||||||
console.log(
|
writeLine(
|
||||||
` ${color('--cliproxy-provider <id>', 'command')} Use routed CLIProxy provider (${cliproxyProviderIds.join(', ')})`
|
` ${color('--cliproxy-provider <id>', 'command')} Use routed CLIProxy provider (${cliproxyProviderIds.join(', ')})`
|
||||||
);
|
);
|
||||||
console.log(` ${color('--base-url <url>', 'command')} API base URL (create)`);
|
writeLine(` ${color('--base-url <url>', 'command')} API base URL (create)`);
|
||||||
console.log(` ${color('--api-key <key>', 'command')} API key (create)`);
|
writeLine(` ${color('--api-key <key>', 'command')} API key (create)`);
|
||||||
console.log(` ${color('--model <model>', 'command')} Default model (create)`);
|
writeLine(` ${color('--model <model>', 'command')} Default model (create)`);
|
||||||
console.log(
|
writeLine(
|
||||||
` ${color('--1m / --no-1m', 'command')} Write or clear [1m] on compatible Claude mappings`
|
` ${color('--1m / --no-1m', 'command')} Write or clear [1m] on compatible Claude mappings`
|
||||||
);
|
);
|
||||||
console.log(
|
writeLine(
|
||||||
` ${color('--target <cli>', 'command')} Default target: claude or droid (create)`
|
` ${color('--target <cli>', 'command')} Default target: claude or droid (create)`
|
||||||
);
|
);
|
||||||
console.log(` ${color('--register', 'command')} Register discovered orphan settings`);
|
writeLine(` ${color('--register', 'command')} Register discovered orphan settings`);
|
||||||
console.log(` ${color('--json', 'command')} JSON output for discover command`);
|
writeLine(` ${color('--json', 'command')} JSON output for discover command`);
|
||||||
console.log(` ${color('--out <file>', 'command')} Export bundle output path`);
|
writeLine(` ${color('--out <file>', 'command')} Export bundle output path`);
|
||||||
console.log(` ${color('--include-secrets', 'command')} Include token in export bundle`);
|
writeLine(` ${color('--include-secrets', 'command')} Include token in export bundle`);
|
||||||
console.log(` ${color('--name <name>', 'command')} Override profile name during import`);
|
writeLine(` ${color('--name <name>', 'command')} Override profile name during import`);
|
||||||
console.log(
|
writeLine(
|
||||||
` ${color('--force', 'command')} Overwrite existing or bypass validation (create/discover/copy/import)`
|
` ${color('--force', 'command')} Overwrite existing or bypass validation (create/discover/copy/import)`
|
||||||
);
|
);
|
||||||
console.log(` ${color('--yes, -y', 'command')} Skip confirmation prompts`);
|
writeLine(` ${color('--yes, -y', 'command')} Skip confirmation prompts`);
|
||||||
console.log('');
|
writeLine('');
|
||||||
console.log(subheader('Provider Presets'));
|
writeLine(subheader('Provider Presets'));
|
||||||
PROVIDER_PRESETS.forEach((preset) => console.log(renderPresetHelpLine(preset, presetIdWidth)));
|
PROVIDER_PRESETS.forEach((preset) => writeLine(renderPresetHelpLine(preset, presetIdWidth)));
|
||||||
Object.entries(presetAliases).forEach(([alias, canonical]) => {
|
Object.entries(presetAliases).forEach(([alias, canonical]) => {
|
||||||
const safeAlias = sanitizeHelpText(alias);
|
const safeAlias = sanitizeHelpText(alias);
|
||||||
const safeCanonical = sanitizeHelpText(canonical);
|
const safeCanonical = sanitizeHelpText(canonical);
|
||||||
console.log(
|
writeLine(` ${dim(`Legacy alias: --preset ${safeAlias} (auto-mapped to ${safeCanonical})`)}`);
|
||||||
` ${dim(`Legacy alias: --preset ${safeAlias} (auto-mapped to ${safeCanonical})`)}`
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
console.log('');
|
writeLine('');
|
||||||
console.log(subheader('Examples'));
|
writeLine(subheader('Examples'));
|
||||||
console.log(` ${dim('# Interactive wizard')}`);
|
writeLine(` ${dim('# Interactive wizard')}`);
|
||||||
console.log(` ${color('ccs api create', 'command')}`);
|
writeLine(` ${color('ccs api create', 'command')}`);
|
||||||
console.log('');
|
writeLine('');
|
||||||
console.log(` ${dim('# Quick setup with preset')}`);
|
writeLine(` ${dim('# Quick setup with preset')}`);
|
||||||
console.log(` ${color('ccs api create --preset anthropic', 'command')}`);
|
writeLine(` ${color('ccs api create --preset anthropic', 'command')}`);
|
||||||
console.log(
|
writeLine(
|
||||||
` ${color('ccs api create --preset anthropic --1m', 'command')} ${dim('# explicit Claude [1m] opt-in')}`
|
` ${color('ccs api create --preset anthropic --1m', 'command')} ${dim('# explicit Claude [1m] opt-in')}`
|
||||||
);
|
);
|
||||||
console.log(` ${color('ccs api create --preset openrouter', 'command')}`);
|
writeLine(` ${color('ccs api create --preset openrouter', 'command')}`);
|
||||||
console.log(` ${color('ccs api create --preset alibaba-coding-plan', 'command')}`);
|
writeLine(` ${color('ccs api create --preset alibaba-coding-plan', 'command')}`);
|
||||||
console.log(` ${color('ccs api create --preset alibaba', 'command')} ${dim('# alias')}`);
|
writeLine(` ${color('ccs api create --preset alibaba', 'command')} ${dim('# alias')}`);
|
||||||
console.log(` ${color('ccs api create --preset glm', 'command')}`);
|
writeLine(` ${color('ccs api create --preset glm', 'command')}`);
|
||||||
console.log('');
|
writeLine('');
|
||||||
console.log(subheader('Claude Long Context'));
|
writeLine(subheader('Claude Long Context'));
|
||||||
console.log(` ${dim('Plain Claude model IDs stay on standard context by default.')}`);
|
writeLine(` ${dim('Plain Claude model IDs stay on standard context by default.')}`);
|
||||||
console.log(
|
writeLine(
|
||||||
` ${dim('Use --1m during create to append [1m] to compatible Claude mappings, or --no-1m to force plain IDs.')}`
|
` ${dim('Use --1m during create to append [1m] to compatible Claude mappings, or --no-1m to force plain IDs.')}`
|
||||||
);
|
);
|
||||||
console.log(
|
writeLine(
|
||||||
` ${dim('CCS controls only the saved [1m] suffix. Provider pricing/entitlement stay upstream, and some accounts can still return 429 for long-context requests.')}`
|
` ${dim('CCS controls only the saved [1m] suffix. Provider pricing/entitlement stay upstream, and some accounts can still return 429 for long-context requests.')}`
|
||||||
);
|
);
|
||||||
console.log('');
|
writeLine('');
|
||||||
console.log(` ${dim('# Create routed profile from existing CLIProxy provider config')}`);
|
writeLine(` ${dim('# Create routed profile from existing CLIProxy provider config')}`);
|
||||||
console.log(` ${color('ccs api create --cliproxy-provider gemini', 'command')}`);
|
writeLine(` ${color('ccs api create --cliproxy-provider gemini', 'command')}`);
|
||||||
console.log(
|
writeLine(
|
||||||
` ${color('ccs api create gemini-droid --cliproxy-provider gemini --target droid', 'command')}`
|
` ${color('ccs api create gemini-droid --cliproxy-provider gemini --target droid', 'command')}`
|
||||||
);
|
);
|
||||||
console.log('');
|
writeLine('');
|
||||||
console.log(` ${dim('# Create with name')}`);
|
writeLine(` ${dim('# Create with name')}`);
|
||||||
console.log(` ${color('ccs api create myapi', 'command')}`);
|
writeLine(` ${color('ccs api create myapi', 'command')}`);
|
||||||
console.log(` ${color('ccs api create mydroid --preset glm --target droid', 'command')}`);
|
writeLine(` ${color('ccs api create mydroid --preset glm --target droid', 'command')}`);
|
||||||
console.log('');
|
writeLine('');
|
||||||
console.log(` ${dim('# Remove API profile')}`);
|
writeLine(` ${dim('# Remove API profile')}`);
|
||||||
console.log(` ${color('ccs api remove myapi', 'command')}`);
|
writeLine(` ${color('ccs api remove myapi', 'command')}`);
|
||||||
console.log('');
|
writeLine('');
|
||||||
console.log(` ${dim('# Discover and register orphan settings files')}`);
|
writeLine(` ${dim('# Discover and register orphan settings files')}`);
|
||||||
console.log(` ${color('ccs api discover', 'command')}`);
|
writeLine(` ${color('ccs api discover', 'command')}`);
|
||||||
console.log(` ${color('ccs api discover --register', 'command')}`);
|
writeLine(` ${color('ccs api discover --register', 'command')}`);
|
||||||
console.log('');
|
writeLine('');
|
||||||
console.log(` ${dim('# Duplicate an existing API profile')}`);
|
writeLine(` ${dim('# Duplicate an existing API profile')}`);
|
||||||
console.log(` ${color('ccs api copy glm glm-backup', 'command')}`);
|
writeLine(` ${color('ccs api copy glm glm-backup', 'command')}`);
|
||||||
console.log('');
|
writeLine('');
|
||||||
console.log(` ${dim('# Export and import across devices')}`);
|
writeLine(` ${dim('# Export and import across devices')}`);
|
||||||
console.log(` ${color('ccs api export glm --out ./glm.ccs-profile.json', 'command')}`);
|
writeLine(` ${color('ccs api export glm --out ./glm.ccs-profile.json', 'command')}`);
|
||||||
console.log(` ${color('ccs api import ./glm.ccs-profile.json', 'command')}`);
|
writeLine(` ${color('ccs api import ./glm.ccs-profile.json', 'command')}`);
|
||||||
console.log('');
|
writeLine('');
|
||||||
console.log(` ${dim('# Show all API profiles')}`);
|
writeLine(` ${dim('# Show all API profiles')}`);
|
||||||
console.log(` ${color('ccs api list', 'command')}`);
|
writeLine(` ${color('ccs api list', 'command')}`);
|
||||||
console.log('');
|
writeLine('');
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function showUnknownApiCommand(command: string): Promise<void> {
|
export async function showUnknownApiCommand(command: string): Promise<void> {
|
||||||
|
|||||||
@@ -10,22 +10,68 @@ import { handleApiRemoveCommand } from './remove-command';
|
|||||||
|
|
||||||
export { parseApiCommandArgs } from './shared';
|
export { parseApiCommandArgs } from './shared';
|
||||||
|
|
||||||
const API_COMMAND_ROUTES: readonly NamedCommandRoute[] = [
|
type ApiCommandHandler = (args: string[]) => Promise<void>;
|
||||||
{ name: 'create', handle: handleApiCreateCommand },
|
type ApiCommandHelpHandler = () => Promise<void>;
|
||||||
{ name: 'list', handle: handleApiListCommand },
|
type ApiCommandUnknownHandler = (command: string) => Promise<void>;
|
||||||
{ name: 'discover', handle: handleApiDiscoverCommand },
|
|
||||||
{ name: 'copy', handle: handleApiCopyCommand },
|
export interface ApiCommandDependencies {
|
||||||
{ name: 'export', handle: handleApiExportCommand },
|
help: ApiCommandHelpHandler;
|
||||||
{ name: 'import', handle: handleApiImportCommand },
|
unknown: ApiCommandUnknownHandler;
|
||||||
{ name: 'remove', aliases: ['delete', 'rm'], handle: handleApiRemoveCommand },
|
create: ApiCommandHandler;
|
||||||
];
|
list: ApiCommandHandler;
|
||||||
|
discover: ApiCommandHandler;
|
||||||
|
copy: ApiCommandHandler;
|
||||||
|
export: ApiCommandHandler;
|
||||||
|
import: ApiCommandHandler;
|
||||||
|
remove: ApiCommandHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_API_COMMAND_DEPENDENCIES: ApiCommandDependencies = {
|
||||||
|
help: showApiCommandHelp,
|
||||||
|
unknown: showUnknownApiCommand,
|
||||||
|
create: handleApiCreateCommand,
|
||||||
|
list: handleApiListCommand,
|
||||||
|
discover: handleApiDiscoverCommand,
|
||||||
|
copy: handleApiCopyCommand,
|
||||||
|
export: handleApiExportCommand,
|
||||||
|
import: handleApiImportCommand,
|
||||||
|
remove: handleApiRemoveCommand,
|
||||||
|
};
|
||||||
|
|
||||||
|
function createApiCommandRoutes(
|
||||||
|
dependencies: ApiCommandDependencies
|
||||||
|
): readonly NamedCommandRoute[] {
|
||||||
|
return [
|
||||||
|
{ name: 'create', handle: dependencies.create },
|
||||||
|
{ name: 'list', handle: dependencies.list },
|
||||||
|
{ name: 'discover', handle: dependencies.discover },
|
||||||
|
{ name: 'copy', handle: dependencies.copy },
|
||||||
|
{ name: 'export', handle: dependencies.export },
|
||||||
|
{ name: 'import', handle: dependencies.import },
|
||||||
|
{ name: 'remove', aliases: ['delete', 'rm'], handle: dependencies.remove },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createApiCommandHandler(
|
||||||
|
overrides: Partial<ApiCommandDependencies> = {}
|
||||||
|
): (args: string[]) => Promise<void> {
|
||||||
|
const dependencies: ApiCommandDependencies = {
|
||||||
|
...DEFAULT_API_COMMAND_DEPENDENCIES,
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
const routes = createApiCommandRoutes(dependencies);
|
||||||
|
|
||||||
|
return async (args: string[]) => {
|
||||||
|
await dispatchNamedCommand({
|
||||||
|
args,
|
||||||
|
routes,
|
||||||
|
onHelp: dependencies.help,
|
||||||
|
onUnknown: dependencies.unknown,
|
||||||
|
allowEmptyHelp: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export async function handleApiCommand(args: string[]): Promise<void> {
|
export async function handleApiCommand(args: string[]): Promise<void> {
|
||||||
await dispatchNamedCommand({
|
await createApiCommandHandler()(args);
|
||||||
args,
|
|
||||||
routes: API_COMMAND_ROUTES,
|
|
||||||
onHelp: showApiCommandHelp,
|
|
||||||
onUnknown: showUnknownApiCommand,
|
|
||||||
allowEmptyHelp: true,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -294,9 +294,10 @@ export function applyClaudeExtendedContextPreference(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
nextModels[key] = enabled && likelySupportsClaudeExtendedContext(value)
|
nextModels[key] =
|
||||||
? applyExtendedContextSuffix(value)
|
enabled && likelySupportsClaudeExtendedContext(value)
|
||||||
: stripExtendedContextSuffix(value);
|
? applyExtendedContextSuffix(value)
|
||||||
|
: stripExtendedContextSuffix(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return nextModels;
|
return nextModels;
|
||||||
|
|||||||
+337
-247
@@ -5,6 +5,8 @@ import { isUnifiedMode } from '../config/unified-config-loader';
|
|||||||
import { getCcsDir, getCcsDirSource } from '../utils/config-manager';
|
import { getCcsDir, getCcsDirSource } from '../utils/config-manager';
|
||||||
import { CLIPROXY_DEFAULT_PORT } from '../cliproxy/config/port-manager';
|
import { CLIPROXY_DEFAULT_PORT } from '../cliproxy/config/port-manager';
|
||||||
|
|
||||||
|
type HelpWriter = (line: string) => void;
|
||||||
|
|
||||||
// Get version from package.json (same as version-command.ts)
|
// Get version from package.json (same as version-command.ts)
|
||||||
const VERSION = JSON.parse(
|
const VERSION = JSON.parse(
|
||||||
fs.readFileSync(path.join(__dirname, '../../package.json'), 'utf8')
|
fs.readFileSync(path.join(__dirname, '../../package.json'), 'utf8')
|
||||||
@@ -19,28 +21,33 @@ const VERSION = JSON.parse(
|
|||||||
*
|
*
|
||||||
* command Description
|
* command Description
|
||||||
*/
|
*/
|
||||||
function printMajorSection(title: string, subtitles: string[], items: [string, string][]): void {
|
function printMajorSection(
|
||||||
|
title: string,
|
||||||
|
subtitles: string[],
|
||||||
|
items: [string, string][],
|
||||||
|
writeLine: HelpWriter = console.log
|
||||||
|
): void {
|
||||||
// Section header with ═══ borders
|
// Section header with ═══ borders
|
||||||
console.log(sectionHeader(title));
|
writeLine(sectionHeader(title));
|
||||||
|
|
||||||
// Subtitles on separate lines (dim)
|
// Subtitles on separate lines (dim)
|
||||||
for (const subtitle of subtitles) {
|
for (const subtitle of subtitles) {
|
||||||
console.log(` ${dim(subtitle)}`);
|
writeLine(` ${dim(subtitle)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty line before items
|
// Empty line before items
|
||||||
console.log('');
|
writeLine('');
|
||||||
|
|
||||||
// Calculate max command length for alignment
|
// Calculate max command length for alignment
|
||||||
const maxCmdLen = Math.max(...items.map(([cmd]) => cmd.length));
|
const maxCmdLen = Math.max(...items.map(([cmd]) => cmd.length));
|
||||||
|
|
||||||
for (const [cmd, desc] of items) {
|
for (const [cmd, desc] of items) {
|
||||||
const paddedCmd = cmd.padEnd(maxCmdLen + 2);
|
const paddedCmd = cmd.padEnd(maxCmdLen + 2);
|
||||||
console.log(` ${color(paddedCmd, 'command')} ${desc}`);
|
writeLine(` ${color(paddedCmd, 'command')} ${desc}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extra spacing after section
|
// Extra spacing after section
|
||||||
console.log('');
|
writeLine('');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -49,20 +56,24 @@ function printMajorSection(title: string, subtitles: string[], items: [string, s
|
|||||||
* Title (context):
|
* Title (context):
|
||||||
* command Description
|
* command Description
|
||||||
*/
|
*/
|
||||||
function printSubSection(title: string, items: [string, string][]): void {
|
function printSubSection(
|
||||||
|
title: string,
|
||||||
|
items: [string, string][],
|
||||||
|
writeLine: HelpWriter = console.log
|
||||||
|
): void {
|
||||||
// Sub-section header (colored, no borders)
|
// Sub-section header (colored, no borders)
|
||||||
console.log(subheader(`${title}:`));
|
writeLine(subheader(`${title}:`));
|
||||||
|
|
||||||
// Calculate max command length for alignment
|
// Calculate max command length for alignment
|
||||||
const maxCmdLen = Math.max(...items.map(([cmd]) => cmd.length));
|
const maxCmdLen = Math.max(...items.map(([cmd]) => cmd.length));
|
||||||
|
|
||||||
for (const [cmd, desc] of items) {
|
for (const [cmd, desc] of items) {
|
||||||
const paddedCmd = cmd.padEnd(maxCmdLen + 2);
|
const paddedCmd = cmd.padEnd(maxCmdLen + 2);
|
||||||
console.log(` ${color(paddedCmd, 'command')} ${desc}`);
|
writeLine(` ${color(paddedCmd, 'command')} ${desc}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Spacing after section
|
// Spacing after section
|
||||||
console.log('');
|
writeLine('');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -71,24 +82,28 @@ function printSubSection(title: string, items: [string, string][]): void {
|
|||||||
* Title:
|
* Title:
|
||||||
* Label: path
|
* Label: path
|
||||||
*/
|
*/
|
||||||
function printConfigSection(title: string, items: [string, string][]): void {
|
function printConfigSection(
|
||||||
console.log(subheader(`${title}:`));
|
title: string,
|
||||||
|
items: [string, string][],
|
||||||
|
writeLine: HelpWriter = console.log
|
||||||
|
): void {
|
||||||
|
writeLine(subheader(`${title}:`));
|
||||||
|
|
||||||
// Calculate max label length for alignment
|
// Calculate max label length for alignment
|
||||||
const maxLabelLen = Math.max(...items.map(([label]) => label.length));
|
const maxLabelLen = Math.max(...items.map(([label]) => label.length));
|
||||||
|
|
||||||
for (const [label, path] of items) {
|
for (const [label, path] of items) {
|
||||||
const paddedLabel = label.padEnd(maxLabelLen);
|
const paddedLabel = label.padEnd(maxLabelLen);
|
||||||
console.log(` ${paddedLabel} ${color(path, 'path')}`);
|
writeLine(` ${paddedLabel} ${color(path, 'path')}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('');
|
writeLine('');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display comprehensive help information for CCS (Claude Code Switch)
|
* Display comprehensive help information for CCS (Claude Code Switch)
|
||||||
*/
|
*/
|
||||||
export async function handleHelpCommand(): Promise<void> {
|
export async function handleHelpCommand(writeLine: HelpWriter = console.log): Promise<void> {
|
||||||
// Initialize UI (if not already)
|
// Initialize UI (if not already)
|
||||||
await initUI();
|
await initUI();
|
||||||
|
|
||||||
@@ -103,24 +118,24 @@ Claude Code Profile & Model Switcher
|
|||||||
|
|
||||||
Run ${color('ccs config', 'command')} for web dashboard`.trim();
|
Run ${color('ccs config', 'command')} for web dashboard`.trim();
|
||||||
|
|
||||||
console.log(
|
writeLine(
|
||||||
box(logo, {
|
box(logo, {
|
||||||
padding: 1,
|
padding: 1,
|
||||||
borderStyle: 'round',
|
borderStyle: 'round',
|
||||||
titleAlignment: 'center',
|
titleAlignment: 'center',
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
console.log('');
|
writeLine('');
|
||||||
|
|
||||||
// Resolve display path for dynamic sections
|
// Resolve display path for dynamic sections
|
||||||
const [dirSource] = getCcsDirSource();
|
const [dirSource] = getCcsDirSource();
|
||||||
const dirDisplay = dirSource === 'default' ? '~/.ccs' : getCcsDir();
|
const dirDisplay = dirSource === 'default' ? '~/.ccs' : getCcsDir();
|
||||||
|
|
||||||
// Usage section
|
// Usage section
|
||||||
console.log(subheader('Usage:'));
|
writeLine(subheader('Usage:'));
|
||||||
console.log(` ${color('ccs', 'command')} [profile] [claude-args...]`);
|
writeLine(` ${color('ccs', 'command')} [profile] [claude-args...]`);
|
||||||
console.log(` ${color('ccs', 'command')} [flags]`);
|
writeLine(` ${color('ccs', 'command')} [flags]`);
|
||||||
console.log('');
|
writeLine('');
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
// MAJOR SECTION 1: API Key Profiles
|
// MAJOR SECTION 1: API Key Profiles
|
||||||
@@ -152,7 +167,8 @@ Run ${color('ccs config', 'command')} for web dashboard`.trim();
|
|||||||
['ccs api import <file>', 'Import profile bundle'],
|
['ccs api import <file>', 'Import profile bundle'],
|
||||||
['ccs api remove', 'Remove an API profile'],
|
['ccs api remove', 'Remove an API profile'],
|
||||||
['ccs api list', 'List all API profiles'],
|
['ccs api list', 'List all API profiles'],
|
||||||
]
|
],
|
||||||
|
writeLine
|
||||||
);
|
);
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
@@ -176,7 +192,8 @@ Run ${color('ccs config', 'command')} for web dashboard`.trim();
|
|||||||
['ccs auth default <name>', 'Set default profile'],
|
['ccs auth default <name>', 'Set default profile'],
|
||||||
['ccs auth reset-default', 'Restore original CCS default'],
|
['ccs auth reset-default', 'Restore original CCS default'],
|
||||||
['ccs cliproxy auth claude', 'Alternative: authenticate Claude account pool via CLIProxy'],
|
['ccs cliproxy auth claude', 'Alternative: authenticate Claude account pool via CLIProxy'],
|
||||||
]
|
],
|
||||||
|
writeLine
|
||||||
);
|
);
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
@@ -232,7 +249,8 @@ Run ${color('ccs config', 'command')} for web dashboard`.trim();
|
|||||||
['ccs kiro --import', 'Import token from Kiro IDE'],
|
['ccs kiro --import', 'Import token from Kiro IDE'],
|
||||||
['ccs kiro --incognito', 'Use incognito browser (default: normal)'],
|
['ccs kiro --incognito', 'Use incognito browser (default: normal)'],
|
||||||
['ccs codex "explain code"', 'Use with prompt'],
|
['ccs codex "explain code"', 'Use with prompt'],
|
||||||
]
|
],
|
||||||
|
writeLine
|
||||||
);
|
);
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
@@ -255,7 +273,8 @@ Run ${color('ccs config', 'command')} for web dashboard`.trim();
|
|||||||
['ccs copilot stop', 'Stop copilot-api daemon'],
|
['ccs copilot stop', 'Stop copilot-api daemon'],
|
||||||
['ccs copilot enable', 'Enable integration'],
|
['ccs copilot enable', 'Enable integration'],
|
||||||
['ccs copilot disable', 'Disable integration'],
|
['ccs copilot disable', 'Disable integration'],
|
||||||
]
|
],
|
||||||
|
writeLine
|
||||||
);
|
);
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
@@ -277,7 +296,8 @@ Run ${color('ccs config', 'command')} for web dashboard`.trim();
|
|||||||
['ccs cursor stop', 'Stop proxy daemon'],
|
['ccs cursor stop', 'Stop proxy daemon'],
|
||||||
['ccs cursor enable', 'Enable cursor integration'],
|
['ccs cursor enable', 'Enable cursor integration'],
|
||||||
['ccs cursor disable', 'Disable cursor integration'],
|
['ccs cursor disable', 'Disable cursor integration'],
|
||||||
]
|
],
|
||||||
|
writeLine
|
||||||
);
|
);
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
@@ -285,281 +305,351 @@ Run ${color('ccs config', 'command')} for web dashboard`.trim();
|
|||||||
// ═══════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
// Delegation
|
// Delegation
|
||||||
printSubSection('Delegation (inside Claude Code CLI)', [
|
printSubSection(
|
||||||
['/ccs "task"', 'Delegate task (auto-selects profile)'],
|
'Delegation (inside Claude Code CLI)',
|
||||||
['/ccs --glm "task"', 'Force GLM-5 for simple tasks'],
|
[
|
||||||
['/ccs --kimi "task"', 'Force Kimi OAuth for long context'],
|
['/ccs "task"', 'Delegate task (auto-selects profile)'],
|
||||||
['/ccs --km "task"', 'Force Kimi API key for long context'],
|
['/ccs --glm "task"', 'Force GLM-5 for simple tasks'],
|
||||||
['/ccs:continue "follow-up"', 'Continue last delegation session'],
|
['/ccs --kimi "task"', 'Force Kimi OAuth for long context'],
|
||||||
]);
|
['/ccs --km "task"', 'Force Kimi API key for long context'],
|
||||||
|
['/ccs:continue "follow-up"', 'Continue last delegation session'],
|
||||||
|
],
|
||||||
|
writeLine
|
||||||
|
);
|
||||||
|
|
||||||
// Delegation CLI Flags (Claude Code passthrough)
|
// Delegation CLI Flags (Claude Code passthrough)
|
||||||
printSubSection('Delegation Flags (Claude Code passthrough)', [
|
printSubSection(
|
||||||
['--max-turns <n>', 'Limit agentic turns (prevents loops)'],
|
'Delegation Flags (Claude Code passthrough)',
|
||||||
['--fallback-model <model>', 'Auto-fallback on overload (sonnet)'],
|
[
|
||||||
['--agents <json>', 'Inject dynamic subagents'],
|
['--max-turns <n>', 'Limit agentic turns (prevents loops)'],
|
||||||
['--betas <features>', 'Enable experimental features'],
|
['--fallback-model <model>', 'Auto-fallback on overload (sonnet)'],
|
||||||
['--allowedTools <list>', 'Restrict available tools'],
|
['--agents <json>', 'Inject dynamic subagents'],
|
||||||
['--disallowedTools <list>', 'Block specific tools'],
|
['--betas <features>', 'Enable experimental features'],
|
||||||
]);
|
['--allowedTools <list>', 'Restrict available tools'],
|
||||||
|
['--disallowedTools <list>', 'Block specific tools'],
|
||||||
|
],
|
||||||
|
writeLine
|
||||||
|
);
|
||||||
|
|
||||||
// Diagnostics
|
// Diagnostics
|
||||||
printSubSection('Diagnostics', [
|
printSubSection(
|
||||||
['ccs setup', 'First-time setup wizard'],
|
'Diagnostics',
|
||||||
['ccs doctor', 'Run health check and diagnostics'],
|
|
||||||
['ccs cleanup', 'Remove old CLIProxy logs'],
|
|
||||||
['ccs config', 'Open web dashboard (includes Claude IDE Extension setup page)'],
|
|
||||||
['ccs config auth setup', 'Configure dashboard login'],
|
|
||||||
['ccs config auth show', 'Show dashboard auth status'],
|
|
||||||
['ccs config channels', 'Show Official Channels status'],
|
|
||||||
[
|
[
|
||||||
'ccs config channels --set telegram,discord',
|
['ccs setup', 'First-time setup wizard'],
|
||||||
'Auto-add Telegram + Discord on supported native Claude runs',
|
['ccs doctor', 'Run health check and diagnostics'],
|
||||||
|
['ccs cleanup', 'Remove old CLIProxy logs'],
|
||||||
|
['ccs config', 'Open web dashboard (includes Claude IDE Extension setup page)'],
|
||||||
|
['ccs config auth setup', 'Configure dashboard login'],
|
||||||
|
['ccs config auth show', 'Show dashboard auth status'],
|
||||||
|
['ccs config channels', 'Show Official Channels status'],
|
||||||
|
[
|
||||||
|
'ccs config channels --set telegram,discord',
|
||||||
|
'Auto-add Telegram + Discord on supported native Claude runs',
|
||||||
|
],
|
||||||
|
['ccs config channels --set-token telegram=<token>', 'Save TELEGRAM_BOT_TOKEN'],
|
||||||
|
['ccs config image-analysis', 'Show image analysis settings'],
|
||||||
|
['ccs config image-analysis --enable', 'Enable image analysis'],
|
||||||
|
['ccs config thinking', 'Show thinking/reasoning settings'],
|
||||||
|
['ccs config thinking --mode auto', 'Set thinking mode'],
|
||||||
|
['ccs config thinking --clear-provider-override codex', 'Clear provider overrides'],
|
||||||
|
['ccs config --port 3000', 'Use specific port'],
|
||||||
|
['ccs config --host 0.0.0.0', 'Force all-interface binding for remote devices'],
|
||||||
|
['ccs persist <profile>', 'Write profile setup to ~/.claude/settings.json'],
|
||||||
|
['ccs persist --list-backups', 'List available settings.json backups'],
|
||||||
|
['ccs persist --restore', 'Restore settings.json from latest backup'],
|
||||||
|
['ccs sync', 'Sync delegation commands and skills'],
|
||||||
|
['ccs update', 'Update CCS to latest version'],
|
||||||
|
['ccs update --force', 'Force reinstall current version'],
|
||||||
|
['ccs update --beta', 'Install from dev channel (unstable)'],
|
||||||
],
|
],
|
||||||
['ccs config channels --set-token telegram=<token>', 'Save TELEGRAM_BOT_TOKEN'],
|
writeLine
|
||||||
['ccs config image-analysis', 'Show image analysis settings'],
|
);
|
||||||
['ccs config image-analysis --enable', 'Enable image analysis'],
|
|
||||||
['ccs config thinking', 'Show thinking/reasoning settings'],
|
|
||||||
['ccs config thinking --mode auto', 'Set thinking mode'],
|
|
||||||
['ccs config thinking --clear-provider-override codex', 'Clear provider overrides'],
|
|
||||||
['ccs config --port 3000', 'Use specific port'],
|
|
||||||
['ccs config --host 0.0.0.0', 'Force all-interface binding for remote devices'],
|
|
||||||
['ccs persist <profile>', 'Write profile setup to ~/.claude/settings.json'],
|
|
||||||
['ccs persist --list-backups', 'List available settings.json backups'],
|
|
||||||
['ccs persist --restore', 'Restore settings.json from latest backup'],
|
|
||||||
['ccs sync', 'Sync delegation commands and skills'],
|
|
||||||
['ccs update', 'Update CCS to latest version'],
|
|
||||||
['ccs update --force', 'Force reinstall current version'],
|
|
||||||
['ccs update --beta', 'Install from dev channel (unstable)'],
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Environment export
|
// Environment export
|
||||||
printSubSection('Environment Export', [
|
printSubSection(
|
||||||
['ccs env <profile>', 'Export env vars for third-party tools'],
|
'Environment Export',
|
||||||
['ccs env <profile> --format openai', 'OpenAI-compatible vars (OpenCode/Cursor)'],
|
|
||||||
['ccs env <profile> --format anthropic', 'Anthropic vars (default)'],
|
|
||||||
['ccs env <profile> --format raw', 'All effective env vars'],
|
|
||||||
[
|
[
|
||||||
'ccs env <profile> --format claude-extension --ide vscode',
|
['ccs env <profile>', 'Export env vars for third-party tools'],
|
||||||
'VS Code/Cursor Claude extension settings JSON',
|
['ccs env <profile> --format openai', 'OpenAI-compatible vars (OpenCode/Cursor)'],
|
||||||
|
['ccs env <profile> --format anthropic', 'Anthropic vars (default)'],
|
||||||
|
['ccs env <profile> --format raw', 'All effective env vars'],
|
||||||
|
[
|
||||||
|
'ccs env <profile> --format claude-extension --ide vscode',
|
||||||
|
'VS Code/Cursor Claude extension settings JSON',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'ccs env <profile> --format claude-extension --ide windsurf',
|
||||||
|
'Windsurf Claude extension settings JSON',
|
||||||
|
],
|
||||||
|
['ccs env <profile> --shell fish', 'Fish shell syntax'],
|
||||||
],
|
],
|
||||||
[
|
writeLine
|
||||||
'ccs env <profile> --format claude-extension --ide windsurf',
|
);
|
||||||
'Windsurf Claude extension settings JSON',
|
|
||||||
],
|
|
||||||
['ccs env <profile> --shell fish', 'Fish shell syntax'],
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Flags
|
// Flags
|
||||||
printSubSection('Flags', [
|
printSubSection(
|
||||||
['--config-dir <path>', 'Use custom CCS config directory'],
|
'Flags',
|
||||||
['--target <cli>', 'Target CLI: claude (default), droid'],
|
[
|
||||||
['-h, --help', 'Show this help message'],
|
['--config-dir <path>', 'Use custom CCS config directory'],
|
||||||
['-v, --version', 'Show version and installation info'],
|
['--target <cli>', 'Target CLI: claude (default), droid'],
|
||||||
['-sc, --shell-completion', 'Install shell auto-completion'],
|
['-h, --help', 'Show this help message'],
|
||||||
]);
|
['-v, --version', 'Show version and installation info'],
|
||||||
|
['-sc, --shell-completion', 'Install shell auto-completion'],
|
||||||
|
],
|
||||||
|
writeLine
|
||||||
|
);
|
||||||
|
|
||||||
// Aliases
|
// Aliases
|
||||||
printSubSection('Aliases', [
|
printSubSection(
|
||||||
['ccs-droid <profile> [args]', 'Explicit Droid runtime alias'],
|
'Aliases',
|
||||||
['ccsd <profile> [args]', 'Legacy shortcut for: ccs-droid <profile> [args]'],
|
[
|
||||||
]);
|
['ccs-droid <profile> [args]', 'Explicit Droid runtime alias'],
|
||||||
|
['ccsd <profile> [args]', 'Legacy shortcut for: ccs-droid <profile> [args]'],
|
||||||
|
],
|
||||||
|
writeLine
|
||||||
|
);
|
||||||
|
|
||||||
// Multi-target examples
|
// Multi-target examples
|
||||||
printSubSection('Multi-Target', [
|
printSubSection(
|
||||||
['ccs glm --target droid', 'Run GLM profile on Droid CLI'],
|
'Multi-Target',
|
||||||
['ccs-droid glm', 'Same as above (explicit alias)'],
|
|
||||||
['ccsd glm', 'Legacy shortcut for ccs-droid'],
|
|
||||||
['ccs-droid codex', 'Run built-in CLIProxy Codex profile on Droid'],
|
|
||||||
['ccs-droid agy', 'Run built-in CLIProxy Antigravity profile on Droid'],
|
|
||||||
[
|
[
|
||||||
'ccs-droid codex exec --skip-permissions-unsafe "fix failing tests"',
|
['ccs glm --target droid', 'Run GLM profile on Droid CLI'],
|
||||||
'Pass through Droid exec mode',
|
['ccs-droid glm', 'Same as above (explicit alias)'],
|
||||||
|
['ccsd glm', 'Legacy shortcut for ccs-droid'],
|
||||||
|
['ccs-droid codex', 'Run built-in CLIProxy Codex profile on Droid'],
|
||||||
|
['ccs-droid agy', 'Run built-in CLIProxy Antigravity profile on Droid'],
|
||||||
|
[
|
||||||
|
'ccs-droid codex exec --skip-permissions-unsafe "fix failing tests"',
|
||||||
|
'Pass through Droid exec mode',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'ccs-droid codex -m custom:gpt-5.3-codex "fix failing tests"',
|
||||||
|
'Auto-routes short exec flags',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'ccs-droid codex --skip-permissions-unsafe "fix failing tests"',
|
||||||
|
'Auto-routes to Droid exec when exec-only flags are detected',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'ccs cliproxy create my-codex --provider codex --target droid',
|
||||||
|
'Create CLIProxy variant with Droid as default target',
|
||||||
|
],
|
||||||
|
['ccs glm', 'Run GLM profile on Claude Code (default)'],
|
||||||
],
|
],
|
||||||
['ccs-droid codex -m custom:gpt-5.3-codex "fix failing tests"', 'Auto-routes short exec flags'],
|
writeLine
|
||||||
[
|
);
|
||||||
'ccs-droid codex --skip-permissions-unsafe "fix failing tests"',
|
|
||||||
'Auto-routes to Droid exec when exec-only flags are detected',
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'ccs cliproxy create my-codex --provider codex --target droid',
|
|
||||||
'Create CLIProxy variant with Droid as default target',
|
|
||||||
],
|
|
||||||
['ccs glm', 'Run GLM profile on Claude Code (default)'],
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Configuration
|
// Configuration
|
||||||
printConfigSection('Configuration', [
|
printConfigSection(
|
||||||
['Config File:', isUnifiedMode() ? `${dirDisplay}/config.yaml` : `${dirDisplay}/config.json`],
|
'Configuration',
|
||||||
['Profiles:', `${dirDisplay}/profiles.json`],
|
[
|
||||||
['Instances:', `${dirDisplay}/instances/`],
|
['Config File:', isUnifiedMode() ? `${dirDisplay}/config.yaml` : `${dirDisplay}/config.json`],
|
||||||
['Settings:', `${dirDisplay}/*.settings.json`],
|
['Profiles:', `${dirDisplay}/profiles.json`],
|
||||||
]);
|
['Instances:', `${dirDisplay}/instances/`],
|
||||||
|
['Settings:', `${dirDisplay}/*.settings.json`],
|
||||||
|
],
|
||||||
|
writeLine
|
||||||
|
);
|
||||||
|
|
||||||
// CLI Proxy management
|
// CLI Proxy management
|
||||||
printSubSection('CLI Proxy Plus Management', [
|
printSubSection(
|
||||||
['ccs cliproxy', 'Show CLIProxy Plus status and version'],
|
'CLI Proxy Plus Management',
|
||||||
['ccs cliproxy --help', 'Full CLIProxy Plus management help'],
|
[
|
||||||
['ccs cliproxy doctor', 'Quota diagnostics (Antigravity)'],
|
['ccs cliproxy', 'Show CLIProxy Plus status and version'],
|
||||||
['ccs cliproxy --install <ver>', 'Install specific version (e.g., 6.6.6)'],
|
['ccs cliproxy --help', 'Full CLIProxy Plus management help'],
|
||||||
['ccs cliproxy --latest', 'Update to latest version'],
|
['ccs cliproxy doctor', 'Quota diagnostics (Antigravity)'],
|
||||||
['', ''], // Spacer
|
['ccs cliproxy --install <ver>', 'Install specific version (e.g., 6.6.6)'],
|
||||||
['ccs cliproxy pause <p> <a>', 'Pause account from rotation'],
|
['ccs cliproxy --latest', 'Update to latest version'],
|
||||||
['ccs cliproxy resume <p> <a>', 'Resume paused account'],
|
['', ''], // Spacer
|
||||||
['ccs cliproxy status', 'Show CLIProxy process status'],
|
['ccs cliproxy pause <p> <a>', 'Pause account from rotation'],
|
||||||
['ccs cliproxy quota', 'Show quota/tier/pause status for all providers'],
|
['ccs cliproxy resume <p> <a>', 'Resume paused account'],
|
||||||
['ccs cliproxy quota --provider <name>', 'Show quota/tier/pause status for one provider'],
|
['ccs cliproxy status', 'Show CLIProxy process status'],
|
||||||
]);
|
['ccs cliproxy quota', 'Show quota/tier/pause status for all providers'],
|
||||||
|
['ccs cliproxy quota --provider <name>', 'Show quota/tier/pause status for one provider'],
|
||||||
|
],
|
||||||
|
writeLine
|
||||||
|
);
|
||||||
|
|
||||||
// CLI Proxy configuration flags (new)
|
// CLI Proxy configuration flags (new)
|
||||||
printSubSection('CLI Proxy Configuration', [
|
printSubSection(
|
||||||
['--proxy-host <host>', 'Remote proxy hostname/IP'],
|
'CLI Proxy Configuration',
|
||||||
['--proxy-port <port>', `Proxy port (default: ${CLIPROXY_DEFAULT_PORT})`],
|
[
|
||||||
['--proxy-protocol <proto>', 'Protocol: http or https (default: http)'],
|
['--proxy-host <host>', 'Remote proxy hostname/IP'],
|
||||||
['--proxy-auth-token <token>', 'Auth token for remote proxy'],
|
['--proxy-port <port>', `Proxy port (default: ${CLIPROXY_DEFAULT_PORT})`],
|
||||||
['--proxy-timeout <ms>', 'Connection timeout in ms (default: 2000)'],
|
['--proxy-protocol <proto>', 'Protocol: http or https (default: http)'],
|
||||||
['--local-proxy', 'Force local mode, ignore remote config'],
|
['--proxy-auth-token <token>', 'Auth token for remote proxy'],
|
||||||
['--remote-only', 'Fail if remote unreachable (no fallback)'],
|
['--proxy-timeout <ms>', 'Connection timeout in ms (default: 2000)'],
|
||||||
['--allow-self-signed', 'Allow self-signed certs (for dev proxies)'],
|
['--local-proxy', 'Force local mode, ignore remote config'],
|
||||||
]);
|
['--remote-only', 'Fail if remote unreachable (no fallback)'],
|
||||||
|
['--allow-self-signed', 'Allow self-signed certs (for dev proxies)'],
|
||||||
|
],
|
||||||
|
writeLine
|
||||||
|
);
|
||||||
|
|
||||||
// W3: Thinking Budget explanation
|
// W3: Thinking Budget explanation
|
||||||
printSubSection('Extended Thinking / Reasoning', [
|
printSubSection(
|
||||||
['--thinking off', 'Disable extended thinking'],
|
'Extended Thinking / Reasoning',
|
||||||
['--thinking auto', 'Let model decide dynamically'],
|
[
|
||||||
['--thinking low', '1K tokens - Quick responses'],
|
['--thinking off', 'Disable extended thinking'],
|
||||||
['--thinking medium', '8K tokens - Standard analysis'],
|
['--thinking auto', 'Let model decide dynamically'],
|
||||||
['--thinking high', '24K tokens - Deep reasoning'],
|
['--thinking low', '1K tokens - Quick responses'],
|
||||||
['--thinking xhigh', '32K tokens - Maximum depth'],
|
['--thinking medium', '8K tokens - Standard analysis'],
|
||||||
['--thinking <number>', 'Custom token budget (512-100000)'],
|
['--thinking high', '24K tokens - Deep reasoning'],
|
||||||
['', ''],
|
['--thinking xhigh', '32K tokens - Maximum depth'],
|
||||||
['--effort <level>', 'Codex alias for reasoning effort (medium/high/xhigh)'],
|
['--thinking <number>', 'Custom token budget (512-100000)'],
|
||||||
['--effort xhigh', 'Pin Codex effort to xhigh for this run'],
|
['', ''],
|
||||||
['', ''],
|
['--effort <level>', 'Codex alias for reasoning effort (medium/high/xhigh)'],
|
||||||
['Droid exec:', 'Use native Droid flag: --reasoning-effort <level>'],
|
['--effort xhigh', 'Pin Codex effort to xhigh for this run'],
|
||||||
['', 'CCS auto-maps --thinking/--effort to --reasoning-effort in droid exec mode.'],
|
['', ''],
|
||||||
['', 'For interactive droid sessions, CCS applies reasoning via Droid BYOK model config.'],
|
['Droid exec:', 'Use native Droid flag: --reasoning-effort <level>'],
|
||||||
['', 'When multiple reasoning flags are provided, the first flag wins.'],
|
['', 'CCS auto-maps --thinking/--effort to --reasoning-effort in droid exec mode.'],
|
||||||
['', ''],
|
['', 'For interactive droid sessions, CCS applies reasoning via Droid BYOK model config.'],
|
||||||
['Note:', 'Extended thinking allocates compute for step-by-step reasoning'],
|
['', 'When multiple reasoning flags are provided, the first flag wins.'],
|
||||||
['', 'before responding.'],
|
['', ''],
|
||||||
['', 'Providers: agy/gemini use --thinking, codex uses --effort (or --thinking alias).'],
|
['Note:', 'Extended thinking allocates compute for step-by-step reasoning'],
|
||||||
['', 'Codex model suffixes also pin effort: -medium / -high / -xhigh.'],
|
['', 'before responding.'],
|
||||||
]);
|
['', 'Providers: agy/gemini use --thinking, codex uses --effort (or --thinking alias).'],
|
||||||
|
['', 'Codex model suffixes also pin effort: -medium / -high / -xhigh.'],
|
||||||
|
],
|
||||||
|
writeLine
|
||||||
|
);
|
||||||
|
|
||||||
// Extended Context (1M)
|
// Extended Context (1M)
|
||||||
printSubSection('Extended Context (--1m)', [
|
printSubSection(
|
||||||
['--1m', 'Request 1M token context when the selected model supports [1m]'],
|
'Extended Context (--1m)',
|
||||||
['--no-1m', 'Force standard context (Claude default stays plain)'],
|
[
|
||||||
['', ''],
|
['--1m', 'Request 1M token context when the selected model supports [1m]'],
|
||||||
['Auto behavior:', 'Gemini models: CCS auto-adds [1m] when supported'],
|
['--no-1m', 'Force standard context (Claude default stays plain)'],
|
||||||
['', 'Claude models: plain by default, opt-in with --1m or saved [1m]'],
|
['', ''],
|
||||||
['', ''],
|
['Auto behavior:', 'Gemini models: CCS auto-adds [1m] when supported'],
|
||||||
['Note:', 'CCS only controls the saved [1m] suffix.'],
|
['', 'Claude models: plain by default, opt-in with --1m or saved [1m]'],
|
||||||
['', 'Provider pricing and entitlement stay upstream.'],
|
['', ''],
|
||||||
['', 'Some accounts/providers can still return 429 extra-usage errors for long-context requests.'],
|
['Note:', 'CCS only controls the saved [1m] suffix.'],
|
||||||
]);
|
['', 'Provider pricing and entitlement stay upstream.'],
|
||||||
|
[
|
||||||
|
'',
|
||||||
|
'Some accounts/providers can still return 429 extra-usage errors for long-context requests.',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
writeLine
|
||||||
|
);
|
||||||
|
|
||||||
// Image Analysis
|
// Image Analysis
|
||||||
printSubSection('Image Analysis (CLIProxy vision)', [
|
printSubSection(
|
||||||
['ccs config image-analysis', 'Show current settings'],
|
'Image Analysis (CLIProxy vision)',
|
||||||
['ccs config image-analysis --enable', 'Enable for CLIProxy providers'],
|
|
||||||
['ccs config image-analysis --disable', 'Disable (use native Read)'],
|
|
||||||
['ccs config image-analysis --timeout 120', 'Set analysis timeout'],
|
|
||||||
['ccs config image-analysis --set-model <p> <m>', 'Set provider model'],
|
|
||||||
['', ''],
|
|
||||||
['Note:', 'When enabled, images/PDFs are analyzed via vision models'],
|
|
||||||
['', 'instead of passing raw data to Claude. Works with CLIProxy'],
|
|
||||||
['', 'providers (agy, gemini, codex, kiro, ghcp).'],
|
|
||||||
]);
|
|
||||||
|
|
||||||
printSubSection('Official Channels (official Claude plugins)', [
|
|
||||||
['ccs config', 'Dashboard -> Settings -> Channels (fastest path)'],
|
|
||||||
['ccs config channels', 'Show current status'],
|
|
||||||
[
|
[
|
||||||
'ccs config channels --set telegram,discord',
|
['ccs config image-analysis', 'Show current settings'],
|
||||||
'Auto-add selected channels on native Claude default/account sessions',
|
['ccs config image-analysis --enable', 'Enable for CLIProxy providers'],
|
||||||
|
['ccs config image-analysis --disable', 'Disable (use native Read)'],
|
||||||
|
['ccs config image-analysis --timeout 120', 'Set analysis timeout'],
|
||||||
|
['ccs config image-analysis --set-model <p> <m>', 'Set provider model'],
|
||||||
|
['', ''],
|
||||||
|
['Note:', 'When enabled, images/PDFs are analyzed via vision models'],
|
||||||
|
['', 'instead of passing raw data to Claude. Works with CLIProxy'],
|
||||||
|
['', 'providers (agy, gemini, codex, kiro, ghcp).'],
|
||||||
],
|
],
|
||||||
['ccs config channels --set all', 'Enable Telegram, Discord, and iMessage'],
|
writeLine
|
||||||
['ccs config channels --unattended', 'Also add --dangerously-skip-permissions'],
|
);
|
||||||
['ccs config channels --set-token telegram=<token>', 'Save TELEGRAM_BOT_TOKEN'],
|
|
||||||
['ccs config channels --set-token discord=<token>', 'Save DISCORD_BOT_TOKEN'],
|
printSubSection(
|
||||||
['ccs config channels --clear-token [channel]', 'Remove one or all saved channel tokens'],
|
'Official Channels (official Claude plugins)',
|
||||||
['', ''],
|
[
|
||||||
['', 'Fastest path: turn on the channel, save the token if needed, then run ccs.'],
|
['ccs config', 'Dashboard -> Settings -> Channels (fastest path)'],
|
||||||
['Note:', 'Runtime-only. Applies to native Claude default/account sessions only.'],
|
['ccs config channels', 'Show current status'],
|
||||||
['', 'Not supported for ccs glm, other API/OAuth profiles, or Droid targets.'],
|
[
|
||||||
['', 'Telegram/Discord tokens live in ~/.claude/channels/<channel>/.env.'],
|
'ccs config channels --set telegram,discord',
|
||||||
['', 'Current-process TELEGRAM_BOT_TOKEN / DISCORD_BOT_TOKEN also work for that launch.'],
|
'Auto-add selected channels on native Claude default/account sessions',
|
||||||
['', 'iMessage is macOS-only and requires local OS permissions instead of a bot token.'],
|
],
|
||||||
]);
|
['ccs config channels --set all', 'Enable Telegram, Discord, and iMessage'],
|
||||||
|
['ccs config channels --unattended', 'Also add --dangerously-skip-permissions'],
|
||||||
|
['ccs config channels --set-token telegram=<token>', 'Save TELEGRAM_BOT_TOKEN'],
|
||||||
|
['ccs config channels --set-token discord=<token>', 'Save DISCORD_BOT_TOKEN'],
|
||||||
|
['ccs config channels --clear-token [channel]', 'Remove one or all saved channel tokens'],
|
||||||
|
['', ''],
|
||||||
|
['', 'Fastest path: turn on the channel, save the token if needed, then run ccs.'],
|
||||||
|
['Note:', 'Runtime-only. Applies to native Claude default/account sessions only.'],
|
||||||
|
['', 'Not supported for ccs glm, other API/OAuth profiles, or Droid targets.'],
|
||||||
|
['', 'Telegram/Discord tokens live in ~/.claude/channels/<channel>/.env.'],
|
||||||
|
['', 'Current-process TELEGRAM_BOT_TOKEN / DISCORD_BOT_TOKEN also work for that launch.'],
|
||||||
|
['', 'iMessage is macOS-only and requires local OS permissions instead of a bot token.'],
|
||||||
|
],
|
||||||
|
writeLine
|
||||||
|
);
|
||||||
|
|
||||||
// CCS Environment Variables
|
// CCS Environment Variables
|
||||||
printSubSection('Environment Variables', [
|
printSubSection(
|
||||||
['CCS_DIR', 'Override CCS config directory (default: ~/.ccs)'],
|
'Environment Variables',
|
||||||
['CCS_HOME', 'Override home directory (legacy, appends .ccs)'],
|
[
|
||||||
['CCS_DEBUG', 'Enable debug logging'],
|
['CCS_DIR', 'Override CCS config directory (default: ~/.ccs)'],
|
||||||
['CCS_THINKING', 'Override thinking level (flag > env > config)'],
|
['CCS_HOME', 'Override home directory (legacy, appends .ccs)'],
|
||||||
]);
|
['CCS_DEBUG', 'Enable debug logging'],
|
||||||
|
['CCS_THINKING', 'Override thinking level (flag > env > config)'],
|
||||||
|
],
|
||||||
|
writeLine
|
||||||
|
);
|
||||||
|
|
||||||
// CLI Proxy env vars
|
// CLI Proxy env vars
|
||||||
printSubSection('CLI Proxy Environment Variables', [
|
printSubSection(
|
||||||
['CCS_PROXY_HOST', 'Remote proxy hostname'],
|
'CLI Proxy Environment Variables',
|
||||||
['CCS_PROXY_PORT', 'Proxy port'],
|
[
|
||||||
['CCS_PROXY_PROTOCOL', 'Protocol (http/https)'],
|
['CCS_PROXY_HOST', 'Remote proxy hostname'],
|
||||||
['CCS_PROXY_AUTH_TOKEN', 'Auth token'],
|
['CCS_PROXY_PORT', 'Proxy port'],
|
||||||
['CCS_PROXY_TIMEOUT', 'Connection timeout in ms'],
|
['CCS_PROXY_PROTOCOL', 'Protocol (http/https)'],
|
||||||
['CCS_PROXY_FALLBACK_ENABLED', 'Enable local fallback (1/0)'],
|
['CCS_PROXY_AUTH_TOKEN', 'Auth token'],
|
||||||
['CCS_ALLOW_SELF_SIGNED', 'Allow self-signed certs (1/0)'],
|
['CCS_PROXY_TIMEOUT', 'Connection timeout in ms'],
|
||||||
]);
|
['CCS_PROXY_FALLBACK_ENABLED', 'Enable local fallback (1/0)'],
|
||||||
|
['CCS_ALLOW_SELF_SIGNED', 'Allow self-signed certs (1/0)'],
|
||||||
|
],
|
||||||
|
writeLine
|
||||||
|
);
|
||||||
|
|
||||||
// CLI Proxy paths
|
// CLI Proxy paths
|
||||||
console.log(subheader('CLI Proxy:'));
|
writeLine(subheader('CLI Proxy:'));
|
||||||
console.log(` Binary: ${color(`${dirDisplay}/cliproxy/bin/cli-proxy-api-plus`, 'path')}`);
|
writeLine(` Binary: ${color(`${dirDisplay}/cliproxy/bin/cli-proxy-api-plus`, 'path')}`);
|
||||||
console.log(` Config: ${color(`${dirDisplay}/cliproxy/config.yaml`, 'path')}`);
|
writeLine(` Config: ${color(`${dirDisplay}/cliproxy/config.yaml`, 'path')}`);
|
||||||
console.log(` Auth: ${color(`${dirDisplay}/cliproxy/auth/`, 'path')}`);
|
writeLine(` Auth: ${color(`${dirDisplay}/cliproxy/auth/`, 'path')}`);
|
||||||
console.log(` ${dim(`Port: ${CLIPROXY_DEFAULT_PORT} (default)`)}`);
|
writeLine(` ${dim(`Port: ${CLIPROXY_DEFAULT_PORT} (default)`)}`);
|
||||||
console.log('');
|
writeLine('');
|
||||||
|
|
||||||
// Shared Data
|
// Shared Data
|
||||||
console.log(subheader('Shared Data:'));
|
writeLine(subheader('Shared Data:'));
|
||||||
console.log(` Commands: ${color(`${dirDisplay}/shared/commands/`, 'path')}`);
|
writeLine(` Commands: ${color(`${dirDisplay}/shared/commands/`, 'path')}`);
|
||||||
console.log(` Skills: ${color(`${dirDisplay}/shared/skills/`, 'path')}`);
|
writeLine(` Skills: ${color(`${dirDisplay}/shared/skills/`, 'path')}`);
|
||||||
console.log(` Agents: ${color(`${dirDisplay}/shared/agents/`, 'path')}`);
|
writeLine(` Agents: ${color(`${dirDisplay}/shared/agents/`, 'path')}`);
|
||||||
console.log(` ${dim('Note: Symlinked across all profiles')}`);
|
writeLine(` ${dim('Note: Symlinked across all profiles')}`);
|
||||||
console.log('');
|
writeLine('');
|
||||||
|
|
||||||
// Examples (aligned with consistent spacing)
|
// Examples (aligned with consistent spacing)
|
||||||
console.log(subheader('Examples:'));
|
writeLine(subheader('Examples:'));
|
||||||
console.log(` $ ${color('ccs', 'command')} ${dim('# Use default account')}`);
|
writeLine(` $ ${color('ccs', 'command')} ${dim('# Use default account')}`);
|
||||||
console.log(
|
writeLine(
|
||||||
` $ ${color('ccs gemini', 'command')} ${dim('# OAuth (browser opens first time)')}`
|
` $ ${color('ccs gemini', 'command')} ${dim('# OAuth (browser opens first time)')}`
|
||||||
);
|
);
|
||||||
console.log(` $ ${color('ccs glm "implement API"', 'command')} ${dim('# API key model')}`);
|
writeLine(` $ ${color('ccs glm "implement API"', 'command')} ${dim('# API key model')}`);
|
||||||
console.log(` $ ${color('ccs config', 'command')} ${dim('# Open web dashboard')}`);
|
writeLine(` $ ${color('ccs config', 'command')} ${dim('# Open web dashboard')}`);
|
||||||
console.log('');
|
writeLine('');
|
||||||
|
|
||||||
// Update examples
|
// Update examples
|
||||||
console.log(subheader('Update:'));
|
writeLine(subheader('Update:'));
|
||||||
console.log(
|
writeLine(
|
||||||
` $ ${color('ccs update', 'command')} ${dim('# Update to latest stable')}`
|
` $ ${color('ccs update', 'command')} ${dim('# Update to latest stable')}`
|
||||||
);
|
);
|
||||||
console.log(
|
writeLine(
|
||||||
` $ ${color('ccs update --force', 'command')} ${dim('# Force reinstall current')}`
|
` $ ${color('ccs update --force', 'command')} ${dim('# Force reinstall current')}`
|
||||||
);
|
);
|
||||||
console.log(` $ ${color('ccs update --beta', 'command')} ${dim('# Install dev channel')}`);
|
writeLine(` $ ${color('ccs update --beta', 'command')} ${dim('# Install dev channel')}`);
|
||||||
console.log('');
|
writeLine('');
|
||||||
|
|
||||||
// Docs link
|
// Docs link
|
||||||
console.log(` ${dim('Docs: https://github.com/kaitranntt/ccs')}`);
|
writeLine(` ${dim('Docs: https://github.com/kaitranntt/ccs')}`);
|
||||||
console.log('');
|
writeLine('');
|
||||||
|
|
||||||
// Uninstall
|
// Uninstall
|
||||||
console.log(subheader('Uninstall:'));
|
writeLine(subheader('Uninstall:'));
|
||||||
console.log(` ${color('npm uninstall -g @kaitranntt/ccs', 'command')}`);
|
writeLine(` ${color('npm uninstall -g @kaitranntt/ccs', 'command')}`);
|
||||||
console.log('');
|
writeLine('');
|
||||||
|
|
||||||
// License
|
// License
|
||||||
console.log(dim('License: MIT'));
|
writeLine(dim('License: MIT'));
|
||||||
console.log('');
|
writeLine('');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,74 +1,48 @@
|
|||||||
import { afterEach, beforeEach, describe, expect, it, mock } from 'bun:test';
|
import { beforeEach, describe, expect, it } from 'bun:test';
|
||||||
|
|
||||||
|
import { createApiCommandHandler } from '../../../src/commands/api-command';
|
||||||
|
|
||||||
let calls: string[] = [];
|
let calls: string[] = [];
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
calls = [];
|
calls = [];
|
||||||
|
});
|
||||||
|
|
||||||
mock.module('../../../src/commands/api-command/help', () => ({
|
function buildHandleApiCommand() {
|
||||||
showApiCommandHelp: async () => {
|
return createApiCommandHandler({
|
||||||
|
help: async () => {
|
||||||
calls.push('help');
|
calls.push('help');
|
||||||
},
|
},
|
||||||
showUnknownApiCommand: async (command: string) => {
|
unknown: async (command: string) => {
|
||||||
calls.push(`unknown:${command}`);
|
calls.push(`unknown:${command}`);
|
||||||
},
|
},
|
||||||
}));
|
create: async (args: string[]) => {
|
||||||
|
|
||||||
mock.module('../../../src/commands/api-command/create-command', () => ({
|
|
||||||
handleApiCreateCommand: async (args: string[]) => {
|
|
||||||
calls.push(`create:${args.join(' ')}`);
|
calls.push(`create:${args.join(' ')}`);
|
||||||
},
|
},
|
||||||
}));
|
list: async (args: string[]) => {
|
||||||
|
|
||||||
mock.module('../../../src/commands/api-command/list-command', () => ({
|
|
||||||
handleApiListCommand: async (args: string[]) => {
|
|
||||||
calls.push(`list:${args.join(' ')}`);
|
calls.push(`list:${args.join(' ')}`);
|
||||||
},
|
},
|
||||||
}));
|
discover: async (args: string[]) => {
|
||||||
|
|
||||||
mock.module('../../../src/commands/api-command/remove-command', () => ({
|
|
||||||
handleApiRemoveCommand: async (args: string[]) => {
|
|
||||||
calls.push(`remove:${args.join(' ')}`);
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
mock.module('../../../src/commands/api-command/discover-command', () => ({
|
|
||||||
handleApiDiscoverCommand: async (args: string[]) => {
|
|
||||||
calls.push(`discover:${args.join(' ')}`);
|
calls.push(`discover:${args.join(' ')}`);
|
||||||
},
|
},
|
||||||
}));
|
copy: async (args: string[]) => {
|
||||||
|
|
||||||
mock.module('../../../src/commands/api-command/copy-command', () => ({
|
|
||||||
handleApiCopyCommand: async (args: string[]) => {
|
|
||||||
calls.push(`copy:${args.join(' ')}`);
|
calls.push(`copy:${args.join(' ')}`);
|
||||||
},
|
},
|
||||||
}));
|
export: async (args: string[]) => {
|
||||||
|
|
||||||
mock.module('../../../src/commands/api-command/export-command', () => ({
|
|
||||||
handleApiExportCommand: async (args: string[]) => {
|
|
||||||
calls.push(`export:${args.join(' ')}`);
|
calls.push(`export:${args.join(' ')}`);
|
||||||
},
|
},
|
||||||
}));
|
import: async (args: string[]) => {
|
||||||
|
|
||||||
mock.module('../../../src/commands/api-command/import-command', () => ({
|
|
||||||
handleApiImportCommand: async (args: string[]) => {
|
|
||||||
calls.push(`import:${args.join(' ')}`);
|
calls.push(`import:${args.join(' ')}`);
|
||||||
},
|
},
|
||||||
}));
|
remove: async (args: string[]) => {
|
||||||
});
|
calls.push(`remove:${args.join(' ')}`);
|
||||||
|
},
|
||||||
afterEach(() => {
|
});
|
||||||
mock.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
async function loadHandleApiCommand() {
|
|
||||||
const mod = await import(`../../../src/commands/api-command?test=${Date.now()}-${Math.random()}`);
|
|
||||||
return mod.handleApiCommand;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('api-command router', () => {
|
describe('api-command router', () => {
|
||||||
it('defaults to help when no subcommand is provided', async () => {
|
it('defaults to help when no subcommand is provided', async () => {
|
||||||
const handleApiCommand = await loadHandleApiCommand();
|
const handleApiCommand = buildHandleApiCommand();
|
||||||
|
|
||||||
await handleApiCommand([]);
|
await handleApiCommand([]);
|
||||||
|
|
||||||
@@ -76,7 +50,7 @@ describe('api-command router', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('routes remove aliases through the named command dispatcher', async () => {
|
it('routes remove aliases through the named command dispatcher', async () => {
|
||||||
const handleApiCommand = await loadHandleApiCommand();
|
const handleApiCommand = buildHandleApiCommand();
|
||||||
|
|
||||||
await handleApiCommand(['rm', 'profile-a']);
|
await handleApiCommand(['rm', 'profile-a']);
|
||||||
|
|
||||||
@@ -84,7 +58,7 @@ describe('api-command router', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('forwards list arguments to the handler for validation', async () => {
|
it('forwards list arguments to the handler for validation', async () => {
|
||||||
const handleApiCommand = await loadHandleApiCommand();
|
const handleApiCommand = buildHandleApiCommand();
|
||||||
|
|
||||||
await handleApiCommand(['list', 'unexpected']);
|
await handleApiCommand(['list', 'unexpected']);
|
||||||
|
|
||||||
@@ -92,7 +66,7 @@ describe('api-command router', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('routes hardened subcommands through their handlers', async () => {
|
it('routes hardened subcommands through their handlers', async () => {
|
||||||
const handleApiCommand = await loadHandleApiCommand();
|
const handleApiCommand = buildHandleApiCommand();
|
||||||
|
|
||||||
await handleApiCommand(['discover', '--json']);
|
await handleApiCommand(['discover', '--json']);
|
||||||
await handleApiCommand(['copy', 'source', 'dest']);
|
await handleApiCommand(['copy', 'source', 'dest']);
|
||||||
@@ -108,7 +82,7 @@ describe('api-command router', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('delegates unknown commands to the shared unknown handler', async () => {
|
it('delegates unknown commands to the shared unknown handler', async () => {
|
||||||
const handleApiCommand = await loadHandleApiCommand();
|
const handleApiCommand = buildHandleApiCommand();
|
||||||
|
|
||||||
await handleApiCommand(['bogus']);
|
await handleApiCommand(['bogus']);
|
||||||
|
|
||||||
|
|||||||
@@ -15,20 +15,6 @@ beforeEach(() => {
|
|||||||
logLines.push(args.map(String).join(' '));
|
logLines.push(args.map(String).join(' '));
|
||||||
};
|
};
|
||||||
|
|
||||||
const uiModule = {
|
|
||||||
initUI: async () => {},
|
|
||||||
header: (message: string) => message,
|
|
||||||
subheader: (message: string) => message,
|
|
||||||
color: (message: string) => message,
|
|
||||||
dim: (message: string) => message,
|
|
||||||
ok: (message: string) => message,
|
|
||||||
info: (message: string) => message,
|
|
||||||
warn: (message: string) => message,
|
|
||||||
fail: (message: string) => message,
|
|
||||||
};
|
|
||||||
mock.module('../../../src/utils/ui', () => uiModule);
|
|
||||||
mock.module('../../../src/utils/ui.ts', () => uiModule);
|
|
||||||
|
|
||||||
mock.module('../../../src/commands/config-auth/setup-command', () => ({
|
mock.module('../../../src/commands/config-auth/setup-command', () => ({
|
||||||
handleSetup: async () => {
|
handleSetup: async () => {
|
||||||
calls.push('setup');
|
calls.push('setup');
|
||||||
@@ -55,9 +41,7 @@ afterEach(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
async function loadHandleConfigAuthCommand() {
|
async function loadHandleConfigAuthCommand() {
|
||||||
const mod = await import(
|
const mod = await import(`../../../src/commands/config-auth?test=${Date.now()}-${Math.random()}`);
|
||||||
`../../../src/commands/config-auth?test=${Date.now()}-${Math.random()}`
|
|
||||||
);
|
|
||||||
return mod.handleConfigAuthCommand;
|
return mod.handleConfigAuthCommand;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { afterEach, describe, expect, test } from 'bun:test';
|
import { describe, expect, test } from 'bun:test';
|
||||||
|
|
||||||
import { showApiCommandHelp } from '../../../src/commands/api-command/help';
|
import { showApiCommandHelp } from '../../../src/commands/api-command/help';
|
||||||
import { handleHelpCommand } from '../../../src/commands/help-command';
|
import { handleHelpCommand } from '../../../src/commands/help-command';
|
||||||
@@ -8,19 +8,9 @@ function stripAnsi(input: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe('help command parity', () => {
|
describe('help command parity', () => {
|
||||||
const originalLog = console.log;
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
console.log = originalLog;
|
|
||||||
});
|
|
||||||
|
|
||||||
test('root help documents cliproxy provider filter under quota command', async () => {
|
test('root help documents cliproxy provider filter under quota command', async () => {
|
||||||
const lines: string[] = [];
|
const lines: string[] = [];
|
||||||
console.log = (...args: unknown[]) => {
|
await handleHelpCommand((line) => lines.push(line));
|
||||||
lines.push(args.map((arg) => String(arg)).join(' '));
|
|
||||||
};
|
|
||||||
|
|
||||||
await handleHelpCommand();
|
|
||||||
|
|
||||||
const rendered = stripAnsi(lines.join('\n'));
|
const rendered = stripAnsi(lines.join('\n'));
|
||||||
expect(rendered.includes('ccs cliproxy status [provider]')).toBe(false);
|
expect(rendered.includes('ccs cliproxy status [provider]')).toBe(false);
|
||||||
@@ -31,11 +21,7 @@ describe('help command parity', () => {
|
|||||||
|
|
||||||
test('root help documents llama.cpp as a local API profile', async () => {
|
test('root help documents llama.cpp as a local API profile', async () => {
|
||||||
const lines: string[] = [];
|
const lines: string[] = [];
|
||||||
console.log = (...args: unknown[]) => {
|
await handleHelpCommand((line) => lines.push(line));
|
||||||
lines.push(args.map((arg) => String(arg)).join(' '));
|
|
||||||
};
|
|
||||||
|
|
||||||
await handleHelpCommand();
|
|
||||||
|
|
||||||
const rendered = stripAnsi(lines.join('\n'));
|
const rendered = stripAnsi(lines.join('\n'));
|
||||||
expect(rendered.includes('ccs llamacpp')).toBe(true);
|
expect(rendered.includes('ccs llamacpp')).toBe(true);
|
||||||
@@ -44,11 +30,7 @@ describe('help command parity', () => {
|
|||||||
|
|
||||||
test('root help no longer markets glmt as a supported profile', async () => {
|
test('root help no longer markets glmt as a supported profile', async () => {
|
||||||
const lines: string[] = [];
|
const lines: string[] = [];
|
||||||
console.log = (...args: unknown[]) => {
|
await handleHelpCommand((line) => lines.push(line));
|
||||||
lines.push(args.map((arg) => String(arg)).join(' '));
|
|
||||||
};
|
|
||||||
|
|
||||||
await handleHelpCommand();
|
|
||||||
|
|
||||||
const rendered = stripAnsi(lines.join('\n'));
|
const rendered = stripAnsi(lines.join('\n'));
|
||||||
expect(rendered.includes('ccs glmt')).toBe(false);
|
expect(rendered.includes('ccs glmt')).toBe(false);
|
||||||
@@ -57,11 +39,7 @@ describe('help command parity', () => {
|
|||||||
|
|
||||||
test('root help documents Claude IDE extension setup surfaces', async () => {
|
test('root help documents Claude IDE extension setup surfaces', async () => {
|
||||||
const lines: string[] = [];
|
const lines: string[] = [];
|
||||||
console.log = (...args: unknown[]) => {
|
await handleHelpCommand((line) => lines.push(line));
|
||||||
lines.push(args.map((arg) => String(arg)).join(' '));
|
|
||||||
};
|
|
||||||
|
|
||||||
await handleHelpCommand();
|
|
||||||
|
|
||||||
const rendered = stripAnsi(lines.join('\n'));
|
const rendered = stripAnsi(lines.join('\n'));
|
||||||
expect(rendered.includes('Claude IDE Extension setup page')).toBe(true);
|
expect(rendered.includes('Claude IDE Extension setup page')).toBe(true);
|
||||||
@@ -75,11 +53,7 @@ describe('help command parity', () => {
|
|||||||
|
|
||||||
test('root help documents dashboard host binding example', async () => {
|
test('root help documents dashboard host binding example', async () => {
|
||||||
const lines: string[] = [];
|
const lines: string[] = [];
|
||||||
console.log = (...args: unknown[]) => {
|
await handleHelpCommand((line) => lines.push(line));
|
||||||
lines.push(args.map((arg) => String(arg)).join(' '));
|
|
||||||
};
|
|
||||||
|
|
||||||
await handleHelpCommand();
|
|
||||||
|
|
||||||
const rendered = stripAnsi(lines.join('\n'));
|
const rendered = stripAnsi(lines.join('\n'));
|
||||||
expect(rendered.includes('ccs config --host 0.0.0.0')).toBe(true);
|
expect(rendered.includes('ccs config --host 0.0.0.0')).toBe(true);
|
||||||
@@ -88,48 +62,36 @@ describe('help command parity', () => {
|
|||||||
|
|
||||||
test('root help documents official channels native-only scope and process-env tokens', async () => {
|
test('root help documents official channels native-only scope and process-env tokens', async () => {
|
||||||
const lines: string[] = [];
|
const lines: string[] = [];
|
||||||
console.log = (...args: unknown[]) => {
|
await handleHelpCommand((line) => lines.push(line));
|
||||||
lines.push(args.map((arg) => String(arg)).join(' '));
|
|
||||||
};
|
|
||||||
|
|
||||||
await handleHelpCommand();
|
|
||||||
|
|
||||||
const rendered = stripAnsi(lines.join('\n'));
|
const rendered = stripAnsi(lines.join('\n'));
|
||||||
expect(rendered.includes('Dashboard -> Settings -> Channels (fastest path)')).toBe(true);
|
expect(rendered.includes('Dashboard -> Settings -> Channels (fastest path)')).toBe(true);
|
||||||
expect(
|
expect(
|
||||||
rendered.includes('Fastest path: turn on the channel, save the token if needed, then run ccs.')
|
rendered.includes(
|
||||||
|
'Fastest path: turn on the channel, save the token if needed, then run ccs.'
|
||||||
|
)
|
||||||
).toBe(true);
|
).toBe(true);
|
||||||
expect(rendered.includes('Not supported for ccs glm')).toBe(true);
|
expect(rendered.includes('Not supported for ccs glm')).toBe(true);
|
||||||
expect(rendered.includes('Current-process TELEGRAM_BOT_TOKEN / DISCORD_BOT_TOKEN also work')).toBe(
|
expect(
|
||||||
true
|
rendered.includes('Current-process TELEGRAM_BOT_TOKEN / DISCORD_BOT_TOKEN also work')
|
||||||
);
|
).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('root help explains Claude [1m] as an explicit CCS suffix with upstream limits', async () => {
|
test('root help explains Claude [1m] as an explicit CCS suffix with upstream limits', async () => {
|
||||||
const lines: string[] = [];
|
const lines: string[] = [];
|
||||||
console.log = (...args: unknown[]) => {
|
await handleHelpCommand((line) => lines.push(line));
|
||||||
lines.push(args.map((arg) => String(arg)).join(' '));
|
|
||||||
};
|
|
||||||
|
|
||||||
await handleHelpCommand();
|
|
||||||
|
|
||||||
const rendered = stripAnsi(lines.join('\n'));
|
const rendered = stripAnsi(lines.join('\n'));
|
||||||
expect(rendered.includes('Claude models: plain by default, opt-in with --1m or saved [1m]')).toBe(
|
expect(
|
||||||
true
|
rendered.includes('Claude models: plain by default, opt-in with --1m or saved [1m]')
|
||||||
);
|
).toBe(true);
|
||||||
expect(rendered.includes('CCS only controls the saved [1m] suffix.')).toBe(true);
|
expect(rendered.includes('CCS only controls the saved [1m] suffix.')).toBe(true);
|
||||||
expect(rendered.includes('return 429 extra-usage errors for long-context requests')).toBe(
|
expect(rendered.includes('return 429 extra-usage errors for long-context requests')).toBe(true);
|
||||||
true
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('api help documents create-time Claude [1m] flags and entitlement warning', async () => {
|
test('api help documents create-time Claude [1m] flags and entitlement warning', async () => {
|
||||||
const lines: string[] = [];
|
const lines: string[] = [];
|
||||||
console.log = (...args: unknown[]) => {
|
await showApiCommandHelp((line) => lines.push(line));
|
||||||
lines.push(args.map((arg) => String(arg)).join(' '));
|
|
||||||
};
|
|
||||||
|
|
||||||
await showApiCommandHelp();
|
|
||||||
|
|
||||||
const rendered = stripAnsi(lines.join('\n'));
|
const rendered = stripAnsi(lines.join('\n'));
|
||||||
expect(rendered.includes('--1m / --no-1m')).toBe(true);
|
expect(rendered.includes('--1m / --no-1m')).toBe(true);
|
||||||
|
|||||||
Reference in New Issue
Block a user