diff --git a/src/api/services/profile-lifecycle-validation.ts b/src/api/services/profile-lifecycle-validation.ts index 7ff09654..d7902584 100644 --- a/src/api/services/profile-lifecycle-validation.ts +++ b/src/api/services/profile-lifecycle-validation.ts @@ -23,6 +23,8 @@ const ALLOWED_ANTHROPIC_ENV_KEYS = new Set([ 'ANTHROPIC_BASE_URL', 'ANTHROPIC_AUTH_TOKEN', 'ANTHROPIC_API_KEY', + // Written by profile-writer when --extra-models is supplied + 'ANTHROPIC_EXTRA_MODELS', ...MODEL_ENV_KEYS, ]); diff --git a/src/ccs.ts b/src/ccs.ts index 28a89ec3..e8fe5996 100644 --- a/src/ccs.ts +++ b/src/ccs.ts @@ -46,31 +46,37 @@ async function main(): Promise { return; } - // Phase C: profile + target detection (extracted to dispatcher/profile-resolver.ts) - const resolvedProfile = await resolveProfileAndTarget({ args, browserLaunchOverride, cliLogger }); - const { profile, profileInfo, resolvedTarget, nativeClaudeRemainingArgs } = resolvedProfile; - - // Dynamic imports needed by Phase E flows — preserve original load ordering. - const InstanceManagerModule = await import('./management/instance-manager'); - const InstanceManager = InstanceManagerModule.default; - const ProfileRegistryModule = await import('./auth/profile-registry'); - const ProfileRegistry = ProfileRegistryModule.default; - const AccountContextModule = await import('./auth/account-context'); - const { resolveAccountContextPolicy, isAccountContextMetadata } = AccountContextModule; - const ProfileContinuityModule = await import('./auth/profile-continuity-inheritance'); - const { resolveProfileContinuityInheritance } = ProfileContinuityModule; - - // Build full dispatch context (Phase E) - const dispatchCtx = { - ...resolvedProfile, - InstanceManager, - ProfileRegistry, - resolveAccountContextPolicy, - isAccountContextMetadata, - resolveProfileContinuityInheritance, - }; - try { + // Phase C: profile + target detection — inside try so ProfileNotFoundError + // reaches the rich showProfileNotFound() handler below instead of falling + // through to the bare unhandledRejection handler. + const resolvedProfile = await resolveProfileAndTarget({ + args, + browserLaunchOverride, + cliLogger, + }); + const { profile, profileInfo, resolvedTarget, nativeClaudeRemainingArgs } = resolvedProfile; + + // Dynamic imports needed by Phase E flows — preserve original load ordering. + const InstanceManagerModule = await import('./management/instance-manager'); + const InstanceManager = InstanceManagerModule.default; + const ProfileRegistryModule = await import('./auth/profile-registry'); + const ProfileRegistry = ProfileRegistryModule.default; + const AccountContextModule = await import('./auth/account-context'); + const { resolveAccountContextPolicy, isAccountContextMetadata } = AccountContextModule; + const ProfileContinuityModule = await import('./auth/profile-continuity-inheritance'); + const { resolveProfileContinuityInheritance } = ProfileContinuityModule; + + // Build full dispatch context (Phase E) + const dispatchCtx = { + ...resolvedProfile, + InstanceManager, + ProfileRegistry, + resolveAccountContextPolicy, + isAccountContextMetadata, + resolveProfileContinuityInheritance, + }; + // Special case: headless delegation (-p/--prompt) // Keep existing behavior for Claude targets only; non-claude targets must continue // through normal adapter dispatch logic. diff --git a/src/commands/api-command/create-command.ts b/src/commands/api-command/create-command.ts index b0d799f8..77b585f3 100644 --- a/src/commands/api-command/create-command.ts +++ b/src/commands/api-command/create-command.ts @@ -196,14 +196,17 @@ async function resolveModelConfiguration( if (isOpenRouterUrl(baseUrl) && !providedModel) { console.log(''); console.log(info('OpenRouter detected!')); - const useInteractive = await InteractivePrompt.confirm('Browse models interactively?', { - default: true, - }); - if (useInteractive) { - const selection = await pickOpenRouterModel(); - if (selection) { - openRouterModel = selection.model; - openRouterTierMapping = selection.tierMapping; + // --yes/-y suppresses interactive model browser; use preset defaultModel instead + if (!yes) { + const useInteractive = await InteractivePrompt.confirm('Browse models interactively?', { + default: true, + }); + if (useInteractive) { + const selection = await pickOpenRouterModel(); + if (selection) { + openRouterModel = selection.model; + openRouterTierMapping = selection.tierMapping; + } } } console.log(''); @@ -571,6 +574,17 @@ export async function handleApiCreateCommand(args: string[]): Promise { console.log( ` ${color(`ccs ${name} --target claude "your prompt"`, 'command')} ${dim('# override to Claude')}` ); + } else if (target === 'codex') { + console.log( + ` ${color(`ccs ${name} "your prompt"`, 'command')} ${dim('# uses Codex CLI by default')}` + ); + console.log( + ` ${color(`ccs-codex ${name} "your prompt"`, 'command')} ${dim('# explicit Codex alias')}` + ); + console.log(` ${color(`ccsx ${name} "your prompt"`, 'command')} ${dim('# short 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')}` diff --git a/src/commands/proxy-command.ts b/src/commands/proxy-command.ts index 53a11b11..ba066702 100644 --- a/src/commands/proxy-command.ts +++ b/src/commands/proxy-command.ts @@ -10,7 +10,8 @@ import { startOpenAICompatProxy, stopOpenAICompatProxy, } from '../proxy'; -import { loadSettings } from '../config/config-loader-facade'; +import { loadConfigSafe, loadSettings } from '../config/config-loader-facade'; +import { ProfileError } from '../errors'; function parseOptionValue(args: string[], key: string): string | undefined { const exactIndex = args.findIndex((arg) => arg === key); @@ -82,11 +83,22 @@ function showHelp(): number { } function resolveProfile(profileName: string) { + // Pre-check profile in config to avoid the generic error() + npm-install hint from getSettingsPath. + const config = loadConfigSafe(); + if (!config.profiles[profileName]) { + throw new ProfileError( + `Profile "${profileName}" is not configured for an OpenAI-compatible endpoint`, + profileName + ); + } const settingsPath = expandPath(getSettingsPath(profileName)); const settings = loadSettings(settingsPath); const profile = resolveOpenAICompatProfileConfig(profileName, settingsPath, settings.env || {}); if (!profile) { - throw new Error(`Profile "${profileName}" is not configured for an OpenAI-compatible endpoint`); + throw new ProfileError( + `Profile "${profileName}" is not configured for an OpenAI-compatible endpoint`, + profileName + ); } return profile; }