Skip to content

Commit 76f95f0

Browse files
koki-developclaude
andcommitted
feat: Implement double Ctrl+C exit control
- Disable Ink's default exitOnCtrlC behavior - Add custom double Ctrl+C handler with 3-second timeout - Show warning message after first Ctrl+C press - Cancel exit on any other input - Hide warning before exit on second Ctrl+C 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 16153af commit 76f95f0

File tree

2 files changed

+58
-3
lines changed

2 files changed

+58
-3
lines changed

src/App.tsx

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { Box } from "ink";
1+
import { Box, Text, useApp, useInput } from "ink";
22
import type React from "react";
3-
import { useState } from "react";
3+
import { useEffect, useRef, useState } from "react";
44
import { ChatHistory } from "./components/ChatHistory";
55
import { InputField } from "./components/InputField";
66
import { Spinner } from "./components/Spinner";
@@ -13,6 +13,52 @@ export const App: React.FC = () => {
1313
const [messages, setMessages] = useState<Message[]>([]);
1414
const [input, setInput] = useState("");
1515
const [isLoading, setIsLoading] = useState(false);
16+
const [showExitWarning, setShowExitWarning] = useState(false);
17+
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
18+
19+
const { exit } = useApp();
20+
21+
// Handle double Ctrl+C exit
22+
useInput((input, key) => {
23+
if (key.ctrl && input === "c") {
24+
if (!showExitWarning) {
25+
setShowExitWarning(true);
26+
27+
// Clear timeout if it exists
28+
if (timeoutRef.current) {
29+
clearTimeout(timeoutRef.current);
30+
}
31+
32+
// Set timeout to reset warning after 3 seconds
33+
timeoutRef.current = setTimeout(() => {
34+
setShowExitWarning(false);
35+
timeoutRef.current = null;
36+
}, 3000);
37+
} else {
38+
// Second Ctrl+C within timeout - exit immediately
39+
if (timeoutRef.current) {
40+
clearTimeout(timeoutRef.current);
41+
}
42+
exit();
43+
}
44+
} else if (showExitWarning) {
45+
// Cancel Ctrl+C exit when any other input is detected
46+
if (timeoutRef.current) {
47+
clearTimeout(timeoutRef.current);
48+
timeoutRef.current = null;
49+
}
50+
setShowExitWarning(false);
51+
}
52+
});
53+
54+
// Cleanup timeout on unmount
55+
useEffect(() => {
56+
return () => {
57+
if (timeoutRef.current) {
58+
clearTimeout(timeoutRef.current);
59+
}
60+
};
61+
}, []);
1662

1763
const handleSubmit = async () => {
1864
if (input.trim() === "" || isLoading) return;
@@ -53,6 +99,13 @@ export const App: React.FC = () => {
5399
onSubmit={handleSubmit}
54100
showCursor={!isLoading}
55101
/>
102+
{showExitWarning && (
103+
<Box paddingX={1} marginTop={1}>
104+
<Text color="yellow">
105+
Press Ctrl+C again within 3 seconds to exit
106+
</Text>
107+
</Box>
108+
)}
56109
</Box>
57110
);
58111
};

src/index.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ program
2121
.action(() => {
2222
console.log(logo);
2323

24-
render(<App />);
24+
render(<App />, {
25+
exitOnCtrlC: false, // Disable default Ctrl+C exit behavior
26+
});
2527
});
2628

2729
program.parse(process.argv);

0 commit comments

Comments
 (0)