mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-12 16:15:13 +00:00
fix(cli): clearer profile/proxy/api errors and rich not-found suggestions
Move resolveProfileAndTarget() inside the try block in ccs.ts so a missing profile shows suggestions/available-profiles instead of a generic error. Use a typed ProfileError for proxy start on an unknown profile (drops the irrelevant install hint). api create: honor --yes for the OpenRouter model browser, show ccs-codex/ccsx aliases for --target codex, and allow ANTHROPIC_EXTRA_MODELS on import.
This commit is contained in:
1 parent
191396149f
commit
9258aed1ff
4 files changed
+68
-34
No files matched your search
@@ -23,6 +23,8 @@ const ALLOWED_ANTHROPIC_ENV_KEYS = new Set<string>([
|
|||||||
'ANTHROPIC_BASE_URL',
|
'ANTHROPIC_BASE_URL',
|
||||||
'ANTHROPIC_AUTH_TOKEN',
|
'ANTHROPIC_AUTH_TOKEN',
|
||||||
'ANTHROPIC_API_KEY',
|
'ANTHROPIC_API_KEY',
|
||||||
|
// Written by profile-writer when --extra-models is supplied
|
||||||
|
'ANTHROPIC_EXTRA_MODELS',
|
||||||
...MODEL_ENV_KEYS,
|
...MODEL_ENV_KEYS,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
+30
-24
@@ -46,31 +46,37 @@ async function main(): Promise<void> {
|
|||||||
return;
|
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 {
|
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)
|
// Special case: headless delegation (-p/--prompt)
|
||||||
// Keep existing behavior for Claude targets only; non-claude targets must continue
|
// Keep existing behavior for Claude targets only; non-claude targets must continue
|
||||||
// through normal adapter dispatch logic.
|
// through normal adapter dispatch logic.
|
||||||
|
|||||||
@@ -196,14 +196,17 @@ async function resolveModelConfiguration(
|
|||||||
if (isOpenRouterUrl(baseUrl) && !providedModel) {
|
if (isOpenRouterUrl(baseUrl) && !providedModel) {
|
||||||
console.log('');
|
console.log('');
|
||||||
console.log(info('OpenRouter detected!'));
|
console.log(info('OpenRouter detected!'));
|
||||||
const useInteractive = await InteractivePrompt.confirm('Browse models interactively?', {
|
// --yes/-y suppresses interactive model browser; use preset defaultModel instead
|
||||||
default: true,
|
if (!yes) {
|
||||||
});
|
const useInteractive = await InteractivePrompt.confirm('Browse models interactively?', {
|
||||||
if (useInteractive) {
|
default: true,
|
||||||
const selection = await pickOpenRouterModel();
|
});
|
||||||
if (selection) {
|
if (useInteractive) {
|
||||||
openRouterModel = selection.model;
|
const selection = await pickOpenRouterModel();
|
||||||
openRouterTierMapping = selection.tierMapping;
|
if (selection) {
|
||||||
|
openRouterModel = selection.model;
|
||||||
|
openRouterTierMapping = selection.tierMapping;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log('');
|
console.log('');
|
||||||
@@ -571,6 +574,17 @@ export async function handleApiCreateCommand(args: string[]): Promise<void> {
|
|||||||
console.log(
|
console.log(
|
||||||
` ${color(`ccs ${name} --target claude "your prompt"`, 'command')} ${dim('# override to Claude')}`
|
` ${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 {
|
} else {
|
||||||
console.log(
|
console.log(
|
||||||
` ${color(`ccs ${name} "your prompt"`, 'command')} ${dim('# uses claude by default')}`
|
` ${color(`ccs ${name} "your prompt"`, 'command')} ${dim('# uses claude by default')}`
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ import {
|
|||||||
startOpenAICompatProxy,
|
startOpenAICompatProxy,
|
||||||
stopOpenAICompatProxy,
|
stopOpenAICompatProxy,
|
||||||
} from '../proxy';
|
} 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 {
|
function parseOptionValue(args: string[], key: string): string | undefined {
|
||||||
const exactIndex = args.findIndex((arg) => arg === key);
|
const exactIndex = args.findIndex((arg) => arg === key);
|
||||||
@@ -82,11 +83,22 @@ function showHelp(): number {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function resolveProfile(profileName: string) {
|
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 settingsPath = expandPath(getSettingsPath(profileName));
|
||||||
const settings = loadSettings(settingsPath);
|
const settings = loadSettings(settingsPath);
|
||||||
const profile = resolveOpenAICompatProfileConfig(profileName, settingsPath, settings.env || {});
|
const profile = resolveOpenAICompatProfileConfig(profileName, settingsPath, settings.env || {});
|
||||||
if (!profile) {
|
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;
|
return profile;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user