diff --git a/package.json b/package.json
index 4710bad..044f710 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@vibe-kit/grok-cli",
- "version": "0.0.19",
+ "version": "0.0.20",
"description": "An open-source AI agent that brings the power of Grok directly into your terminal.",
"main": "dist/index.js",
"bin": {
diff --git a/src/hooks/use-input-handler.ts b/src/hooks/use-input-handler.ts
index 6a95227..e9d1948 100644
--- a/src/hooks/use-input-handler.ts
+++ b/src/hooks/use-input-handler.ts
@@ -43,6 +43,7 @@ export function useInputHandler({
isConfirmationActive = false,
}: UseInputHandlerProps) {
const [input, setInput] = useState("");
+ const [cursorPosition, setCursorPosition] = useState(0);
const [showCommandSuggestions, setShowCommandSuggestions] = useState(false);
const [selectedCommandIndex, setSelectedCommandIndex] = useState(0);
const [showModelSelection, setShowModelSelection] = useState(false);
@@ -86,6 +87,7 @@ export function useInputHandler({
confirmationService.resetSession();
setInput("");
+ setCursorPosition(0);
return true;
}
@@ -127,6 +129,7 @@ Examples:
};
setChatHistory((prev) => [...prev, helpEntry]);
setInput("");
+ setCursorPosition(0);
return true;
}
@@ -139,6 +142,7 @@ Examples:
setShowModelSelection(true);
setSelectedModelIndex(0);
setInput("");
+ setCursorPosition(0);
return true;
}
@@ -167,6 +171,7 @@ Available models: ${modelNames.join(", ")}`,
}
setInput("");
+ setCursorPosition(0);
return true;
}
@@ -371,6 +376,7 @@ Respond with ONLY the commit message, no additional text.`;
setIsProcessing(false);
setIsStreaming(false);
setInput("");
+ setCursorPosition(0);
return true;
}
@@ -428,6 +434,7 @@ Respond with ONLY the commit message, no additional text.`;
}
setInput("");
+ setCursorPosition(0);
return true;
}
@@ -444,6 +451,7 @@ Respond with ONLY the commit message, no additional text.`;
setIsProcessing(true);
setInput("");
+ setCursorPosition(0);
try {
setIsStreaming(true);
@@ -642,7 +650,9 @@ Respond with ONLY the commit message, no additional text.`;
filteredSuggestions.length - 1
);
const selectedCommand = filteredSuggestions[safeIndex];
- setInput(selectedCommand.command + " ");
+ const newInput = selectedCommand.command + " ";
+ setInput(newInput);
+ setCursorPosition(newInput.length);
setShowCommandSuggestions(false);
setSelectedCommandIndex(0);
return;
@@ -693,20 +703,35 @@ Respond with ONLY the commit message, no additional text.`;
return;
}
+ // Handle left and right arrow keys for cursor movement
+ if (key.leftArrow && !showCommandSuggestions && !showModelSelection) {
+ setCursorPosition(prev => Math.max(0, prev - 1));
+ return;
+ }
+
+ if (key.rightArrow && !showCommandSuggestions && !showModelSelection) {
+ setCursorPosition(prev => Math.min(input.length, prev + 1));
+ return;
+ }
+
if (key.backspace || key.delete) {
- const newInput = input.slice(0, -1);
- setInput(newInput);
+ if (cursorPosition > 0) {
+ const newInput = input.slice(0, cursorPosition - 1) + input.slice(cursorPosition);
+ setInput(newInput);
+ setCursorPosition(prev => prev - 1);
- if (!newInput.startsWith("/")) {
- setShowCommandSuggestions(false);
- setSelectedCommandIndex(0);
+ if (!newInput.startsWith("/")) {
+ setShowCommandSuggestions(false);
+ setSelectedCommandIndex(0);
+ }
}
return;
}
if (inputChar && !key.ctrl && !key.meta) {
- const newInput = input + inputChar;
+ const newInput = input.slice(0, cursorPosition) + inputChar + input.slice(cursorPosition);
setInput(newInput);
+ setCursorPosition(prev => prev + 1);
if (newInput.startsWith("/")) {
setShowCommandSuggestions(true);
@@ -720,6 +745,7 @@ Respond with ONLY the commit message, no additional text.`;
return {
input,
+ cursorPosition,
showCommandSuggestions,
selectedCommandIndex,
showModelSelection,
diff --git a/src/ui/components/chat-input.tsx b/src/ui/components/chat-input.tsx
index 5e7d5a5..8fc2cab 100644
--- a/src/ui/components/chat-input.tsx
+++ b/src/ui/components/chat-input.tsx
@@ -3,21 +3,27 @@ import { Box, Text } from "ink";
interface ChatInputProps {
input: string;
+ cursorPosition: number;
isProcessing: boolean;
isStreaming: boolean;
}
export function ChatInput({
input,
+ cursorPosition,
isProcessing,
isStreaming,
}: ChatInputProps) {
+ const beforeCursor = input.slice(0, cursorPosition);
+ const afterCursor = input.slice(cursorPosition);
+
return (
❯
- {input}
+ {beforeCursor}
{!isProcessing && !isStreaming && █}
+ {afterCursor}
);
diff --git a/src/ui/components/chat-interface.tsx b/src/ui/components/chat-interface.tsx
index 2cc10d6..0bbdf31 100644
--- a/src/ui/components/chat-interface.tsx
+++ b/src/ui/components/chat-interface.tsx
@@ -36,6 +36,7 @@ function ChatInterfaceWithAgent({ agent }: { agent: GrokAgent }) {
const {
input,
+ cursorPosition,
showCommandSuggestions,
selectedCommandIndex,
showModelSelection,
@@ -210,6 +211,7 @@ function ChatInterfaceWithAgent({ agent }: { agent: GrokAgent }) {