mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 00:22:34 +00:00
refactor(dispatcher): scaffold src/dispatcher/ and extract pure helpers from ccs.ts
Phase 1 of #1165. Pure code-motion: moves top-level helpers out of the 1775-LOC CLI entry point into focused dispatcher modules. Zero behavior change, zero main() changes. - src/dispatcher/cli-argument-parser.ts (199 LOC): DetectedProfile, RuntimeReasoningResolution, NATIVE_CLAUDE_EFFORT constants, detectProfile, normalizeLegacyCursorArgs, printCursorLegacySubcommandDeprecation, resolveRuntimeReasoningFlags, normalizeCodexRuntimeReasoningOverride, exitWithRuntimeReasoningFlagError, normalizeNativeClaudeEffortArgs, shouldNormalizeNativeClaudeEffort, shouldPassthroughNativeCodexFlagCommand, getNativeCodexPassthroughArgs. - src/dispatcher/environment-builder.ts (125 LOC): resolveCodexRuntimeConfigOverrides, refreshUpdateCache, showCachedUpdateNotification, resolveNativeClaudeLaunchArgs. - src/dispatcher/target-executor.ts (56 LOC): ProfileError, execNativeCodexFlagCommand. ccs.ts: 1775 -> 1475 LOC (-300). Cleaned up 8 now-orphaned imports. Behavior unchanged; full suite passes 1824/1824. Refs #1165
This commit is contained in:
+22
-322
@@ -46,11 +46,7 @@ import {
|
||||
resolveOptionalBrowserAttachRuntime,
|
||||
syncBrowserMcpToConfigDir,
|
||||
} from './utils/browser';
|
||||
import {
|
||||
getBrowserConfig,
|
||||
getGlobalEnvConfig,
|
||||
getOfficialChannelsConfig,
|
||||
} from './config/unified-config-loader';
|
||||
import { getBrowserConfig, getGlobalEnvConfig } from './config/unified-config-loader';
|
||||
import {
|
||||
ensureProfileHooks as ensureImageAnalyzerHooks,
|
||||
removeImageAnalysisProfileHook,
|
||||
@@ -65,13 +61,6 @@ import {
|
||||
} from './utils/hooks';
|
||||
import { fail, info, warn } from './utils/ui';
|
||||
import { isCopilotSubcommandToken } from './copilot/constants';
|
||||
import {
|
||||
buildOfficialChannelsArgs,
|
||||
getOfficialChannelsEnvironmentStatus,
|
||||
officialChannelRequiresMacOS,
|
||||
resolveOfficialChannelsLaunchPlan,
|
||||
} from './channels/official-channels-runtime';
|
||||
import { getOfficialChannelReadiness } from './channels/official-channels-store';
|
||||
import { isCursorSubcommandToken, LEGACY_CURSOR_PROFILE_NAME } from './cursor/constants';
|
||||
import { isCLIProxyProvider } from './cliproxy/provider-capabilities';
|
||||
|
||||
@@ -85,7 +74,6 @@ import { isDeprecatedGlmtProfileName, normalizeDeprecatedGlmtEnv } from './utils
|
||||
import { createOpenAICompatLaunchSettings } from './utils/openai-compat-launch-settings';
|
||||
import { maybeWarnAboutResumeLaneMismatch } from './auth/resume-lane-warning';
|
||||
import { createLogger, runWithRequestId } from './services/logging';
|
||||
import { buildCodexBrowserMcpOverrides } from './utils/browser-codex-overrides';
|
||||
import type { ProfileDetectionResult } from './auth/profile-detector';
|
||||
import type { BrowserLaunchOverride } from './utils/browser';
|
||||
|
||||
@@ -102,10 +90,7 @@ import {
|
||||
type TargetCredentials,
|
||||
} from './targets';
|
||||
import { resolveTargetType, stripTargetFlag } from './targets/target-resolver';
|
||||
import {
|
||||
DroidReasoningFlagError,
|
||||
resolveDroidReasoningRuntime,
|
||||
} from './targets/droid-reasoning-runtime';
|
||||
import { DroidReasoningFlagError } from './targets/droid-reasoning-runtime';
|
||||
import { DroidCommandRouterError, routeDroidCommandArgs } from './targets/droid-command-router';
|
||||
import { resolveCliproxyBridgeMetadata } from './api/services/cliproxy-profile-bridge';
|
||||
import {
|
||||
@@ -115,316 +100,31 @@ import {
|
||||
} from './proxy';
|
||||
|
||||
// Version and Update check utilities
|
||||
import { getVersion } from './utils/version';
|
||||
import {
|
||||
checkForUpdates,
|
||||
showUpdateNotification,
|
||||
checkCachedUpdate,
|
||||
isCacheStale,
|
||||
} from './utils/update-checker';
|
||||
import { isCacheStale } from './utils/update-checker';
|
||||
// Note: npm is now the only supported installation method
|
||||
|
||||
// ========== Profile Detection ==========
|
||||
|
||||
interface DetectedProfile {
|
||||
profile: string;
|
||||
remainingArgs: string[];
|
||||
}
|
||||
|
||||
interface RuntimeReasoningResolution {
|
||||
argsWithoutReasoningFlags: string[];
|
||||
reasoningOverride: string | number | undefined;
|
||||
reasoningSource: 'flag' | 'env' | undefined;
|
||||
sourceDisplay: string | undefined;
|
||||
}
|
||||
|
||||
const CODEX_RUNTIME_REASONING_LEVELS = new Set(['minimal', 'low', 'medium', 'high', 'xhigh']);
|
||||
const CODEX_NATIVE_PASSTHROUGH_FLAGS = new Set(['--help', '-h', '--version', '-v']);
|
||||
const NATIVE_CLAUDE_EFFORT_LEVELS = ['low', 'medium', 'high', 'xhigh', 'max'] as const;
|
||||
const NATIVE_CLAUDE_EFFORT_LEVEL_SET = new Set<string>(NATIVE_CLAUDE_EFFORT_LEVELS);
|
||||
|
||||
function resolveCodexRuntimeConfigOverrides(
|
||||
target: ReturnType<typeof resolveTargetType>,
|
||||
browserLaunchOverride: BrowserLaunchOverride | undefined
|
||||
): string[] {
|
||||
if (target !== 'codex') {
|
||||
return [];
|
||||
}
|
||||
|
||||
const codexBrowserExposure = resolveBrowserExposure(
|
||||
getBrowserConfig().codex,
|
||||
browserLaunchOverride
|
||||
);
|
||||
if (!codexBrowserExposure.exposeForLaunch) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return buildCodexBrowserMcpOverrides();
|
||||
}
|
||||
|
||||
/**
|
||||
* Smart profile detection
|
||||
*/
|
||||
function detectProfile(args: string[]): DetectedProfile {
|
||||
if (args.length === 0 || args[0].startsWith('-')) {
|
||||
// No args or first arg is a flag → use default profile
|
||||
return { profile: 'default', remainingArgs: args };
|
||||
} else {
|
||||
// First arg doesn't start with '-' → treat as profile name
|
||||
return { profile: args[0], remainingArgs: args.slice(1) };
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeLegacyCursorArgs(args: string[]): string[] {
|
||||
if (args[0] === 'legacy' && args[1] === 'cursor') {
|
||||
return [LEGACY_CURSOR_PROFILE_NAME, ...args.slice(2)];
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
function printCursorLegacySubcommandDeprecation(subcommand: string): void {
|
||||
console.error(
|
||||
info(`\`ccs cursor ${subcommand}\` is deprecated for the legacy Cursor IDE bridge.`)
|
||||
);
|
||||
console.error(
|
||||
info(
|
||||
`Use \`ccs legacy cursor ${subcommand}\` for the old bridge, or \`ccs cursor --auth|--accounts|--config\` for the CLIProxy provider.`
|
||||
)
|
||||
);
|
||||
console.error('');
|
||||
}
|
||||
|
||||
function resolveRuntimeReasoningFlags(
|
||||
args: string[],
|
||||
envThinkingValue: string | undefined
|
||||
): RuntimeReasoningResolution {
|
||||
const runtime = resolveDroidReasoningRuntime(args, envThinkingValue);
|
||||
|
||||
if (runtime.duplicateDisplays.length > 0) {
|
||||
console.error(
|
||||
warn(
|
||||
`[!] Multiple reasoning flags detected. Using first occurrence: ${runtime.sourceDisplay || '<first-flag>'}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
argsWithoutReasoningFlags: runtime.argsWithoutReasoningFlags,
|
||||
reasoningOverride: runtime.reasoningOverride,
|
||||
reasoningSource: runtime.sourceFlag
|
||||
? 'flag'
|
||||
: runtime.reasoningOverride !== undefined
|
||||
? 'env'
|
||||
: undefined,
|
||||
sourceDisplay: runtime.sourceDisplay,
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeCodexRuntimeReasoningOverride(
|
||||
value: string | number | undefined
|
||||
): string | undefined {
|
||||
return typeof value === 'string' && CODEX_RUNTIME_REASONING_LEVELS.has(value) ? value : undefined;
|
||||
}
|
||||
|
||||
function exitWithRuntimeReasoningFlagError(
|
||||
message: string,
|
||||
options: {
|
||||
codexAliasLevels: string;
|
||||
includeDroidExecExample?: boolean;
|
||||
}
|
||||
): never {
|
||||
console.error(fail(message));
|
||||
console.error(' Examples: --thinking low, --thinking 8192, --thinking off');
|
||||
console.error(` Codex alias: --effort ${options.codexAliasLevels}`);
|
||||
if (options.includeDroidExecExample) {
|
||||
console.error(' Droid exec: --reasoning-effort high');
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function normalizeNativeClaudeEffortArgs(args: string[]): string[] {
|
||||
const normalizedArgs: string[] = [];
|
||||
const allowedValues = NATIVE_CLAUDE_EFFORT_LEVELS.join(', ');
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i];
|
||||
if (arg === '--effort') {
|
||||
const rawValue = args[i + 1];
|
||||
if (!rawValue || rawValue.startsWith('-') || !rawValue.trim()) {
|
||||
throw new Error(`--effort requires a value: ${allowedValues}`);
|
||||
}
|
||||
const value = rawValue.toLowerCase();
|
||||
if (!NATIVE_CLAUDE_EFFORT_LEVEL_SET.has(value)) {
|
||||
throw new Error(`Invalid --effort value: ${rawValue}. Expected one of: ${allowedValues}.`);
|
||||
}
|
||||
normalizedArgs.push(arg, value);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg.startsWith('--effort=')) {
|
||||
const rawValue = arg.slice('--effort='.length);
|
||||
if (!rawValue.trim()) {
|
||||
throw new Error(`--effort requires a value: ${allowedValues}`);
|
||||
}
|
||||
const value = rawValue.toLowerCase();
|
||||
if (!NATIVE_CLAUDE_EFFORT_LEVEL_SET.has(value)) {
|
||||
throw new Error(`Invalid --effort value: ${rawValue}. Expected one of: ${allowedValues}.`);
|
||||
}
|
||||
normalizedArgs.push(`--effort=${value}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
normalizedArgs.push(arg);
|
||||
}
|
||||
|
||||
return normalizedArgs;
|
||||
}
|
||||
|
||||
function shouldNormalizeNativeClaudeEffort(profileType: ProfileDetectionResult['type']): boolean {
|
||||
return profileType === 'default' || profileType === 'account' || profileType === 'settings';
|
||||
}
|
||||
// Import extracted dispatcher modules
|
||||
import {
|
||||
detectProfile,
|
||||
normalizeLegacyCursorArgs,
|
||||
printCursorLegacySubcommandDeprecation,
|
||||
resolveRuntimeReasoningFlags,
|
||||
normalizeCodexRuntimeReasoningOverride,
|
||||
exitWithRuntimeReasoningFlagError,
|
||||
normalizeNativeClaudeEffortArgs,
|
||||
shouldNormalizeNativeClaudeEffort,
|
||||
shouldPassthroughNativeCodexFlagCommand,
|
||||
} from './dispatcher/cli-argument-parser';
|
||||
import {
|
||||
resolveCodexRuntimeConfigOverrides,
|
||||
refreshUpdateCache,
|
||||
showCachedUpdateNotification,
|
||||
resolveNativeClaudeLaunchArgs,
|
||||
} from './dispatcher/environment-builder';
|
||||
import { type ProfileError, execNativeCodexFlagCommand } from './dispatcher/target-executor';
|
||||
|
||||
// ========== Main Execution ==========
|
||||
|
||||
interface ProfileError extends Error {
|
||||
profileName?: string;
|
||||
availableProfiles?: string;
|
||||
suggestions?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform background update check (refreshes cache, no notification)
|
||||
*/
|
||||
async function refreshUpdateCache(): Promise<void> {
|
||||
try {
|
||||
const currentVersion = getVersion();
|
||||
// npm is now the only supported installation method
|
||||
await checkForUpdates(currentVersion, true, 'npm');
|
||||
} catch (_e) {
|
||||
// Silently fail - update check shouldn't crash main CLI
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show update notification if cached result indicates update available
|
||||
* Returns true if notification was shown
|
||||
*/
|
||||
async function showCachedUpdateNotification(): Promise<boolean> {
|
||||
try {
|
||||
const currentVersion = getVersion();
|
||||
const updateInfo = checkCachedUpdate(currentVersion);
|
||||
|
||||
if (updateInfo) {
|
||||
await showUpdateNotification(updateInfo);
|
||||
return true;
|
||||
}
|
||||
} catch (_e) {
|
||||
// Silently fail
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function resolveNativeClaudeLaunchArgs(
|
||||
args: string[],
|
||||
profileType: 'default' | 'account',
|
||||
targetConfigDir?: string
|
||||
): string[] {
|
||||
const config = getOfficialChannelsConfig();
|
||||
const environment = getOfficialChannelsEnvironmentStatus(
|
||||
targetConfigDir ? { CLAUDE_CONFIG_DIR: targetConfigDir } : undefined
|
||||
);
|
||||
const channelReadiness = {
|
||||
telegram: getOfficialChannelReadiness('telegram'),
|
||||
discord: getOfficialChannelReadiness('discord'),
|
||||
imessage: !officialChannelRequiresMacOS('imessage') || process.platform === 'darwin',
|
||||
};
|
||||
const plan = resolveOfficialChannelsLaunchPlan({
|
||||
args,
|
||||
config,
|
||||
target: 'claude',
|
||||
profileType,
|
||||
environment,
|
||||
channelReadiness,
|
||||
});
|
||||
|
||||
for (const message of plan.skippedMessages) {
|
||||
console.error(warn(message));
|
||||
}
|
||||
|
||||
if (
|
||||
config.selected.length > 0 &&
|
||||
environment.auth.state === 'eligible' &&
|
||||
environment.auth.orgRequirementMessage
|
||||
) {
|
||||
console.error(warn(environment.auth.orgRequirementMessage));
|
||||
}
|
||||
|
||||
if (!plan.applied) {
|
||||
return args;
|
||||
}
|
||||
|
||||
return buildOfficialChannelsArgs(args, plan.appliedChannels, plan.wantsPermissionBypass);
|
||||
}
|
||||
|
||||
function shouldPassthroughNativeCodexFlagCommand(args: string[]): boolean {
|
||||
return getNativeCodexPassthroughArgs(args) !== null;
|
||||
}
|
||||
|
||||
function getNativeCodexPassthroughArgs(args: string[]): string[] | null {
|
||||
const targetArgs = stripTargetFlag(args);
|
||||
if (resolveTargetType(args) !== 'codex' || targetArgs.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const firstArg = targetArgs[0] || '';
|
||||
if (CODEX_NATIVE_PASSTHROUGH_FLAGS.has(firstArg)) {
|
||||
return targetArgs;
|
||||
}
|
||||
|
||||
const secondArg = targetArgs[1] || '';
|
||||
if (firstArg === 'codex' && CODEX_NATIVE_PASSTHROUGH_FLAGS.has(secondArg)) {
|
||||
return targetArgs.slice(1);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function execNativeCodexFlagCommand(args: string[]): void {
|
||||
const adapter = getTarget('codex');
|
||||
if (!adapter) {
|
||||
console.error(fail('Target adapter not found for "codex"'));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const binaryInfo = adapter.detectBinary();
|
||||
if (!binaryInfo) {
|
||||
console.error(fail('Codex CLI not found.'));
|
||||
console.error(info('Install a recent @openai/codex build, then retry.'));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const targetArgs = getNativeCodexPassthroughArgs(args);
|
||||
if (!targetArgs) {
|
||||
console.error(fail('Native Codex passthrough args could not be resolved.'));
|
||||
process.exit(1);
|
||||
}
|
||||
const creds: TargetCredentials = {
|
||||
profile: 'default',
|
||||
baseUrl: '',
|
||||
apiKey: '',
|
||||
};
|
||||
|
||||
const builtArgs = adapter.buildArgs('default', targetArgs, {
|
||||
creds,
|
||||
profileType: 'default',
|
||||
binaryInfo,
|
||||
});
|
||||
const targetEnv = adapter.buildEnv(creds, 'default');
|
||||
adapter.exec(builtArgs, targetEnv, { binaryInfo });
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
// Register target adapters
|
||||
registerTarget(new ClaudeAdapter());
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
/**
|
||||
* CLI argument parsing and normalization utilities.
|
||||
*
|
||||
* Extracted from src/ccs.ts (lines 129-244, 246-296, 371-392).
|
||||
* Pure functions — no side effects except console.error and process.exit.
|
||||
*/
|
||||
|
||||
import { fail, warn } from '../utils/ui';
|
||||
import { LEGACY_CURSOR_PROFILE_NAME } from '../cursor/constants';
|
||||
import { resolveTargetType, stripTargetFlag } from '../targets/target-resolver';
|
||||
import { resolveDroidReasoningRuntime } from '../targets/droid-reasoning-runtime';
|
||||
import type { ProfileDetectionResult } from '../auth/profile-detector';
|
||||
|
||||
// ========== Interfaces ==========
|
||||
|
||||
export interface DetectedProfile {
|
||||
profile: string;
|
||||
remainingArgs: string[];
|
||||
}
|
||||
|
||||
export interface RuntimeReasoningResolution {
|
||||
argsWithoutReasoningFlags: string[];
|
||||
reasoningOverride: string | number | undefined;
|
||||
reasoningSource: 'flag' | 'env' | undefined;
|
||||
sourceDisplay: string | undefined;
|
||||
}
|
||||
|
||||
// ========== Constants ==========
|
||||
|
||||
export const CODEX_RUNTIME_REASONING_LEVELS = new Set([
|
||||
'minimal',
|
||||
'low',
|
||||
'medium',
|
||||
'high',
|
||||
'xhigh',
|
||||
]);
|
||||
|
||||
export const CODEX_NATIVE_PASSTHROUGH_FLAGS = new Set(['--help', '-h', '--version', '-v']);
|
||||
|
||||
export const NATIVE_CLAUDE_EFFORT_LEVELS = ['low', 'medium', 'high', 'xhigh', 'max'] as const;
|
||||
|
||||
export const NATIVE_CLAUDE_EFFORT_LEVEL_SET = new Set<string>(NATIVE_CLAUDE_EFFORT_LEVELS);
|
||||
|
||||
// ========== Profile Detection ==========
|
||||
|
||||
/**
|
||||
* Smart profile detection — first non-flag arg is the profile name.
|
||||
*/
|
||||
export function detectProfile(args: string[]): DetectedProfile {
|
||||
if (args.length === 0 || args[0].startsWith('-')) {
|
||||
// No args or first arg is a flag → use default profile
|
||||
return { profile: 'default', remainingArgs: args };
|
||||
} else {
|
||||
// First arg doesn't start with '-' → treat as profile name
|
||||
return { profile: args[0], remainingArgs: args.slice(1) };
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeLegacyCursorArgs(args: string[]): string[] {
|
||||
if (args[0] === 'legacy' && args[1] === 'cursor') {
|
||||
return [LEGACY_CURSOR_PROFILE_NAME, ...args.slice(2)];
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
export function printCursorLegacySubcommandDeprecation(subcommand: string): void {
|
||||
console.error(
|
||||
warn(`\`ccs cursor ${subcommand}\` is deprecated for the legacy Cursor IDE bridge.`)
|
||||
);
|
||||
console.error(
|
||||
warn(
|
||||
`Use \`ccs legacy cursor ${subcommand}\` for the old bridge, or \`ccs cursor --auth|--accounts|--config\` for the CLIProxy provider.`
|
||||
)
|
||||
);
|
||||
console.error('');
|
||||
}
|
||||
|
||||
// ========== Runtime Reasoning Flags ==========
|
||||
|
||||
export function resolveRuntimeReasoningFlags(
|
||||
args: string[],
|
||||
envThinkingValue: string | undefined
|
||||
): RuntimeReasoningResolution {
|
||||
const runtime = resolveDroidReasoningRuntime(args, envThinkingValue);
|
||||
|
||||
if (runtime.duplicateDisplays.length > 0) {
|
||||
console.error(
|
||||
warn(
|
||||
`[!] Multiple reasoning flags detected. Using first occurrence: ${runtime.sourceDisplay || '<first-flag>'}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
argsWithoutReasoningFlags: runtime.argsWithoutReasoningFlags,
|
||||
reasoningOverride: runtime.reasoningOverride,
|
||||
reasoningSource: runtime.sourceFlag
|
||||
? 'flag'
|
||||
: runtime.reasoningOverride !== undefined
|
||||
? 'env'
|
||||
: undefined,
|
||||
sourceDisplay: runtime.sourceDisplay,
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeCodexRuntimeReasoningOverride(
|
||||
value: string | number | undefined
|
||||
): string | undefined {
|
||||
return typeof value === 'string' && CODEX_RUNTIME_REASONING_LEVELS.has(value) ? value : undefined;
|
||||
}
|
||||
|
||||
export function exitWithRuntimeReasoningFlagError(
|
||||
message: string,
|
||||
options: {
|
||||
codexAliasLevels: string;
|
||||
includeDroidExecExample?: boolean;
|
||||
}
|
||||
): never {
|
||||
console.error(fail(message));
|
||||
console.error(' Examples: --thinking low, --thinking 8192, --thinking off');
|
||||
console.error(` Codex alias: --effort ${options.codexAliasLevels}`);
|
||||
if (options.includeDroidExecExample) {
|
||||
console.error(' Droid exec: --reasoning-effort high');
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// ========== Native Claude Effort Normalization ==========
|
||||
|
||||
export function normalizeNativeClaudeEffortArgs(args: string[]): string[] {
|
||||
const normalizedArgs: string[] = [];
|
||||
const allowedValues = NATIVE_CLAUDE_EFFORT_LEVELS.join(', ');
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i];
|
||||
if (arg === '--effort') {
|
||||
const rawValue = args[i + 1];
|
||||
if (!rawValue || rawValue.startsWith('-') || !rawValue.trim()) {
|
||||
throw new Error(`--effort requires a value: ${allowedValues}`);
|
||||
}
|
||||
const value = rawValue.toLowerCase();
|
||||
if (!NATIVE_CLAUDE_EFFORT_LEVEL_SET.has(value)) {
|
||||
throw new Error(`Invalid --effort value: ${rawValue}. Expected one of: ${allowedValues}.`);
|
||||
}
|
||||
normalizedArgs.push(arg, value);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg.startsWith('--effort=')) {
|
||||
const rawValue = arg.slice('--effort='.length);
|
||||
if (!rawValue.trim()) {
|
||||
throw new Error(`--effort requires a value: ${allowedValues}`);
|
||||
}
|
||||
const value = rawValue.toLowerCase();
|
||||
if (!NATIVE_CLAUDE_EFFORT_LEVEL_SET.has(value)) {
|
||||
throw new Error(`Invalid --effort value: ${rawValue}. Expected one of: ${allowedValues}.`);
|
||||
}
|
||||
normalizedArgs.push(`--effort=${value}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
normalizedArgs.push(arg);
|
||||
}
|
||||
|
||||
return normalizedArgs;
|
||||
}
|
||||
|
||||
export function shouldNormalizeNativeClaudeEffort(
|
||||
profileType: ProfileDetectionResult['type']
|
||||
): boolean {
|
||||
return profileType === 'default' || profileType === 'account' || profileType === 'settings';
|
||||
}
|
||||
|
||||
// ========== Native Codex Passthrough ==========
|
||||
|
||||
export function shouldPassthroughNativeCodexFlagCommand(args: string[]): boolean {
|
||||
return getNativeCodexPassthroughArgs(args) !== null;
|
||||
}
|
||||
|
||||
export function getNativeCodexPassthroughArgs(args: string[]): string[] | null {
|
||||
const targetArgs = stripTargetFlag(args);
|
||||
if (resolveTargetType(args) !== 'codex' || targetArgs.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const firstArg = targetArgs[0] || '';
|
||||
if (CODEX_NATIVE_PASSTHROUGH_FLAGS.has(firstArg)) {
|
||||
return targetArgs;
|
||||
}
|
||||
|
||||
const secondArg = targetArgs[1] || '';
|
||||
if (firstArg === 'codex' && CODEX_NATIVE_PASSTHROUGH_FLAGS.has(secondArg)) {
|
||||
return targetArgs.slice(1);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* Runtime environment and launch-args builders.
|
||||
*
|
||||
* Extracted from src/ccs.ts (lines 146-163, 300-327, 329-369).
|
||||
* Covers update-cache helpers and launch-arg resolution for native Claude/Codex targets.
|
||||
*/
|
||||
|
||||
import { warn } from '../utils/ui';
|
||||
import { resolveBrowserExposure } from '../utils/browser';
|
||||
import { getBrowserConfig, getOfficialChannelsConfig } from '../config/unified-config-loader';
|
||||
import {
|
||||
buildOfficialChannelsArgs,
|
||||
getOfficialChannelsEnvironmentStatus,
|
||||
officialChannelRequiresMacOS,
|
||||
resolveOfficialChannelsLaunchPlan,
|
||||
} from '../channels/official-channels-runtime';
|
||||
import { getOfficialChannelReadiness } from '../channels/official-channels-store';
|
||||
import { buildCodexBrowserMcpOverrides } from '../utils/browser-codex-overrides';
|
||||
import { getVersion } from '../utils/version';
|
||||
import {
|
||||
checkForUpdates,
|
||||
showUpdateNotification,
|
||||
checkCachedUpdate,
|
||||
} from '../utils/update-checker';
|
||||
import { resolveTargetType } from '../targets/target-resolver';
|
||||
import type { BrowserLaunchOverride } from '../utils/browser';
|
||||
|
||||
// ========== Codex Runtime Config ==========
|
||||
|
||||
export function resolveCodexRuntimeConfigOverrides(
|
||||
target: ReturnType<typeof resolveTargetType>,
|
||||
browserLaunchOverride: BrowserLaunchOverride | undefined
|
||||
): string[] {
|
||||
if (target !== 'codex') {
|
||||
return [];
|
||||
}
|
||||
|
||||
const codexBrowserExposure = resolveBrowserExposure(
|
||||
getBrowserConfig().codex,
|
||||
browserLaunchOverride
|
||||
);
|
||||
if (!codexBrowserExposure.exposeForLaunch) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return buildCodexBrowserMcpOverrides();
|
||||
}
|
||||
|
||||
// ========== Update Cache Helpers ==========
|
||||
|
||||
/**
|
||||
* Perform background update check (refreshes cache, no notification).
|
||||
*/
|
||||
export async function refreshUpdateCache(): Promise<void> {
|
||||
try {
|
||||
const currentVersion = getVersion();
|
||||
// npm is now the only supported installation method
|
||||
await checkForUpdates(currentVersion, true, 'npm');
|
||||
} catch (_e) {
|
||||
// Silently fail - update check shouldn't crash main CLI
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show update notification if cached result indicates update available.
|
||||
* Returns true if notification was shown.
|
||||
*/
|
||||
export async function showCachedUpdateNotification(): Promise<boolean> {
|
||||
try {
|
||||
const currentVersion = getVersion();
|
||||
const updateInfo = checkCachedUpdate(currentVersion);
|
||||
|
||||
if (updateInfo) {
|
||||
await showUpdateNotification(updateInfo);
|
||||
return true;
|
||||
}
|
||||
} catch (_e) {
|
||||
// Silently fail
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ========== Native Claude Launch Args ==========
|
||||
|
||||
export function resolveNativeClaudeLaunchArgs(
|
||||
args: string[],
|
||||
profileType: 'default' | 'account',
|
||||
targetConfigDir?: string
|
||||
): string[] {
|
||||
const config = getOfficialChannelsConfig();
|
||||
const environment = getOfficialChannelsEnvironmentStatus(
|
||||
targetConfigDir ? { CLAUDE_CONFIG_DIR: targetConfigDir } : undefined
|
||||
);
|
||||
const channelReadiness = {
|
||||
telegram: getOfficialChannelReadiness('telegram'),
|
||||
discord: getOfficialChannelReadiness('discord'),
|
||||
imessage: !officialChannelRequiresMacOS('imessage') || process.platform === 'darwin',
|
||||
};
|
||||
const plan = resolveOfficialChannelsLaunchPlan({
|
||||
args,
|
||||
config,
|
||||
target: 'claude',
|
||||
profileType,
|
||||
environment,
|
||||
channelReadiness,
|
||||
});
|
||||
|
||||
for (const message of plan.skippedMessages) {
|
||||
console.error(warn(message));
|
||||
}
|
||||
|
||||
if (
|
||||
config.selected.length > 0 &&
|
||||
environment.auth.state === 'eligible' &&
|
||||
environment.auth.orgRequirementMessage
|
||||
) {
|
||||
console.error(warn(environment.auth.orgRequirementMessage));
|
||||
}
|
||||
|
||||
if (!plan.applied) {
|
||||
return args;
|
||||
}
|
||||
|
||||
return buildOfficialChannelsArgs(args, plan.appliedChannels, plan.wantsPermissionBypass);
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Native target execution — short-circuit dispatch for passthrough flag commands.
|
||||
*
|
||||
* Extracted from src/ccs.ts (lines 291-295, 394-426).
|
||||
* Handles direct execution of native Codex flag passthrough (--help, --version, etc.)
|
||||
* before the main profile dispatch loop runs.
|
||||
*/
|
||||
|
||||
import { fail, info } from '../utils/ui';
|
||||
import { getTarget } from '../targets';
|
||||
import { getNativeCodexPassthroughArgs } from './cli-argument-parser';
|
||||
import type { TargetCredentials } from '../targets';
|
||||
|
||||
// ========== Interfaces ==========
|
||||
|
||||
export interface ProfileError extends Error {
|
||||
profileName?: string;
|
||||
availableProfiles?: string;
|
||||
suggestions?: string[];
|
||||
}
|
||||
|
||||
// ========== Native Codex Flag Command Executor ==========
|
||||
|
||||
export function execNativeCodexFlagCommand(args: string[]): void {
|
||||
const adapter = getTarget('codex');
|
||||
if (!adapter) {
|
||||
console.error(fail('Target adapter not found for "codex"'));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const binaryInfo = adapter.detectBinary();
|
||||
if (!binaryInfo) {
|
||||
console.error(fail('Codex CLI not found.'));
|
||||
console.error(info('Install a recent @openai/codex build, then retry.'));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const targetArgs = getNativeCodexPassthroughArgs(args);
|
||||
if (!targetArgs) {
|
||||
console.error(fail('Native Codex passthrough args could not be resolved.'));
|
||||
process.exit(1);
|
||||
}
|
||||
const creds: TargetCredentials = {
|
||||
profile: 'default',
|
||||
baseUrl: '',
|
||||
apiKey: '',
|
||||
};
|
||||
|
||||
const builtArgs = adapter.buildArgs('default', targetArgs, {
|
||||
creds,
|
||||
profileType: 'default',
|
||||
binaryInfo,
|
||||
});
|
||||
const targetEnv = adapter.buildEnv(creds, 'default');
|
||||
adapter.exec(builtArgs, targetEnv, { binaryInfo });
|
||||
}
|
||||
Reference in New Issue
Block a user