Skip to content

Commit 6ab29b5

Browse files
committed
feat: allow exit and quit commands without leading slash
1 parent e177314 commit 6ab29b5

File tree

3 files changed

+45
-3
lines changed

3 files changed

+45
-3
lines changed

docs/cli/commands.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,8 @@ Slash commands provide meta-level control over the CLI itself.
215215
purposes.
216216

217217
- **`/quit`** (or **`/exit`**)
218-
- **Description:** Exit Gemini CLI.
218+
- **Description:** Exit Gemini CLI. You can also type `quit` or `exit` without
219+
the leading slash.
219220

220221
- **`/vim`**
221222
- **Description:** Toggle vim mode on or off. When vim mode is enabled, the

packages/cli/src/ui/hooks/slashCommandProcessor.test.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,31 @@ describe('useSlashCommandProcessor', () => {
573573

574574
expect(mockSetQuittingMessages).toHaveBeenCalledWith(['bye']);
575575
});
576+
577+
it.each(['exit', 'quit', 'EXIT', 'Quit'])(
578+
'should handle "%s" command without a slash',
579+
async (cmd) => {
580+
const quitAction = vi
581+
.fn()
582+
.mockResolvedValue({ type: 'quit', messages: ['bye'] });
583+
const command = createTestCommand({
584+
name: 'quit',
585+
altNames: ['exit'],
586+
action: quitAction,
587+
});
588+
const result = await setupProcessorHook([command]);
589+
590+
await waitFor(() =>
591+
expect(result.current.slashCommands).toHaveLength(1),
592+
);
593+
594+
await act(async () => {
595+
await result.current.handleSlashCommand(cmd);
596+
});
597+
598+
expect(quitAction).toHaveBeenCalled();
599+
},
600+
);
576601
it('should handle "submit_prompt" action returned from a file-based command', async () => {
577602
const fileCommand = createTestCommand(
578603
{

packages/cli/src/ui/hooks/slashCommandProcessor.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,14 @@ export const useSlashCommandProcessor = (
321321
}
322322

323323
const trimmed = rawQuery.trim();
324-
if (!trimmed.startsWith('/') && !trimmed.startsWith('?')) {
324+
const parts = trimmed.split(/\s+/);
325+
const commandWord = parts[0].toLowerCase();
326+
const isExitOrQuit = commandWord === 'exit' || commandWord === 'quit';
327+
if (
328+
!trimmed.startsWith('/') &&
329+
!trimmed.startsWith('?') &&
330+
!isExitOrQuit
331+
) {
325332
return false;
326333
}
327334

@@ -336,11 +343,20 @@ export const useSlashCommandProcessor = (
336343
}
337344

338345
let hasError = false;
346+
let commandToParse = trimmed;
347+
if (
348+
isExitOrQuit &&
349+
!trimmed.startsWith('/') &&
350+
!trimmed.startsWith('?')
351+
) {
352+
parts[0] = commandWord;
353+
commandToParse = `/${parts.join(' ')}`;
354+
}
339355
const {
340356
commandToExecute,
341357
args,
342358
canonicalPath: resolvedCommandPath,
343-
} = parseSlashCommand(trimmed, commands);
359+
} = parseSlashCommand(commandToParse, commands);
344360

345361
const subcommand =
346362
resolvedCommandPath.length > 1

0 commit comments

Comments
 (0)