feat(cli): add direct argument support for /approval-mode command

Allow users to set approval mode directly via argument instead of
opening the dialog. For example:
- /approval-mode plan
- /approval-mode yolo
- /approval-mode auto-edit
- /approval-mode default

If no argument is provided, the dialog opens as before.
If an invalid argument is provided, an error message shows valid options.

Also adds tab completion for mode arguments.

Fixes #1353
This commit is contained in:
Tu Shaokun 2026-01-01 20:17:15 +08:00
parent b95d9a8d2d
commit 8fcdd86b91
5 changed files with 246 additions and 14 deletions

View file

@ -4,29 +4,34 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, it, expect } from 'vitest';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { approvalModeCommand } from './approvalModeCommand.js';
import {
type CommandContext,
CommandKind,
type OpenDialogActionReturn,
type MessageActionReturn,
} from './types.js';
import { createMockCommandContext } from '../../test-utils/mockCommandContext.js';
import type { LoadedSettings } from '../../config/settings.js';
describe('approvalModeCommand', () => {
let mockContext: CommandContext;
let mockSetValue: ReturnType<typeof vi.fn>;
let mockSetApprovalMode: ReturnType<typeof vi.fn>;
beforeEach(() => {
mockSetValue = vi.fn();
mockSetApprovalMode = vi.fn();
mockContext = createMockCommandContext({
services: {
config: {
getApprovalMode: () => 'default',
setApprovalMode: () => {},
setApprovalMode: mockSetApprovalMode,
},
settings: {
merged: {},
setValue: () => {},
merged: { tools: { approvalMode: 'default' } },
setValue: mockSetValue,
forScope: () => ({}),
} as unknown as LoadedSettings,
},
@ -41,7 +46,7 @@ describe('approvalModeCommand', () => {
expect(approvalModeCommand.kind).toBe(CommandKind.BUILT_IN);
});
it('should open approval mode dialog when invoked', async () => {
it('should open approval mode dialog when invoked without arguments', async () => {
const result = (await approvalModeCommand.action?.(
mockContext,
'',
@ -51,21 +56,177 @@ describe('approvalModeCommand', () => {
expect(result.dialog).toBe('approval-mode');
});
it('should open approval mode dialog with arguments (ignored)', async () => {
it('should open approval mode dialog when invoked with whitespace only', async () => {
const result = (await approvalModeCommand.action?.(
mockContext,
'some arguments',
' ',
)) as OpenDialogActionReturn;
expect(result.type).toBe('dialog');
expect(result.dialog).toBe('approval-mode');
});
describe('direct mode setting', () => {
it('should set approval mode to "plan" when argument is "plan"', async () => {
const result = (await approvalModeCommand.action?.(
mockContext,
'plan',
)) as MessageActionReturn;
expect(result.type).toBe('message');
expect(result.messageType).toBe('info');
expect(result.content).toContain('plan');
expect(mockSetValue).toHaveBeenCalledWith(
'User',
'tools.approvalMode',
'plan',
);
expect(mockSetApprovalMode).toHaveBeenCalled();
});
it('should set approval mode to "yolo" when argument is "yolo"', async () => {
const result = (await approvalModeCommand.action?.(
mockContext,
'yolo',
)) as MessageActionReturn;
expect(result.type).toBe('message');
expect(result.messageType).toBe('info');
expect(result.content).toContain('yolo');
expect(mockSetValue).toHaveBeenCalledWith(
'User',
'tools.approvalMode',
'yolo',
);
});
it('should set approval mode to "auto-edit" when argument is "auto-edit"', async () => {
const result = (await approvalModeCommand.action?.(
mockContext,
'auto-edit',
)) as MessageActionReturn;
expect(result.type).toBe('message');
expect(result.messageType).toBe('info');
expect(result.content).toContain('auto-edit');
expect(mockSetValue).toHaveBeenCalledWith(
'User',
'tools.approvalMode',
'auto-edit',
);
});
it('should set approval mode to "default" when argument is "default"', async () => {
const result = (await approvalModeCommand.action?.(
mockContext,
'default',
)) as MessageActionReturn;
expect(result.type).toBe('message');
expect(result.messageType).toBe('info');
expect(result.content).toContain('default');
expect(mockSetValue).toHaveBeenCalledWith(
'User',
'tools.approvalMode',
'default',
);
});
it('should be case-insensitive for mode argument', async () => {
const result = (await approvalModeCommand.action?.(
mockContext,
'YOLO',
)) as MessageActionReturn;
expect(result.type).toBe('message');
expect(result.messageType).toBe('info');
expect(mockSetValue).toHaveBeenCalledWith(
'User',
'tools.approvalMode',
'yolo',
);
});
it('should handle argument with leading/trailing whitespace', async () => {
const result = (await approvalModeCommand.action?.(
mockContext,
' plan ',
)) as MessageActionReturn;
expect(result.type).toBe('message');
expect(result.messageType).toBe('info');
expect(mockSetValue).toHaveBeenCalledWith(
'User',
'tools.approvalMode',
'plan',
);
});
});
describe('invalid mode argument', () => {
it('should return error for invalid mode', async () => {
const result = (await approvalModeCommand.action?.(
mockContext,
'invalid-mode',
)) as MessageActionReturn;
expect(result.type).toBe('message');
expect(result.messageType).toBe('error');
expect(result.content).toContain('invalid-mode');
expect(result.content).toContain('plan');
expect(result.content).toContain('yolo');
expect(mockSetValue).not.toHaveBeenCalled();
expect(mockSetApprovalMode).not.toHaveBeenCalled();
});
});
it('should not have subcommands', () => {
expect(approvalModeCommand.subCommands).toBeUndefined();
});
it('should not have completion function', () => {
expect(approvalModeCommand.completion).toBeUndefined();
describe('completion', () => {
it('should have completion function', () => {
expect(approvalModeCommand.completion).toBeDefined();
});
it('should return all modes when partial arg is empty', async () => {
const completions = await approvalModeCommand.completion?.(
mockContext,
'',
);
expect(completions).toContain('plan');
expect(completions).toContain('default');
expect(completions).toContain('auto-edit');
expect(completions).toContain('yolo');
});
it('should filter modes based on partial arg', async () => {
const completions = await approvalModeCommand.completion?.(
mockContext,
'p',
);
expect(completions).toContain('plan');
expect(completions).not.toContain('yolo');
});
it('should filter modes case-insensitively', async () => {
const completions = await approvalModeCommand.completion?.(
mockContext,
'A',
);
expect(completions).toContain('auto-edit');
});
it('should return empty array when no modes match', async () => {
const completions = await approvalModeCommand.completion?.(
mockContext,
'xyz',
);
expect(completions).toEqual([]);
});
});
});