mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 08:17:11 +00:00
feat(cliproxy): add target support to subcommands
- add --target parsing and validation in create/edit/remove flows - show target in list/remove/edit UX and usage examples - document target option in cliproxy help output
This commit is contained in:
@@ -45,10 +45,13 @@ export async function handleList(): Promise<void> {
|
||||
const variant = variants[name];
|
||||
const providerDisplay = variant.type === 'composite' ? 'composite' : variant.provider;
|
||||
const portStr = variant.port ? String(variant.port) : '-';
|
||||
return [name, providerDisplay, portStr, variant.settings || '-'];
|
||||
return [name, providerDisplay, variant.target || 'claude', portStr, variant.settings || '-'];
|
||||
});
|
||||
console.log(
|
||||
table(rows, { head: ['Variant', 'Provider', 'Port', 'Settings'], colWidths: [15, 12, 8, 30] })
|
||||
table(rows, {
|
||||
head: ['Variant', 'Provider', 'Target', 'Port', 'Settings'],
|
||||
colWidths: [15, 12, 10, 8, 24],
|
||||
})
|
||||
);
|
||||
console.log('');
|
||||
console.log(dim(`Total: ${variantNames.length} custom variant(s)`));
|
||||
|
||||
@@ -81,6 +81,7 @@ export async function showHelp(): Promise<void> {
|
||||
'Options:',
|
||||
[
|
||||
['--backend <type>', 'Use specific backend: original | plus (default: from config)'],
|
||||
['--target <cli>', 'Default target for created/edited variants: claude | droid'],
|
||||
['--verbose, -v', 'Show detailed quota fetch diagnostics'],
|
||||
],
|
||||
],
|
||||
|
||||
@@ -12,6 +12,7 @@ import { triggerOAuth } from '../../cliproxy/auth/oauth-handler';
|
||||
import { CLIProxyProfileName, CLIPROXY_PROFILES } from '../../auth/profile-detector';
|
||||
import { supportsModelConfig, getProviderCatalog, ModelEntry } from '../../cliproxy/model-catalog';
|
||||
import { CLIProxyProvider, CLIProxyBackend } from '../../cliproxy/types';
|
||||
import type { TargetType } from '../../targets/target-adapter';
|
||||
import { isUnifiedMode } from '../../config/unified-config-loader';
|
||||
import { initUI, header, color, ok, fail, warn, info, infoBox, dim } from '../../utils/ui';
|
||||
import { InteractivePrompt } from '../../utils/prompt';
|
||||
@@ -32,13 +33,23 @@ interface CliproxyProfileArgs {
|
||||
provider?: CLIProxyProfileName;
|
||||
model?: string;
|
||||
account?: string;
|
||||
target?: TargetType;
|
||||
force?: boolean;
|
||||
yes?: boolean;
|
||||
composite?: boolean;
|
||||
errors: string[];
|
||||
}
|
||||
|
||||
function parseTargetValue(rawValue: string): TargetType | null {
|
||||
const normalized = rawValue.trim().toLowerCase();
|
||||
if (normalized === 'claude' || normalized === 'droid') {
|
||||
return normalized;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function parseProfileArgs(args: string[]): CliproxyProfileArgs {
|
||||
const result: CliproxyProfileArgs = {};
|
||||
const result: CliproxyProfileArgs = { errors: [] };
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i];
|
||||
if (arg === '--provider' && args[i + 1]) {
|
||||
@@ -47,6 +58,27 @@ function parseProfileArgs(args: string[]): CliproxyProfileArgs {
|
||||
result.model = args[++i];
|
||||
} else if (arg === '--account' && args[i + 1]) {
|
||||
result.account = args[++i];
|
||||
} else if (arg === '--target') {
|
||||
const rawValue = args[i + 1];
|
||||
if (!rawValue || rawValue.startsWith('-')) {
|
||||
result.errors.push('Missing value for --target');
|
||||
} else {
|
||||
i += 1;
|
||||
const parsedTarget = parseTargetValue(rawValue);
|
||||
if (!parsedTarget) {
|
||||
result.errors.push(`Invalid --target value "${rawValue}". Use: claude or droid`);
|
||||
} else {
|
||||
result.target = parsedTarget;
|
||||
}
|
||||
}
|
||||
} else if (arg.startsWith('--target=')) {
|
||||
const rawValue = arg.slice('--target='.length);
|
||||
const parsedTarget = parseTargetValue(rawValue);
|
||||
if (!parsedTarget) {
|
||||
result.errors.push(`Invalid --target value "${rawValue}". Use: claude or droid`);
|
||||
} else {
|
||||
result.target = parsedTarget;
|
||||
}
|
||||
} else if (arg === '--force') {
|
||||
result.force = true;
|
||||
} else if (arg === '--yes' || arg === '-y') {
|
||||
@@ -145,6 +177,11 @@ export async function handleCreate(
|
||||
): Promise<void> {
|
||||
await initUI();
|
||||
const parsedArgs = parseProfileArgs(args);
|
||||
if (parsedArgs.errors.length > 0) {
|
||||
parsedArgs.errors.forEach((errorMessage) => console.log(fail(errorMessage)));
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
console.log(header(`Create ${getBackendLabel(backend)} Variant`));
|
||||
console.log('');
|
||||
|
||||
@@ -168,6 +205,17 @@ export async function handleCreate(
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let resolvedTarget: TargetType = parsedArgs.target || 'claude';
|
||||
if (!parsedArgs.target && !parsedArgs.yes) {
|
||||
const useDroidByDefault = await InteractivePrompt.confirm(
|
||||
'Set default target to Factory Droid for this variant?',
|
||||
{ default: false }
|
||||
);
|
||||
if (useDroidByDefault) {
|
||||
resolvedTarget = 'droid';
|
||||
}
|
||||
}
|
||||
|
||||
// Composite mode: select provider+model per tier
|
||||
if (parsedArgs.composite) {
|
||||
console.log(info('Composite variant — select provider and model for each tier'));
|
||||
@@ -203,6 +251,7 @@ export async function handleCreate(
|
||||
const result = createCompositeVariant({
|
||||
name,
|
||||
defaultTier,
|
||||
target: resolvedTarget,
|
||||
tiers: { opus, sonnet, haiku },
|
||||
});
|
||||
|
||||
@@ -217,7 +266,8 @@ export async function handleCreate(
|
||||
? `Opus: ${tiers.opus.provider} / ${tiers.opus.model}\n` +
|
||||
`Sonnet: ${tiers.sonnet.provider} / ${tiers.sonnet.model}\n` +
|
||||
`Haiku: ${tiers.haiku.provider} / ${tiers.haiku.model}\n` +
|
||||
`Default: ${defaultTier}`
|
||||
`Default: ${defaultTier}\n` +
|
||||
`Target: ${resolvedTarget}`
|
||||
: '';
|
||||
const portInfo = result.variant?.port ? `\nPort: ${result.variant.port}` : '';
|
||||
console.log(
|
||||
@@ -228,7 +278,24 @@ export async function handleCreate(
|
||||
);
|
||||
console.log('');
|
||||
console.log(header('Usage'));
|
||||
console.log(` ${color(`ccs ${name} "your prompt"`, 'command')}`);
|
||||
if (resolvedTarget === 'droid') {
|
||||
console.log(
|
||||
` ${color(`ccs ${name} "your prompt"`, 'command')} ${dim('# uses droid by default')}`
|
||||
);
|
||||
console.log(
|
||||
` ${color(`ccsd ${name} "your prompt"`, 'command')} ${dim('# explicit droid alias')}`
|
||||
);
|
||||
console.log(
|
||||
` ${color(`ccs ${name} --target claude "your prompt"`, 'command')} ${dim('# override to Claude')}`
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
` ${color(`ccs ${name} "your prompt"`, 'command')} ${dim('# uses claude by default')}`
|
||||
);
|
||||
console.log(
|
||||
` ${color(`ccs ${name} --target droid "your prompt"`, 'command')} ${dim('# run on droid for this call')}`
|
||||
);
|
||||
}
|
||||
console.log('');
|
||||
return;
|
||||
}
|
||||
@@ -350,7 +417,7 @@ export async function handleCreate(
|
||||
// Create variant
|
||||
console.log('');
|
||||
console.log(info(`Creating ${getBackendLabel(backend)} variant...`));
|
||||
const result = createVariant(name, provider, model, account);
|
||||
const result = createVariant(name, provider, model, account, resolvedTarget);
|
||||
|
||||
if (!result.success) {
|
||||
console.log(fail(`Failed to create variant: ${result.error}`));
|
||||
@@ -367,13 +434,30 @@ export async function handleCreate(
|
||||
const portInfo = result.variant?.port ? `Port: ${result.variant.port}\n` : '';
|
||||
console.log(
|
||||
infoBox(
|
||||
`Variant: ${name}\nProvider: ${provider}\nModel: ${model}\n${portInfo}${account ? `Account: ${account}\n` : ''}${isUnifiedMode() ? 'Config' : 'Settings'}: ${settingsDisplay}`,
|
||||
`Variant: ${name}\nProvider: ${provider}\nModel: ${model}\nTarget: ${resolvedTarget}\n${portInfo}${account ? `Account: ${account}\n` : ''}${isUnifiedMode() ? 'Config' : 'Settings'}: ${settingsDisplay}`,
|
||||
configType
|
||||
)
|
||||
);
|
||||
console.log('');
|
||||
console.log(header('Usage'));
|
||||
console.log(` ${color(`ccs ${name} "your prompt"`, 'command')}`);
|
||||
if (resolvedTarget === 'droid') {
|
||||
console.log(
|
||||
` ${color(`ccs ${name} "your prompt"`, 'command')} ${dim('# uses droid by default')}`
|
||||
);
|
||||
console.log(
|
||||
` ${color(`ccsd ${name} "your prompt"`, 'command')} ${dim('# explicit droid alias')}`
|
||||
);
|
||||
console.log(
|
||||
` ${color(`ccs ${name} --target claude "your prompt"`, 'command')} ${dim('# override to Claude')}`
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
` ${color(`ccs ${name} "your prompt"`, 'command')} ${dim('# uses claude by default')}`
|
||||
);
|
||||
console.log(
|
||||
` ${color(`ccs ${name} --target droid "your prompt"`, 'command')} ${dim('# run on droid for this call')}`
|
||||
);
|
||||
}
|
||||
console.log('');
|
||||
console.log(dim('To change model later:'));
|
||||
console.log(` ${color(`ccs ${name} --config`, 'command')}`);
|
||||
@@ -383,6 +467,11 @@ export async function handleCreate(
|
||||
export async function handleRemove(args: string[]): Promise<void> {
|
||||
await initUI();
|
||||
const parsedArgs = parseProfileArgs(args);
|
||||
if (parsedArgs.errors.length > 0) {
|
||||
parsedArgs.errors.forEach((errorMessage) => console.log(fail(errorMessage)));
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
const variants = listVariants();
|
||||
const variantNames = Object.keys(variants);
|
||||
|
||||
@@ -435,6 +524,7 @@ export async function handleRemove(args: string[]): Promise<void> {
|
||||
if (variant.port) {
|
||||
console.log(` Port: ${variant.port}`);
|
||||
}
|
||||
console.log(` Target: ${variant.target || 'claude'}`);
|
||||
console.log(` Settings: ${variant.settings || '-'}`);
|
||||
console.log('');
|
||||
|
||||
@@ -461,6 +551,11 @@ export async function handleEdit(
|
||||
): Promise<void> {
|
||||
await initUI();
|
||||
const parsedArgs = parseProfileArgs(args);
|
||||
if (parsedArgs.errors.length > 0) {
|
||||
parsedArgs.errors.forEach((errorMessage) => console.log(fail(errorMessage)));
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
const variants = listVariants();
|
||||
const variantNames = Object.keys(variants);
|
||||
|
||||
@@ -501,12 +596,14 @@ export async function handleEdit(
|
||||
|
||||
// If not composite, use existing updateVariant() flow (interactive prompts)
|
||||
if (variant.type !== 'composite') {
|
||||
const currentTarget: TargetType = variant.target || 'claude';
|
||||
console.log(header(`Edit Variant: ${name}`));
|
||||
console.log('');
|
||||
console.log(`Current provider: ${variant.provider}`);
|
||||
if (variant.model) {
|
||||
console.log(`Current model: ${variant.model}`);
|
||||
}
|
||||
console.log(`Current target: ${currentTarget}`);
|
||||
console.log('');
|
||||
|
||||
const changeProvider = await InteractivePrompt.confirm('Change provider?', { default: false });
|
||||
@@ -552,6 +649,22 @@ export async function handleEdit(
|
||||
}
|
||||
}
|
||||
|
||||
let newTarget: TargetType | undefined = parsedArgs.target;
|
||||
if (!parsedArgs.target) {
|
||||
const changeTarget = await InteractivePrompt.confirm('Change default target?', {
|
||||
default: false,
|
||||
});
|
||||
if (changeTarget) {
|
||||
const targetOptions = [
|
||||
{ id: 'claude', label: 'Claude Code' },
|
||||
{ id: 'droid', label: 'Factory Droid' },
|
||||
];
|
||||
newTarget = (await InteractivePrompt.selectFromList('Select target:', targetOptions, {
|
||||
defaultIndex: currentTarget === 'droid' ? 1 : 0,
|
||||
})) as TargetType;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('');
|
||||
console.log(info(`Updating ${getBackendLabel(backend)} variant...`));
|
||||
// Use existing updateVariant from variant-service for single-provider variants
|
||||
@@ -559,6 +672,7 @@ export async function handleEdit(
|
||||
const result = updateVariant(name, {
|
||||
provider: newProvider,
|
||||
model: changeModel ? newModel : undefined,
|
||||
target: newTarget,
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
@@ -566,13 +680,35 @@ export async function handleEdit(
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const resolvedTarget = result.variant?.target || currentTarget;
|
||||
console.log('');
|
||||
console.log(ok(`Variant updated: ${name}`));
|
||||
console.log('');
|
||||
console.log(header('Usage'));
|
||||
if (resolvedTarget === 'droid') {
|
||||
console.log(
|
||||
` ${color(`ccs ${name} "your prompt"`, 'command')} ${dim('# uses droid by default')}`
|
||||
);
|
||||
console.log(
|
||||
` ${color(`ccsd ${name} "your prompt"`, 'command')} ${dim('# explicit droid alias')}`
|
||||
);
|
||||
console.log(
|
||||
` ${color(`ccs ${name} --target claude "your prompt"`, 'command')} ${dim('# override to Claude')}`
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
` ${color(`ccs ${name} "your prompt"`, 'command')} ${dim('# uses claude by default')}`
|
||||
);
|
||||
console.log(
|
||||
` ${color(`ccs ${name} --target droid "your prompt"`, 'command')} ${dim('# run on droid for this call')}`
|
||||
);
|
||||
}
|
||||
console.log('');
|
||||
return;
|
||||
}
|
||||
|
||||
// Composite variant edit flow
|
||||
const compositeCurrentTarget: TargetType = variant.target || 'claude';
|
||||
console.log(header(`Edit Composite Variant: ${name}`));
|
||||
console.log('');
|
||||
if (!variant.tiers) {
|
||||
@@ -585,6 +721,7 @@ export async function handleEdit(
|
||||
console.log(` Sonnet: ${variant.tiers.sonnet.provider} / ${variant.tiers.sonnet.model}`);
|
||||
console.log(` Haiku: ${variant.tiers.haiku.provider} / ${variant.tiers.haiku.model}`);
|
||||
console.log(` Default: ${variant.default_tier}`);
|
||||
console.log(` Target: ${compositeCurrentTarget}`);
|
||||
console.log('');
|
||||
|
||||
const verbose = args.includes('--verbose');
|
||||
@@ -634,11 +771,32 @@ export async function handleEdit(
|
||||
)) as 'opus' | 'sonnet' | 'haiku';
|
||||
}
|
||||
|
||||
let newCompositeTarget: TargetType | undefined = parsedArgs.target;
|
||||
if (!parsedArgs.target) {
|
||||
const changeTarget = await InteractivePrompt.confirm('Change default target?', {
|
||||
default: false,
|
||||
});
|
||||
if (changeTarget) {
|
||||
const targetOptions = [
|
||||
{ id: 'claude', label: 'Claude Code' },
|
||||
{ id: 'droid', label: 'Factory Droid' },
|
||||
];
|
||||
newCompositeTarget = (await InteractivePrompt.selectFromList(
|
||||
'Select target:',
|
||||
targetOptions,
|
||||
{
|
||||
defaultIndex: compositeCurrentTarget === 'droid' ? 1 : 0,
|
||||
}
|
||||
)) as TargetType;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('');
|
||||
console.log(info(`Updating composite ${getBackendLabel(backend)} variant...`));
|
||||
const result = updateCompositeVariant(name, {
|
||||
tiers: updatedTiers,
|
||||
defaultTier: changeDefault ? newDefaultTier : undefined,
|
||||
target: newCompositeTarget,
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
@@ -653,7 +811,8 @@ export async function handleEdit(
|
||||
`Opus: ${finalVariant.tiers.opus.provider} / ${finalVariant.tiers.opus.model}\n` +
|
||||
`Sonnet: ${finalVariant.tiers.sonnet.provider} / ${finalVariant.tiers.sonnet.model}\n` +
|
||||
`Haiku: ${finalVariant.tiers.haiku.provider} / ${finalVariant.tiers.haiku.model}\n` +
|
||||
`Default: ${finalVariant.default_tier}`;
|
||||
`Default: ${finalVariant.default_tier}\n` +
|
||||
`Target: ${finalVariant.target || compositeCurrentTarget}`;
|
||||
const portInfo = finalVariant.port ? `\nPort: ${finalVariant.port}` : '';
|
||||
console.log(
|
||||
infoBox(
|
||||
|
||||
Reference in New Issue
Block a user