basant307/AI_Governance_Project
048
1/**2 * @license3 * Copyright 2026 Qwen Team4 * SPDX-License-Identifier: Apache-2.05 */6 7import { pathToFileURL } from 'node:url';8import type { ArgumentsCamelCase, Argv, Options } from 'yargs';9import { normalizeServeFastPathArgv } from './serve/fast-path-argv.js';10import { initStartupProfiler } from './utils/startupProfiler.js';11import { initCpuProfiler } from './utils/cpuProfiler.js';12 13// Preserve the old entrypoint's profiling baseline before route-specific14// dynamic imports or command handling shift startup measurements.15initStartupProfiler();16initCpuProfiler();17 18type BootstrapRoute = 'serve' | 'mcp' | 'help' | 'version' | 'default';19 20export const TOP_LEVEL_COMMANDS = [21 ['auth', 'Configure authentication (removed)'],22 ['channel <command>', 'Manage messaging channels (Telegram, Discord, etc.)'],23 ['extensions <command>', 'Manage Qwen Code extensions.'],24 ['hooks', 'Manage Qwen Code hooks (use /hooks in interactive mode).'],25 ['mcp', 'Manage MCP servers'],26 [27 'review <command>',28 'Internal helpers used by the /review skill (PR worktree setup, context fetch, rules loading, presubmit checks, cleanup)',29 ],30 [31 'serve',32 'Run Qwen Code as a local HTTP daemon (Stage 1 experimental: --http-bridge)',33 ],34 ['sessions <command>', 'Manage Qwen Code sessions'],35] as const;36 37export const MCP_COMMANDS = [38 ['add <name> <commandOrUrl> [args...]', 'Add a server'],39 ['remove <name>', 'Remove a server'],40 ['list', 'List all configured MCP servers'],41 ['reconnect [server-name]', 'Reconnect to MCP servers'],42 ['approve [name]', 'Approve a pending MCP server'],43 ['reject [name]', 'Reject a pending MCP server'],44] as const;45 46const TOP_LEVEL_HELP_OPTIONS = [47 ['model', { alias: 'm', type: 'string', description: 'Model' }],48 [49 'fallback-model',50 {51 type: 'array',52 description:53 'Fallback model(s) for capacity errors, repeatable or comma-separated (max 3)',54 },55 ],56 [57 'prompt',58 {59 alias: 'p',60 type: 'string',61 description: 'Prompt. Appended to input on stdin (if any).',62 },63 ],64 [65 'prompt-interactive',66 {67 alias: 'i',68 type: 'string',69 description:70 'Execute the provided prompt and continue in interactive mode',71 },72 ],73 [74 'safe-mode',75 {76 type: 'boolean',77 description:78 'Disable all customizations (context files, hooks, extensions, skills, MCP servers) for troubleshooting.',79 },80 ],81 [82 'sandbox',83 {84 alias: 's',85 type: 'boolean',86 description: 'Run in sandbox?',87 },88 ],89 [90 'output-format',91 {92 alias: 'o',93 type: 'string',94 choices: ['text', 'json', 'stream-json'],95 description: 'The format of the CLI output.',96 },97 ],98 [99 'continue',100 {101 alias: 'c',102 type: 'boolean',103 description: 'Resume the most recent session for the current project.',104 },105 ],106 [107 'resume',108 {109 alias: 'r',110 type: 'string',111 description:112 'Resume a specific session by its ID. Use without an ID to show session picker.',113 },114 ],115] as const satisfies ReadonlyArray<readonly [string, Options]>;116 117const VALUE_FLAGS = new Set([118 '--model',119 '-m',120 '--fallback-model',121 '--prompt',122 '-p',123 '--prompt-interactive',124 '-i',125 '--output-format',126 '-o',127 '--resume',128 '-r',129]);130 131function writeStdoutLine(line: string): void {132 process.stdout.write(line.endsWith('\n') ? line : `${line}\n`);133}134 135function hasFlag(136 argv: readonly string[],137 long: string,138 short: string,139): boolean {140 for (let i = 0; i < argv.length; i++) {141 const arg = argv[i]!;142 if (arg === '--') {143 return false;144 }145 if (VALUE_FLAGS.has(arg)) {146 i++;147 continue;148 }149 if (arg === long || arg === short) {150 return true;151 }152 }153 return false;154}155 156async function buildTopLevelHelpParser() {157 const { default: yargs } = await import('yargs');158 const parser = yargs([])159 .scriptName('qwen')160 .usage(161 'Usage: qwen [options] [command]\n\nQwen Code - Launch an interactive CLI, use -p/--prompt for non-interactive mode',162 )163 .version(process.env['CLI_VERSION'] || 'unknown')164 .alias('v', 'version')165 .help()166 .alias('h', 'help')167 .strict()168 .demandCommand(0, 0);169 170 for (const [option, config] of TOP_LEVEL_HELP_OPTIONS) {171 parser.option(option, config);172 }173 174 for (const [command, description] of TOP_LEVEL_COMMANDS) {175 parser.command(command, description);176 }177 178 return parser;179}180 181function firstPositionalArg(argv: readonly string[]): string | undefined {182 for (let i = 0; i < argv.length; i++) {183 const arg = argv[i]!;184 if (arg === '--') {185 return undefined;186 }187 if (VALUE_FLAGS.has(arg)) {188 i++;189 continue;190 }191 if (!arg.startsWith('-')) {192 return arg;193 }194 }195 return undefined;196}197 198function normalizeMcpFastPathArgv(argv: readonly string[]): readonly string[] {199 if (argv[0] === 'mcp' && argv[1] === '--') {200 return [argv[0], ...argv.slice(2)];201 }202 return argv;203}204 205export function resolveBootstrapRoute(206 rawArgv: readonly string[],207): BootstrapRoute {208 const argv = normalizeServeFastPathArgv(rawArgv);209 210 if (hasFlag(argv, '--version', '-v')) {211 return 'version';212 }213 214 const firstArg = argv[0];215 if (firstArg === 'serve') {216 return 'serve';217 }218 if (firstArg === 'mcp') {219 return 'mcp';220 }221 222 const firstPositional = firstPositionalArg(argv);223 if (hasFlag(argv, '--help', '-h') && firstPositional === undefined) {224 return 'help';225 }226 227 return 'default';228}229 230async function printTopLevelHelp(): Promise<void> {231 const help = await (await buildTopLevelHelpParser()).getHelp();232 writeStdoutLine(help);233}234 235function printMcpHelp(): void {236 const lines = [237 'Usage: qwen mcp <command>',238 '',239 'Manage MCP servers',240 '',241 'Commands:',242 ...MCP_COMMANDS.map(243 ([command, description]) => ` qwen mcp ${command} ${description}`,244 ),245 ];246 writeStdoutLine(lines.join('\n'));247}248 249async function printBootstrapVersion(): Promise<void> {250 if (process.env['CLI_VERSION']) {251 writeStdoutLine(process.env['CLI_VERSION']);252 return;253 }254 255 const { getCliVersion } = await import('./utils/version.js');256 writeStdoutLine(await getCliVersion());257}258 259async function runMcpFastPath(rawArgv: readonly string[]): Promise<void> {260 const argv = normalizeMcpFastPathArgv(normalizeServeFastPathArgv(rawArgv));261 const hasSubcommand = argv.length > 1 && !argv[1]!.startsWith('-');262 if (!hasSubcommand) {263 printMcpHelp();264 return;265 }266 267 const [{ default: yargsInstance }, { mcpCommand }] = await Promise.all([268 import('yargs'),269 import('./commands/mcp.js'),270 ]);271 272 const parser = yargsInstance([])273 .scriptName('qwen')274 .command(mcpCommand)275 .version(false)276 .help()277 .alias('h', 'help')278 .strict()279 .strictCommands()280 .demandCommand(1, 'You need at least one command before continuing.')281 .fail((message: string | null, error: Error | undefined, yargs: Argv) => {282 writeStderrLine(message || error?.message || 'Unknown argument error');283 yargs.showHelp();284 process.exitCode = 1;285 })286 .exitProcess(false);287 288 if (hasFlag(argv.slice(2), '--help', '-h')) {289 await parseYargsHelp(parser, argv);290 return;291 }292 293 await parseYargsCommand(parser, argv);294}295 296async function parseYargsHelp(297 parser: Argv,298 argv: readonly string[],299): Promise<void> {300 await new Promise<void>((resolve, reject) => {301 parser.parse(302 argv,303 (error: Error | undefined, _argv: ArgumentsCamelCase, output: string) => {304 if (output) {305 writeStdoutLine(output);306 }307 if (error) {308 reject(error);309 return;310 }311 resolve();312 },313 );314 });315}316 317async function parseYargsCommand(318 parser: Argv,319 argv: readonly string[],320): Promise<void> {321 await new Promise<void>((resolve) => {322 parser.parse(323 argv,324 (error: Error | undefined, _argv: ArgumentsCamelCase, output: string) => {325 if (output) {326 writeStdoutLine(output);327 }328 if (error) {329 writeStderrLine(error.message);330 process.exitCode = 1;331 }332 resolve();333 },334 );335 });336}337 338export async function runCliEntry(339 rawArgv: readonly string[] = process.argv.slice(2),340): Promise<void> {341 const argv = normalizeServeFastPathArgv(rawArgv);342 const route = resolveBootstrapRoute(argv);343 344 if (route === 'version') {345 await printBootstrapVersion();346 return;347 }348 349 if (route === 'serve') {350 const { tryRunServeFastPath } = await import('./serve/fast-path.js');351 if (await tryRunServeFastPath(argv)) {352 return;353 }354 } else if (route === 'mcp') {355 await runMcpFastPath(argv);356 return;357 } else if (route === 'help') {358 await printTopLevelHelp();359 return;360 }361 362 const { main } = await import('./gemini.js');363 await main();364}365 366function getErrnoCode(error: unknown): string | undefined {367 if (!error || typeof error !== 'object') {368 return undefined;369 }370 const code = (error as { code?: unknown }).code;371 return typeof code === 'string' ? code : undefined;372}373 374export function isExpectedPtyRaceError(error: unknown): boolean {375 if (!(error instanceof Error)) {376 return false;377 }378 379 const message = error.message;380 const code = getErrnoCode(error);381 382 if (383 (code === 'EIO' && message.includes('read')) ||384 message.includes('read EIO')385 ) {386 return true;387 }388 389 if (390 (code === 'EAGAIN' && message.includes('read')) ||391 message.includes('read EAGAIN')392 ) {393 return true;394 }395 396 return (397 message.includes('ioctl(2) failed, EBADF') ||398 message.includes('Cannot resize a pty that has already exited')399 );400}401 402export async function handleCriticalError(error: unknown): Promise<void> {403 const [{ FatalError }, { AlreadyReportedError }] = await Promise.all([404 import('@qwen-code/qwen-code-core'),405 import('./utils/errors.js'),406 ]);407 408 if (error instanceof FatalError) {409 let errorMessage = error.message;410 if (!process.env['NO_COLOR']) {411 errorMessage = `\x1b[31m${errorMessage}\x1b[0m`;412 }413 writeStderrLine(errorMessage);414 process.exit(error.exitCode);415 }416 if (error instanceof AlreadyReportedError) {417 process.exit(error.exitCode);418 }419 writeStderrLine('An unexpected critical error occurred:');420 if (error instanceof Error) {421 writeStderrLine(error.stack ?? error.message);422 } else {423 writeStderrLine(String(error));424 }425 process.exit(1);426}427 428function writeStderrLine(line: string): void {429 process.stderr.write(line.endsWith('\n') ? line : `${line}\n`);430}431 432export async function runCliEntryPoint(433 run: () => Promise<void> = runCliEntry,434 handleError: (error: unknown) => Promise<void> = handleCriticalError,435): Promise<void> {436 process.on('uncaughtException', (error) => {437 if (isExpectedPtyRaceError(error)) {438 return;439 }440 441 if (error instanceof Error) {442 writeStderrLine(error.stack ?? error.message);443 } else {444 writeStderrLine(String(error));445 }446 process.exit(1);447 });448 449 try {450 await run();451 } catch (error) {452 try {453 await handleError(error);454 } catch (handlerError) {455 writeStderrLine('An unexpected critical error occurred:');456 writeStderrLine('Original error:');457 if (error instanceof Error) {458 writeStderrLine(error.stack ?? error.message);459 } else {460 writeStderrLine(String(error));461 }462 writeStderrLine('Error handler failed:');463 if (handlerError instanceof Error) {464 writeStderrLine(handlerError.stack ?? handlerError.message);465 } else {466 writeStderrLine(String(handlerError));467 }468 process.exit(1);469 }470 }471}472 473if (474 process.argv[1] !== undefined &&475 import.meta.url === pathToFileURL(process.argv[1]).href476) {477 void runCliEntryPoint();478}479 