Skip to content

Commit 6a74a12

Browse files
feat(command): add /exit that exits the tui
1 parent 4c73aca commit 6a74a12

3 files changed

Lines changed: 40 additions & 13 deletions

File tree

src/components/App.test.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,17 @@ import { render } from 'ink-testing-library';
33

44
import { time } from '../utils';
55

6+
const { mockExit } = vi.hoisted(() => ({
7+
mockExit: vi.fn(),
8+
}));
9+
10+
vi.mock('ink', async () => ({
11+
...(await vi.importActual('ink')),
12+
useApp: vi.fn(() => ({
13+
exit: mockExit,
14+
})),
15+
}));
16+
617
const resetSystemMessage = vi.hoisted(() => vi.fn());
718

819
vi.mock('../utils', async () => ({
@@ -90,6 +101,7 @@ describe('App', () => {
90101
capturedCallbacks.onClose = null;
91102
capturedCallbacks.onToggleMode = null;
92103
resetSystemMessage.mockClear();
104+
mockExit.mockReset();
93105
});
94106

95107
it('renders title', () => {
@@ -137,6 +149,12 @@ describe('App', () => {
137149
expect(lastFrame()).not.toContain('ModelPicker');
138150
});
139151

152+
it('calls exit when /exit command is issued', () => {
153+
render(<App />);
154+
capturedCallbacks.onCommand?.('/exit');
155+
expect(mockExit).toHaveBeenCalledOnce();
156+
});
157+
140158
it('resets the chat session when /clear is issued', async () => {
141159
const { lastFrame, rerender } = render(<App />);
142160

src/components/App.tsx

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Box } from 'ink';
1+
import { Box, useApp } from 'ink';
22
import { useCallback, useState } from 'react';
33

44
import { MODE } from '../constants';
@@ -9,24 +9,32 @@ import { Header } from './Header';
99
import { ModelPicker } from './ModelPicker';
1010

1111
export function App() {
12+
const { exit } = useApp();
1213
const [model, setModel] = useState(() => config.loadConfig().model);
1314
const [picking, setPicking] = useState(false);
1415
const [mode, setMode] = useState<MODE.Name>(MODE.NAME.SAFE);
1516
const [sessionId, setSessionId] = useState(0);
1617

17-
const handleCommand = useCallback((command: string) => {
18-
switch (command) {
19-
case '/model':
20-
setPicking(true);
21-
break;
18+
const handleCommand = useCallback(
19+
(command: string) => {
20+
switch (command) {
21+
case '/model':
22+
setPicking(true);
23+
break;
2224

23-
case '/clear':
24-
agents.resetSystemMessage();
25-
setPicking(false);
26-
setSessionId((sessionId) => sessionId + 1);
27-
break;
28-
}
29-
}, []);
25+
case '/clear':
26+
agents.resetSystemMessage();
27+
setPicking(false);
28+
setSessionId((sessionId) => sessionId + 1);
29+
break;
30+
31+
case '/exit':
32+
exit();
33+
break;
34+
}
35+
},
36+
[exit],
37+
);
3038

3139
const handleSelect = useCallback((selected: string) => {
3240
setModel(selected);

src/constants/command.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ export interface CommandList {
66
export const LIST: CommandList[] = [
77
{ name: '/clear', description: 'clear the current session' },
88
{ name: '/model', description: 'switch the model' },
9+
{ name: '/exit', description: 'exit the application' },
910
] as const;

0 commit comments

Comments
 (0)