fix(cli): honor argv @path in interactive sessions (quoted + unquoted) (#5952)

Co-authored-by: Allen Hutchison <adh@google.com>
This commit is contained in:
Tayyab3245
2025-09-30 21:18:04 -04:00
committed by GitHub
parent f2aa9d283a
commit c913ce3c0b
3 changed files with 191 additions and 23 deletions

View File

@@ -58,6 +58,7 @@ const logger = {
};
export interface CliArgs {
query: string | undefined;
model: string | undefined;
sandbox: boolean | string | undefined;
sandboxImage: string | undefined;
@@ -85,12 +86,12 @@ export interface CliArgs {
screenReader: boolean | undefined;
useSmartEdit: boolean | undefined;
useWriteTodos: boolean | undefined;
promptWords: string[] | undefined;
outputFormat: string | undefined;
}
export async function parseArguments(settings: Settings): Promise<CliArgs> {
const yargsInstance = yargs(hideBin(process.argv))
const rawArgv = hideBin(process.argv);
const yargsInstance = yargs(rawArgv)
.locale('en')
.scriptName('gemini')
.usage(
@@ -166,8 +167,12 @@ export async function parseArguments(settings: Settings): Promise<CliArgs> {
'proxy',
'Use the "proxy" setting in settings.json instead. This flag will be removed in a future version.',
)
.command('$0 [promptWords...]', 'Launch Gemini CLI', (yargsInstance) =>
.command('$0 [query..]', 'Launch Gemini CLI', (yargsInstance) =>
yargsInstance
.positional('query', {
description:
'Positional prompt. Defaults to one-shot; use -i/--prompt-interactive for interactive.',
})
.option('model', {
alias: 'm',
type: 'string',
@@ -301,22 +306,28 @@ export async function parseArguments(settings: Settings): Promise<CliArgs> {
'prompt',
'Use the positional prompt instead. This flag will be removed in a future version.',
)
// Ensure validation flows through .fail() for clean UX
.fail((msg, err, yargs) => {
console.error(msg || err?.message || 'Unknown error');
yargs.showHelp();
process.exit(1);
})
.check((argv) => {
const promptWords = argv['promptWords'] as string[] | undefined;
if (argv['prompt'] && promptWords && promptWords.length > 0) {
throw new Error(
'Cannot use both a positional prompt and the --prompt (-p) flag together',
);
// The 'query' positional can be a string (for one arg) or string[] (for multiple).
// This guard safely checks if any positional argument was provided.
const query = argv['query'] as string | string[] | undefined;
const hasPositionalQuery = Array.isArray(query)
? query.length > 0
: !!query;
if (argv['prompt'] && hasPositionalQuery) {
return 'Cannot use both a positional prompt and the --prompt (-p) flag together';
}
if (argv['prompt'] && argv['promptInteractive']) {
throw new Error(
'Cannot use both --prompt (-p) and --prompt-interactive (-i) together',
);
return 'Cannot use both --prompt (-p) and --prompt-interactive (-i) together';
}
if (argv.yolo && argv['approvalMode']) {
throw new Error(
'Cannot use both --yolo (-y) and --approval-mode together. Use --approval-mode=yolo instead.',
);
return 'Cannot use both --yolo (-y) and --approval-mode together. Use --approval-mode=yolo instead.';
}
return true;
}),
@@ -339,6 +350,8 @@ export async function parseArguments(settings: Settings): Promise<CliArgs> {
yargsInstance.wrap(yargsInstance.terminalWidth());
const result = await yargsInstance.parse();
// If yargs handled --help/--version it will have exited; nothing to do here.
// Handle case where MCP subcommands are executed - they should exit the process
// and not return to main CLI logic
if (
@@ -349,6 +362,26 @@ export async function parseArguments(settings: Settings): Promise<CliArgs> {
process.exit(0);
}
// Normalize query args: handle both quoted "@path file" and unquoted @path file
const queryArg = (result as { query?: string | string[] | undefined }).query;
const q: string | undefined = Array.isArray(queryArg)
? queryArg.join(' ')
: queryArg;
// Route positional args: explicit -i flag -> interactive; else -> one-shot (even for @commands)
if (q && !result['prompt']) {
const hasExplicitInteractive =
result['promptInteractive'] === '' || !!result['promptInteractive'];
if (hasExplicitInteractive) {
result['promptInteractive'] = q;
} else {
result['prompt'] = q;
}
}
// Keep CliArgs.query as a string for downstream typing
(result as Record<string, unknown>)['query'] = q || undefined;
// The import format is now only controlled by settings.memoryImportFormat
// We no longer accept it as a CLI argument
return result as unknown as CliArgs;
@@ -475,8 +508,7 @@ export async function loadCliConfig(
);
let mcpServers = mergeMcpServers(settings, activeExtensions);
const question =
argv.promptInteractive || argv.prompt || (argv.promptWords || []).join(' ');
const question = argv.promptInteractive || argv.prompt || '';
// Determine approval mode with backward compatibility
let approvalMode: ApprovalMode;
@@ -529,11 +561,11 @@ export async function loadCliConfig(
const policyEngineConfig = createPolicyEngineConfig(settings, approvalMode);
// Fix: If promptWords are provided, always use non-interactive mode
const hasPromptWords = argv.promptWords && argv.promptWords.length > 0;
// Interactive mode: explicit -i flag or (TTY + no args + no -p flag)
const hasQuery = !!argv.query;
const interactive =
!!argv.promptInteractive ||
(process.stdin.isTTY && !hasPromptWords && !argv.prompt);
(process.stdin.isTTY && !hasQuery && !argv.prompt);
// In non-interactive mode, exclude tools that require a prompt.
const extraExcludes: string[] = [];
if (!interactive && !argv.experimentalAcp) {