centaurus-cli 2.6.2 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli-adapter.d.ts +13 -22
- package/dist/cli-adapter.d.ts.map +1 -1
- package/dist/cli-adapter.js +383 -240
- package/dist/cli-adapter.js.map +1 -1
- package/dist/config/defaultConfig.d.ts +1 -0
- package/dist/config/defaultConfig.d.ts.map +1 -1
- package/dist/config/defaultConfig.js +3 -1
- package/dist/config/defaultConfig.js.map +1 -1
- package/dist/config/types.d.ts +1 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/types.js +1 -0
- package/dist/config/types.js.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/services/ai-service-client.d.ts +1 -1
- package/dist/services/ai-service-client.d.ts.map +1 -1
- package/dist/services/ai-service-client.js +7 -2
- package/dist/services/ai-service-client.js.map +1 -1
- package/dist/services/api-client.d.ts +6 -0
- package/dist/services/api-client.d.ts.map +1 -1
- package/dist/services/api-client.js +16 -0
- package/dist/services/api-client.js.map +1 -1
- package/dist/tools/command.d.ts.map +1 -1
- package/dist/tools/command.js +77 -25
- package/dist/tools/command.js.map +1 -1
- package/dist/tools/file-ops.d.ts.map +1 -1
- package/dist/tools/file-ops.js +28 -4
- package/dist/tools/file-ops.js.map +1 -1
- package/dist/tools/registry.d.ts +1 -0
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/registry.js +25 -1
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/task-complete.d.ts +3 -0
- package/dist/tools/task-complete.d.ts.map +1 -0
- package/dist/tools/task-complete.js +48 -0
- package/dist/tools/task-complete.js.map +1 -0
- package/dist/tools/types.d.ts +1 -0
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/tools/web-search.d.ts.map +1 -1
- package/dist/tools/web-search.js +16 -2
- package/dist/tools/web-search.js.map +1 -1
- package/dist/ui/components/AgentTimer.d.ts +7 -0
- package/dist/ui/components/AgentTimer.d.ts.map +1 -0
- package/dist/ui/components/AgentTimer.js +27 -0
- package/dist/ui/components/AgentTimer.js.map +1 -0
- package/dist/ui/components/App.d.ts +2 -0
- package/dist/ui/components/App.d.ts.map +1 -1
- package/dist/ui/components/App.js +229 -46
- package/dist/ui/components/App.js.map +1 -1
- package/dist/ui/components/InputBox.d.ts.map +1 -1
- package/dist/ui/components/InputBox.js +586 -130
- package/dist/ui/components/InputBox.js.map +1 -1
- package/dist/ui/components/InteractiveShell.d.ts +16 -0
- package/dist/ui/components/InteractiveShell.d.ts.map +1 -0
- package/dist/ui/components/InteractiveShell.js +152 -0
- package/dist/ui/components/InteractiveShell.js.map +1 -0
- package/dist/ui/components/LoadingIndicator.js +1 -1
- package/dist/ui/components/LoadingIndicator.js.map +1 -1
- package/dist/ui/components/MarkdownRenderer.js +1 -1
- package/dist/ui/components/StreamingMessageDisplay.js +1 -1
- package/dist/ui/components/StreamingMessageDisplay.js.map +1 -1
- package/dist/ui/components/ToolExecutionMessage.d.ts.map +1 -1
- package/dist/ui/components/ToolExecutionMessage.js +43 -47
- package/dist/ui/components/ToolExecutionMessage.js.map +1 -1
- package/dist/utils/ansi-encoder.d.ts +2 -0
- package/dist/utils/ansi-encoder.d.ts.map +1 -0
- package/dist/utils/ansi-encoder.js +57 -0
- package/dist/utils/ansi-encoder.js.map +1 -0
- package/dist/utils/command-history.d.ts +14 -0
- package/dist/utils/command-history.d.ts.map +1 -0
- package/dist/utils/command-history.js +140 -0
- package/dist/utils/command-history.js.map +1 -0
- package/dist/utils/input-classifier.d.ts +26 -0
- package/dist/utils/input-classifier.d.ts.map +1 -0
- package/dist/utils/input-classifier.js +154 -0
- package/dist/utils/input-classifier.js.map +1 -0
- package/dist/utils/markdown-parser.d.ts.map +1 -1
- package/dist/utils/markdown-parser.js +6 -5
- package/dist/utils/markdown-parser.js.map +1 -1
- package/dist/utils/shell.d.ts +7 -0
- package/dist/utils/shell.d.ts.map +1 -1
- package/dist/utils/shell.js +97 -37
- package/dist/utils/shell.js.map +1 -1
- package/models-config.json +30 -0
- package/package.json +2 -1
- package/prompts/system-prompt-autonomous.md +428 -0
- package/dist/tools/ToolRegistry.d.ts +0 -55
- package/dist/tools/ToolRegistry.d.ts.map +0 -1
- package/dist/tools/ToolRegistry.js +0 -600
- package/dist/tools/ToolRegistry.js.map +0 -1
- package/prompts/system-prompt-enhanced.md +0 -659
- package/prompts/system-prompt.md +0 -206
|
@@ -1,169 +1,547 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
1
|
+
import React, { useState, useEffect, useRef, useMemo } from 'react';
|
|
2
2
|
import { Box, Text, useInput } from 'ink';
|
|
3
|
-
import TextInput from 'ink-text-input';
|
|
4
3
|
import * as fs from 'fs';
|
|
5
4
|
import * as path from 'path';
|
|
6
5
|
import { Breadcrumbs } from './Breadcrumbs.js';
|
|
7
6
|
import { ContextWindowIndicator } from './ContextWindowIndicator.js';
|
|
7
|
+
import { detectIntent } from '../../utils/input-classifier.js';
|
|
8
|
+
import { CommandHistoryManager } from '../../utils/command-history.js';
|
|
9
|
+
const getVisualLines = (text, width) => {
|
|
10
|
+
const logicalLines = text.split('\n');
|
|
11
|
+
const visualLines = [];
|
|
12
|
+
let currentOffset = 0;
|
|
13
|
+
logicalLines.forEach((line, i) => {
|
|
14
|
+
if (line.length === 0) {
|
|
15
|
+
visualLines.push({ start: currentOffset, end: currentOffset, isHardEnd: true });
|
|
16
|
+
currentOffset += 1;
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
let remaining = line;
|
|
20
|
+
let lineStartOffset = currentOffset;
|
|
21
|
+
while (remaining.length > 0) {
|
|
22
|
+
let splitIndex = remaining.length;
|
|
23
|
+
let isHardEnd = false;
|
|
24
|
+
if (remaining.length > width) {
|
|
25
|
+
splitIndex = width;
|
|
26
|
+
const lastSpace = remaining.lastIndexOf(' ', width);
|
|
27
|
+
if (lastSpace > 0) {
|
|
28
|
+
splitIndex = lastSpace + 1;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
isHardEnd = true;
|
|
33
|
+
}
|
|
34
|
+
visualLines.push({
|
|
35
|
+
start: lineStartOffset,
|
|
36
|
+
end: lineStartOffset + splitIndex,
|
|
37
|
+
isHardEnd: isHardEnd
|
|
38
|
+
});
|
|
39
|
+
lineStartOffset += splitIndex;
|
|
40
|
+
remaining = remaining.slice(splitIndex);
|
|
41
|
+
}
|
|
42
|
+
currentOffset += line.length + (i < logicalLines.length - 1 ? 1 : 0);
|
|
43
|
+
});
|
|
44
|
+
return visualLines;
|
|
45
|
+
};
|
|
8
46
|
export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...', autoAcceptMode, model, planMode = false, commandMode = false, currentWorkingDirectory, commandHistory = [], onToggleAutoAccept, onToggleCommandMode, isActive = true, subshellContext, currentTokens = 0, maxTokens = 1000000 }) => {
|
|
9
47
|
const [value, setValue] = useState('');
|
|
48
|
+
const [cursorOffset, setCursorOffset] = useState(0);
|
|
10
49
|
const [completions, setCompletions] = useState([]);
|
|
11
50
|
const [completionIndex, setCompletionIndex] = useState(0);
|
|
12
|
-
const [inputKey, setInputKey] = useState(0);
|
|
13
51
|
const [historyIndex, setHistoryIndex] = useState(-1);
|
|
14
52
|
const [tempValue, setTempValue] = useState('');
|
|
15
|
-
const ignoreNextChangeRef =
|
|
53
|
+
const ignoreNextChangeRef = useRef(false);
|
|
54
|
+
// Auto Mode State
|
|
55
|
+
const [isAutoMode, setIsAutoMode] = useState(true);
|
|
56
|
+
const [detectedIntent, setDetectedIntent] = useState('ai');
|
|
57
|
+
// Autocomplete State
|
|
58
|
+
const [autocompleteSuggestion, setAutocompleteSuggestion] = useState(null);
|
|
59
|
+
// Undo/Redo State
|
|
60
|
+
const [undoStack, setUndoStack] = useState([]);
|
|
61
|
+
const [redoStack, setRedoStack] = useState([]);
|
|
62
|
+
// Selection State
|
|
63
|
+
const [selection, setSelection] = useState(null);
|
|
64
|
+
// Configuration for scrolling
|
|
65
|
+
const MAX_VISIBLE_LINES = 9;
|
|
66
|
+
// Load history on mount
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
CommandHistoryManager.getInstance().load();
|
|
69
|
+
}, []);
|
|
16
70
|
// Force clear value if it becomes empty or whitespace-only after external changes
|
|
17
|
-
|
|
18
|
-
if (value && value.trim() === '') {
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
if (value && value.trim() === '' && cursorOffset > 0) {
|
|
19
73
|
setValue('');
|
|
20
|
-
|
|
74
|
+
setCursorOffset(0);
|
|
21
75
|
}
|
|
22
|
-
}, [value]);
|
|
23
|
-
// Determine current working directory
|
|
24
|
-
const currentDir =
|
|
76
|
+
}, [value, cursorOffset]);
|
|
77
|
+
// Determine current working directory
|
|
78
|
+
const currentDir = useMemo(() => {
|
|
25
79
|
const cwd = currentWorkingDirectory || process.cwd();
|
|
26
|
-
// Truncate long paths to prevent layout shifts
|
|
27
80
|
if (cwd.length > 60) {
|
|
28
81
|
return '...' + cwd.slice(-57);
|
|
29
82
|
}
|
|
30
83
|
return cwd;
|
|
31
84
|
}, [currentWorkingDirectory]);
|
|
85
|
+
// Autocomplete Logic
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
if (!value || value.trim() === '') {
|
|
88
|
+
setAutocompleteSuggestion(null);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
// Only show suggestions if we are in command mode (manual or auto-detected)
|
|
92
|
+
// OR if we are in Auto mode and it looks like a command
|
|
93
|
+
const shouldSuggest = commandMode || (isAutoMode && detectedIntent === 'command');
|
|
94
|
+
if (shouldSuggest) {
|
|
95
|
+
const matches = CommandHistoryManager.getInstance().getMatches(value, currentDir);
|
|
96
|
+
if (matches.length > 0) {
|
|
97
|
+
setAutocompleteSuggestion(matches[0]);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
setAutocompleteSuggestion(null);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
setAutocompleteSuggestion(null);
|
|
105
|
+
}
|
|
106
|
+
}, [value, commandMode, isAutoMode, detectedIntent, currentDir]);
|
|
107
|
+
// Auto-classification effect (Synchronous Heuristics Only)
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
// Only run classification if in Auto Mode
|
|
110
|
+
if (!isAutoMode) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
// Only classify if value is non-empty and not just whitespace
|
|
114
|
+
const trimmedValue = value.trim();
|
|
115
|
+
if (!trimmedValue || !onToggleCommandMode) {
|
|
116
|
+
// Default to AI if empty
|
|
117
|
+
setDetectedIntent('ai');
|
|
118
|
+
if (commandMode)
|
|
119
|
+
onToggleCommandMode();
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
// Run local heuristic detection immediately
|
|
123
|
+
const intent = detectIntent(trimmedValue);
|
|
124
|
+
setDetectedIntent(intent);
|
|
125
|
+
// Switch mode based on intent
|
|
126
|
+
if (intent === 'command') {
|
|
127
|
+
if (!commandMode)
|
|
128
|
+
onToggleCommandMode();
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
// intent === 'ai'
|
|
132
|
+
if (commandMode)
|
|
133
|
+
onToggleCommandMode();
|
|
134
|
+
}
|
|
135
|
+
}, [value, commandMode, onToggleCommandMode, isAutoMode]);
|
|
136
|
+
const pushToUndoStack = () => {
|
|
137
|
+
setUndoStack(prev => [...prev, { value, cursorOffset }]);
|
|
138
|
+
setRedoStack([]); // Clear redo stack on new action
|
|
139
|
+
};
|
|
140
|
+
const handleUndo = () => {
|
|
141
|
+
if (undoStack.length === 0)
|
|
142
|
+
return;
|
|
143
|
+
const previousState = undoStack[undoStack.length - 1];
|
|
144
|
+
setRedoStack(prev => [...prev, { value, cursorOffset }]);
|
|
145
|
+
setUndoStack(prev => prev.slice(0, -1));
|
|
146
|
+
setValue(previousState.value);
|
|
147
|
+
setCursorOffset(previousState.cursorOffset);
|
|
148
|
+
setSelection(null);
|
|
149
|
+
};
|
|
32
150
|
useInput((input, key) => {
|
|
33
|
-
|
|
151
|
+
if (!isActive)
|
|
152
|
+
return;
|
|
153
|
+
// Detect OS for platform-specific key handling
|
|
154
|
+
const isWindows = process.platform === 'win32';
|
|
155
|
+
const inputCharCode = input ? input.charCodeAt(0) : null;
|
|
156
|
+
// DELETE WORD BACKWARDS - Check this FIRST before standard backspace/delete
|
|
157
|
+
// Triggers on any of these conditions:
|
|
158
|
+
// 1. Ctrl+W (char code 23) - Standard Unix terminal shortcut
|
|
159
|
+
// 2. Meta/Alt + Backspace/Delete - Mac alternative
|
|
160
|
+
// 3. Ctrl + Delete - Works on all platforms (Ctrl+Backspace doesn't work on Windows/Ink)
|
|
161
|
+
// 4. Windows Ctrl+Del sends char code 127
|
|
162
|
+
const isDeleteWord = inputCharCode === 23 || // Ctrl+W
|
|
163
|
+
(key.meta && (key.backspace || key.delete)) || // Meta/Alt + Backspace/Delete
|
|
164
|
+
(key.ctrl && key.delete) || // Ctrl+Delete (NOTE: Ctrl+Backspace not detectable on Windows/Ink)
|
|
165
|
+
(isWindows && inputCharCode === 127); // Windows: Ctrl+Del sends char 127
|
|
166
|
+
if (isDeleteWord) {
|
|
167
|
+
pushToUndoStack();
|
|
168
|
+
if (cursorOffset > 0) {
|
|
169
|
+
let newOffset = cursorOffset;
|
|
170
|
+
// Skip whitespace backwards
|
|
171
|
+
while (newOffset > 0 && /\s/.test(value[newOffset - 1])) {
|
|
172
|
+
newOffset--;
|
|
173
|
+
}
|
|
174
|
+
// Skip non-whitespace backwards
|
|
175
|
+
while (newOffset > 0 && !/\s/.test(value[newOffset - 1])) {
|
|
176
|
+
newOffset--;
|
|
177
|
+
}
|
|
178
|
+
const newValue = value.slice(0, newOffset) + value.slice(cursorOffset);
|
|
179
|
+
setValue(newValue);
|
|
180
|
+
setCursorOffset(newOffset);
|
|
181
|
+
}
|
|
182
|
+
setHistoryIndex(-1);
|
|
183
|
+
setCompletions([]);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
// Ctrl+T: Toggle auto-accept
|
|
34
187
|
if (key.ctrl && input.toLowerCase() === 't') {
|
|
35
188
|
ignoreNextChangeRef.current = true;
|
|
36
189
|
onToggleAutoAccept();
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
ignoreNextChangeRef.current = false;
|
|
40
|
-
}, 100);
|
|
41
|
-
return; // Return immediately to prevent any further processing
|
|
190
|
+
setTimeout(() => { ignoreNextChangeRef.current = false; }, 100);
|
|
191
|
+
return;
|
|
42
192
|
}
|
|
43
|
-
// Ctrl+D
|
|
193
|
+
// Ctrl+D: Cycle modes (Agent -> Terminal -> Auto -> Agent)
|
|
44
194
|
if (key.ctrl && input.toLowerCase() === 'd') {
|
|
45
195
|
if (onToggleCommandMode) {
|
|
46
196
|
ignoreNextChangeRef.current = true;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}, 100);
|
|
52
|
-
// Only remount if input is empty to prevent losing typed content
|
|
53
|
-
if (!value || value.trim() === '') {
|
|
54
|
-
setTimeout(() => {
|
|
55
|
-
setValue('');
|
|
56
|
-
setInputKey(prev => prev + 1);
|
|
57
|
-
}, 10);
|
|
197
|
+
// Cycle Logic
|
|
198
|
+
if (!isAutoMode && !commandMode) {
|
|
199
|
+
// Agent -> Terminal
|
|
200
|
+
onToggleCommandMode();
|
|
58
201
|
}
|
|
202
|
+
else if (!isAutoMode && commandMode) {
|
|
203
|
+
// Terminal -> Auto
|
|
204
|
+
setIsAutoMode(true);
|
|
205
|
+
// Trigger initial detection for Auto mode
|
|
206
|
+
const intent = detectIntent(value);
|
|
207
|
+
setDetectedIntent(intent);
|
|
208
|
+
// Set command mode based on intent
|
|
209
|
+
if (intent === 'ai' && commandMode)
|
|
210
|
+
onToggleCommandMode();
|
|
211
|
+
// if intent is command, we are already in command mode
|
|
212
|
+
}
|
|
213
|
+
else if (isAutoMode) {
|
|
214
|
+
// Auto -> Agent
|
|
215
|
+
setIsAutoMode(false);
|
|
216
|
+
// Ensure we go back to Agent mode (commandMode = false)
|
|
217
|
+
if (commandMode)
|
|
218
|
+
onToggleCommandMode();
|
|
219
|
+
}
|
|
220
|
+
setTimeout(() => { ignoreNextChangeRef.current = false; }, 100);
|
|
59
221
|
}
|
|
60
|
-
return;
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
// Ctrl+Z: Undo
|
|
225
|
+
if (key.ctrl && input.toLowerCase() === 'z') {
|
|
226
|
+
handleUndo();
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
// Ctrl+A: Select All
|
|
230
|
+
if (key.ctrl && input.toLowerCase() === 'a') {
|
|
231
|
+
setSelection({ start: 0, end: value.length });
|
|
232
|
+
setCursorOffset(value.length);
|
|
233
|
+
return;
|
|
61
234
|
}
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
235
|
+
// DELETE CHAR - Only runs if Delete Word did NOT trigger
|
|
236
|
+
// Triggers on:
|
|
237
|
+
// 1. Backspace or Delete key flag is present
|
|
238
|
+
// 2. OR char code 8 (standard backspace)
|
|
239
|
+
// 3. OR char code 127 (DEL) on non-Windows (Mac/Linux treat 127 as normal backspace)
|
|
240
|
+
const isDeleteChar = key.backspace ||
|
|
241
|
+
key.delete ||
|
|
242
|
+
inputCharCode === 8 ||
|
|
243
|
+
(!isWindows && inputCharCode === 127);
|
|
244
|
+
if (isDeleteChar) {
|
|
245
|
+
pushToUndoStack();
|
|
246
|
+
if (selection) {
|
|
247
|
+
// Delete selection
|
|
248
|
+
const start = Math.min(selection.start, selection.end);
|
|
249
|
+
const end = Math.max(selection.start, selection.end);
|
|
250
|
+
const newValue = value.slice(0, start) + value.slice(end);
|
|
251
|
+
setValue(newValue);
|
|
252
|
+
setCursorOffset(start);
|
|
253
|
+
setSelection(null);
|
|
254
|
+
}
|
|
255
|
+
else if (key.delete && cursorOffset < value.length) {
|
|
256
|
+
// Delete key: Delete character at cursor (forward delete)
|
|
257
|
+
const newValue = value.slice(0, cursorOffset) + value.slice(cursorOffset + 1);
|
|
258
|
+
setValue(newValue);
|
|
71
259
|
}
|
|
72
|
-
else if (
|
|
73
|
-
//
|
|
74
|
-
|
|
75
|
-
setValue(
|
|
76
|
-
|
|
260
|
+
else if (cursorOffset > 0) {
|
|
261
|
+
// Backspace: Delete character before cursor
|
|
262
|
+
const newValue = value.slice(0, cursorOffset - 1) + value.slice(cursorOffset);
|
|
263
|
+
setValue(newValue);
|
|
264
|
+
setCursorOffset(cursorOffset - 1);
|
|
77
265
|
}
|
|
266
|
+
// Reset history/completions on edit
|
|
267
|
+
setHistoryIndex(-1);
|
|
268
|
+
setCompletions([]);
|
|
78
269
|
return;
|
|
79
270
|
}
|
|
80
|
-
//
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
271
|
+
// Handle Enter
|
|
272
|
+
// Check input length to distinguish single key press from paste
|
|
273
|
+
if (key.return && input.length <= 1) {
|
|
274
|
+
// Check for Shift+Enter, Ctrl+Enter, OR explicit newline char
|
|
275
|
+
// Note: Shift+Enter doesn't work on Windows/Ink, so Ctrl+Enter is the alternative
|
|
276
|
+
// Alt+Enter can't be used as it fullscreens the terminal on Windows
|
|
277
|
+
if (key.shift || key.ctrl || input === '\n') {
|
|
278
|
+
pushToUndoStack();
|
|
279
|
+
// Shift+Enter or Ctrl+Enter: Insert newline
|
|
280
|
+
// If selection exists, replace it
|
|
281
|
+
let newValue = value;
|
|
282
|
+
let newOffset = cursorOffset;
|
|
283
|
+
if (selection) {
|
|
284
|
+
const start = Math.min(selection.start, selection.end);
|
|
285
|
+
const end = Math.max(selection.start, selection.end);
|
|
286
|
+
newValue = value.slice(0, start) + '\n' + value.slice(end);
|
|
287
|
+
newOffset = start + 1;
|
|
288
|
+
setSelection(null);
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
newValue = value.slice(0, cursorOffset) + '\n' + value.slice(cursorOffset);
|
|
292
|
+
newOffset = cursorOffset + 1;
|
|
293
|
+
}
|
|
294
|
+
setValue(newValue);
|
|
295
|
+
setCursorOffset(newOffset);
|
|
87
296
|
}
|
|
88
297
|
else {
|
|
89
|
-
//
|
|
90
|
-
|
|
91
|
-
setValue(tempValue);
|
|
92
|
-
setInputKey(prev => prev + 1);
|
|
298
|
+
// Enter: Submit
|
|
299
|
+
handleSubmit();
|
|
93
300
|
}
|
|
94
301
|
return;
|
|
95
302
|
}
|
|
96
|
-
//
|
|
97
|
-
if (key.
|
|
98
|
-
|
|
303
|
+
// Handle Arrows
|
|
304
|
+
if (key.upArrow || key.downArrow || key.leftArrow || key.rightArrow) {
|
|
305
|
+
// Clear selection on arrow keys
|
|
306
|
+
if (selection) {
|
|
307
|
+
setSelection(null);
|
|
308
|
+
// If left/right, maybe move cursor to start/end of selection?
|
|
309
|
+
// For now, just clear selection and let default logic run (or reset cursor to one end)
|
|
310
|
+
if (key.leftArrow)
|
|
311
|
+
setCursorOffset(Math.min(selection.start, selection.end));
|
|
312
|
+
if (key.rightArrow)
|
|
313
|
+
setCursorOffset(Math.max(selection.start, selection.end));
|
|
314
|
+
if (key.upArrow || key.downArrow) {
|
|
315
|
+
// Keep cursor where it is (at the end usually) or move it?
|
|
316
|
+
// Let's just fall through to normal arrow logic from current cursorOffset
|
|
317
|
+
}
|
|
318
|
+
if (key.leftArrow || key.rightArrow)
|
|
319
|
+
return; // We handled the move
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (key.upArrow) {
|
|
323
|
+
const width = (process.stdout.columns || 80) - 6;
|
|
324
|
+
const visualLines = getVisualLines(value, width);
|
|
325
|
+
let currentVisualLineIndex = visualLines.findIndex(line => {
|
|
326
|
+
if (cursorOffset >= line.start && cursorOffset < line.end)
|
|
327
|
+
return true;
|
|
328
|
+
if (cursorOffset === line.end && line.isHardEnd)
|
|
329
|
+
return true;
|
|
330
|
+
return false;
|
|
331
|
+
});
|
|
332
|
+
// If cursor is at the very end of the last line
|
|
333
|
+
if (currentVisualLineIndex === -1 && cursorOffset === value.length) {
|
|
334
|
+
currentVisualLineIndex = visualLines.length - 1;
|
|
335
|
+
}
|
|
336
|
+
if (currentVisualLineIndex <= 0) {
|
|
337
|
+
// Top line: History navigation
|
|
338
|
+
if (commandHistory.length > 0) {
|
|
339
|
+
if (historyIndex === -1) {
|
|
340
|
+
setTempValue(value);
|
|
341
|
+
const newIndex = commandHistory.length - 1;
|
|
342
|
+
setHistoryIndex(newIndex);
|
|
343
|
+
const newValue = commandHistory[newIndex];
|
|
344
|
+
setValue(newValue);
|
|
345
|
+
setCursorOffset(newValue.length);
|
|
346
|
+
}
|
|
347
|
+
else if (historyIndex > 0) {
|
|
348
|
+
const newIndex = historyIndex - 1;
|
|
349
|
+
setHistoryIndex(newIndex);
|
|
350
|
+
const newValue = commandHistory[newIndex];
|
|
351
|
+
setValue(newValue);
|
|
352
|
+
setCursorOffset(newValue.length);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
// Move cursor up one visual line
|
|
358
|
+
const currentLine = visualLines[currentVisualLineIndex];
|
|
359
|
+
const targetLine = visualLines[currentVisualLineIndex - 1];
|
|
360
|
+
const offsetInLine = cursorOffset - currentLine.start;
|
|
361
|
+
const newOffset = targetLine.start + Math.min(offsetInLine, targetLine.end - targetLine.start);
|
|
362
|
+
setCursorOffset(newOffset);
|
|
363
|
+
}
|
|
99
364
|
return;
|
|
100
365
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
366
|
+
if (key.downArrow) {
|
|
367
|
+
const width = (process.stdout.columns || 80) - 6;
|
|
368
|
+
const visualLines = getVisualLines(value, width);
|
|
369
|
+
let currentVisualLineIndex = visualLines.findIndex(line => {
|
|
370
|
+
if (cursorOffset >= line.start && cursorOffset < line.end)
|
|
371
|
+
return true;
|
|
372
|
+
if (cursorOffset === line.end && line.isHardEnd)
|
|
373
|
+
return true;
|
|
374
|
+
return false;
|
|
375
|
+
});
|
|
376
|
+
// If cursor is at the very end of the last line
|
|
377
|
+
if (currentVisualLineIndex === -1 && cursorOffset === value.length) {
|
|
378
|
+
currentVisualLineIndex = visualLines.length - 1;
|
|
379
|
+
}
|
|
380
|
+
if (currentVisualLineIndex === visualLines.length - 1) {
|
|
381
|
+
// Bottom line: History navigation
|
|
382
|
+
if (historyIndex !== -1) {
|
|
383
|
+
if (historyIndex < commandHistory.length - 1) {
|
|
384
|
+
const newIndex = historyIndex + 1;
|
|
385
|
+
setHistoryIndex(newIndex);
|
|
386
|
+
const newValue = commandHistory[newIndex];
|
|
387
|
+
setValue(newValue);
|
|
388
|
+
setCursorOffset(newValue.length);
|
|
389
|
+
}
|
|
390
|
+
else {
|
|
391
|
+
setHistoryIndex(-1);
|
|
392
|
+
setValue(tempValue);
|
|
393
|
+
setCursorOffset(tempValue.length);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
// Move cursor down one visual line
|
|
399
|
+
const currentLine = visualLines[currentVisualLineIndex];
|
|
400
|
+
const targetLine = visualLines[currentVisualLineIndex + 1];
|
|
401
|
+
const offsetInLine = cursorOffset - currentLine.start;
|
|
402
|
+
const newOffset = targetLine.start + Math.min(offsetInLine, targetLine.end - targetLine.start);
|
|
403
|
+
setCursorOffset(newOffset);
|
|
404
|
+
}
|
|
107
405
|
return;
|
|
108
406
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
407
|
+
if (key.leftArrow) {
|
|
408
|
+
if (key.ctrl) {
|
|
409
|
+
// Ctrl+Left: Move word backwards
|
|
410
|
+
let newOffset = cursorOffset;
|
|
411
|
+
if (newOffset > 0) {
|
|
412
|
+
// Skip whitespace backwards
|
|
413
|
+
while (newOffset > 0 && /\s/.test(value[newOffset - 1])) {
|
|
414
|
+
newOffset--;
|
|
415
|
+
}
|
|
416
|
+
// Skip non-whitespace backwards
|
|
417
|
+
while (newOffset > 0 && !/\s/.test(value[newOffset - 1])) {
|
|
418
|
+
newOffset--;
|
|
419
|
+
}
|
|
420
|
+
setCursorOffset(newOffset);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
else if (cursorOffset > 0) {
|
|
424
|
+
setCursorOffset(cursorOffset - 1);
|
|
425
|
+
}
|
|
113
426
|
return;
|
|
114
427
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
428
|
+
if (key.rightArrow) {
|
|
429
|
+
// Autocomplete Logic (Only at end of line)
|
|
430
|
+
if (autocompleteSuggestion && cursorOffset === value.length) {
|
|
431
|
+
if (key.ctrl) {
|
|
432
|
+
// Ctrl+Right: Accept FULL suggestion
|
|
433
|
+
setValue(autocompleteSuggestion);
|
|
434
|
+
setCursorOffset(autocompleteSuggestion.length);
|
|
435
|
+
setAutocompleteSuggestion(null);
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
else {
|
|
439
|
+
// Right: Accept NEXT WORD
|
|
440
|
+
const remaining = autocompleteSuggestion.slice(value.length);
|
|
441
|
+
// Match next chunk of non-whitespace (including preceding whitespace)
|
|
442
|
+
const match = remaining.match(/^(\s*\S+)/);
|
|
443
|
+
if (match) {
|
|
444
|
+
const toAdd = match[0];
|
|
445
|
+
const newValue = value + toAdd;
|
|
446
|
+
setValue(newValue);
|
|
447
|
+
setCursorOffset(newValue.length);
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
else if (remaining.length > 0) {
|
|
451
|
+
// Fallback: if only whitespace remains or regex fails, take it all
|
|
452
|
+
const newValue = value + remaining;
|
|
453
|
+
setValue(newValue);
|
|
454
|
+
setCursorOffset(newValue.length);
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
// Navigation Logic (if not completing)
|
|
460
|
+
if (key.ctrl) {
|
|
461
|
+
// Ctrl+Right: Move word forwards
|
|
462
|
+
let newOffset = cursorOffset;
|
|
463
|
+
if (newOffset < value.length) {
|
|
464
|
+
// Skip non-whitespace forwards
|
|
465
|
+
while (newOffset < value.length && !/\s/.test(value[newOffset])) {
|
|
466
|
+
newOffset++;
|
|
467
|
+
}
|
|
468
|
+
// Skip whitespace forwards
|
|
469
|
+
while (newOffset < value.length && /\s/.test(value[newOffset])) {
|
|
470
|
+
newOffset++;
|
|
471
|
+
}
|
|
472
|
+
setCursorOffset(newOffset);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
else if (cursorOffset < value.length) {
|
|
476
|
+
setCursorOffset(cursorOffset + 1);
|
|
477
|
+
}
|
|
119
478
|
return;
|
|
120
479
|
}
|
|
121
|
-
//
|
|
122
|
-
if (
|
|
123
|
-
|
|
124
|
-
|
|
480
|
+
// Tab Completion
|
|
481
|
+
if (key.tab && !key.shift) {
|
|
482
|
+
// Priority 1: Accept autocomplete suggestion
|
|
483
|
+
if (autocompleteSuggestion) {
|
|
484
|
+
setValue(autocompleteSuggestion);
|
|
485
|
+
setCursorOffset(autocompleteSuggestion.length);
|
|
486
|
+
setAutocompleteSuggestion(null);
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
// Priority 2: File completion (only in command mode)
|
|
490
|
+
if (commandMode) {
|
|
491
|
+
handleTabCompletion();
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
125
494
|
}
|
|
126
|
-
//
|
|
127
|
-
|
|
495
|
+
// Regular Input
|
|
496
|
+
// Ignore control keys to prevent printing garbage (like 'v' for Ctrl+V)
|
|
497
|
+
if (input && !key.ctrl && !key.meta) {
|
|
498
|
+
pushToUndoStack();
|
|
499
|
+
// Handle paste with newlines
|
|
500
|
+
const cleanedInput = input.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
501
|
+
let newValue = value;
|
|
502
|
+
let newOffset = cursorOffset;
|
|
503
|
+
if (selection) {
|
|
504
|
+
const start = Math.min(selection.start, selection.end);
|
|
505
|
+
const end = Math.max(selection.start, selection.end);
|
|
506
|
+
newValue = value.slice(0, start) + cleanedInput + value.slice(end);
|
|
507
|
+
newOffset = start + cleanedInput.length;
|
|
508
|
+
setSelection(null);
|
|
509
|
+
}
|
|
510
|
+
else {
|
|
511
|
+
newValue = value.slice(0, cursorOffset) + cleanedInput + value.slice(cursorOffset);
|
|
512
|
+
newOffset = cursorOffset + cleanedInput.length;
|
|
513
|
+
}
|
|
514
|
+
setValue(newValue);
|
|
515
|
+
setCursorOffset(newOffset);
|
|
516
|
+
// Reset history/completions
|
|
128
517
|
setHistoryIndex(-1);
|
|
129
|
-
|
|
518
|
+
setCompletions([]);
|
|
130
519
|
}
|
|
131
|
-
|
|
132
|
-
// ink-text-input doesn't handle newlines well, so we keep them in the value
|
|
133
|
-
setValue(newValue);
|
|
134
|
-
};
|
|
520
|
+
}, { isActive });
|
|
135
521
|
const handleTabCompletion = async () => {
|
|
136
522
|
if (!value)
|
|
137
523
|
return;
|
|
138
|
-
// Get the last word (path) from the command
|
|
139
524
|
const words = value.split(' ');
|
|
140
525
|
const lastWord = words[words.length - 1];
|
|
141
|
-
// If we have existing completions, cycle through them
|
|
142
526
|
if (completions.length > 0) {
|
|
143
527
|
const nextIndex = (completionIndex + 1) % completions.length;
|
|
144
528
|
setCompletionIndex(nextIndex);
|
|
145
|
-
// Replace the last word with the next completion
|
|
146
529
|
words[words.length - 1] = completions[nextIndex];
|
|
147
530
|
const newValue = words.join(' ');
|
|
148
|
-
// Force remount to reset cursor position
|
|
149
531
|
setValue(newValue);
|
|
150
|
-
|
|
532
|
+
setCursorOffset(newValue.length);
|
|
151
533
|
return;
|
|
152
534
|
}
|
|
153
|
-
// Get completions for the current path
|
|
154
535
|
try {
|
|
155
536
|
const cwd = currentWorkingDirectory || process.cwd();
|
|
156
537
|
let searchDir = cwd;
|
|
157
538
|
let searchPattern = lastWord;
|
|
158
539
|
let dirPart = '';
|
|
159
|
-
// If the path contains a directory separator, split it
|
|
160
540
|
if (lastWord.includes('/') || lastWord.includes('\\')) {
|
|
161
541
|
const lastSep = Math.max(lastWord.lastIndexOf('/'), lastWord.lastIndexOf('\\'));
|
|
162
542
|
dirPart = lastWord.substring(0, lastSep + 1);
|
|
163
543
|
searchPattern = lastWord.substring(lastSep + 1);
|
|
164
|
-
// For subshells, use their path format
|
|
165
544
|
if (subshellContext && subshellContext.type !== 'local') {
|
|
166
|
-
// Use forward slashes for Unix-like paths
|
|
167
545
|
searchDir = dirPart.startsWith('/') ? dirPart.slice(0, -1) : cwd + '/' + dirPart.slice(0, -1);
|
|
168
546
|
}
|
|
169
547
|
else {
|
|
@@ -171,27 +549,19 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
171
549
|
}
|
|
172
550
|
}
|
|
173
551
|
let entries;
|
|
174
|
-
// Check if we're in a subshell context
|
|
175
552
|
if (subshellContext && subshellContext.type !== 'local' && subshellContext.handler) {
|
|
176
|
-
// Use the subshell handler to list directory
|
|
177
553
|
const dirEntries = await subshellContext.handler.listDirectory(searchDir);
|
|
178
|
-
entries = dirEntries.map(entry => ({
|
|
179
|
-
name: entry.name,
|
|
180
|
-
type: entry.type
|
|
181
|
-
}));
|
|
554
|
+
entries = dirEntries.map(entry => ({ name: entry.name, type: entry.type }));
|
|
182
555
|
}
|
|
183
556
|
else {
|
|
184
|
-
|
|
185
|
-
if (!fs.existsSync(searchDir)) {
|
|
557
|
+
if (!fs.existsSync(searchDir))
|
|
186
558
|
return;
|
|
187
|
-
}
|
|
188
559
|
const fsEntries = fs.readdirSync(searchDir, { withFileTypes: true });
|
|
189
560
|
entries = fsEntries.map(entry => ({
|
|
190
561
|
name: entry.name,
|
|
191
562
|
type: entry.isDirectory() ? 'directory' : 'file'
|
|
192
563
|
}));
|
|
193
564
|
}
|
|
194
|
-
// Filter matches
|
|
195
565
|
const matches = entries
|
|
196
566
|
.filter(entry => entry.name.toLowerCase().startsWith(searchPattern.toLowerCase()))
|
|
197
567
|
.map(entry => {
|
|
@@ -200,48 +570,132 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
200
570
|
return entry.type === 'directory' ? fullPath + separator : fullPath;
|
|
201
571
|
})
|
|
202
572
|
.sort();
|
|
203
|
-
if (matches.length
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
setValue(newValue);
|
|
209
|
-
setInputKey(prev => prev + 1);
|
|
210
|
-
setCompletions([]);
|
|
211
|
-
}
|
|
212
|
-
else if (matches.length > 1) {
|
|
213
|
-
// Multiple matches - set up for cycling
|
|
214
|
-
setCompletions(matches);
|
|
215
|
-
setCompletionIndex(0);
|
|
573
|
+
if (matches.length > 0) {
|
|
574
|
+
if (matches.length > 1) {
|
|
575
|
+
setCompletions(matches);
|
|
576
|
+
setCompletionIndex(0);
|
|
577
|
+
}
|
|
216
578
|
words[words.length - 1] = matches[0];
|
|
217
579
|
const newValue = words.join(' ');
|
|
218
|
-
// Force remount to reset cursor position
|
|
219
580
|
setValue(newValue);
|
|
220
|
-
|
|
581
|
+
setCursorOffset(newValue.length);
|
|
221
582
|
}
|
|
222
583
|
}
|
|
223
584
|
catch (error) {
|
|
224
|
-
// Ignore errors
|
|
585
|
+
// Ignore errors
|
|
225
586
|
}
|
|
226
587
|
};
|
|
227
588
|
const handleSubmit = () => {
|
|
228
|
-
|
|
229
|
-
if (!isActive) {
|
|
589
|
+
if (!isActive)
|
|
230
590
|
return;
|
|
231
|
-
}
|
|
232
591
|
const trimmedValue = value.trim();
|
|
233
592
|
if (trimmedValue) {
|
|
593
|
+
// Save to history if it was a command
|
|
594
|
+
if (commandMode) {
|
|
595
|
+
CommandHistoryManager.getInstance().addCommand(trimmedValue, currentDir);
|
|
596
|
+
}
|
|
234
597
|
onSubmit(trimmedValue);
|
|
235
598
|
setValue('');
|
|
599
|
+
setCursorOffset(0);
|
|
236
600
|
setCompletions([]);
|
|
237
601
|
setCompletionIndex(0);
|
|
238
602
|
setHistoryIndex(-1);
|
|
239
603
|
setTempValue('');
|
|
240
|
-
|
|
604
|
+
setUndoStack([]);
|
|
605
|
+
setRedoStack([]);
|
|
606
|
+
setSelection(null);
|
|
607
|
+
setAutocompleteSuggestion(null);
|
|
608
|
+
}
|
|
609
|
+
};
|
|
610
|
+
// Rendering Logic with Scrolling
|
|
611
|
+
const renderInput = () => {
|
|
612
|
+
const lines = value.split('\n');
|
|
613
|
+
// If empty, show placeholder
|
|
614
|
+
if (lines.length === 1 && lines[0] === '') {
|
|
615
|
+
return React.createElement(Text, { color: "gray" }, placeholder);
|
|
616
|
+
}
|
|
617
|
+
// Calculate cursor line and column
|
|
618
|
+
let currentPos = 0;
|
|
619
|
+
let cursorLine = 0;
|
|
620
|
+
let cursorCol = 0;
|
|
621
|
+
for (let i = 0; i < lines.length; i++) {
|
|
622
|
+
if (currentPos + lines[i].length >= cursorOffset) {
|
|
623
|
+
cursorLine = i;
|
|
624
|
+
cursorCol = cursorOffset - currentPos;
|
|
625
|
+
break;
|
|
626
|
+
}
|
|
627
|
+
currentPos += lines[i].length + 1; // +1 for newline
|
|
628
|
+
}
|
|
629
|
+
// Edge case: cursor at very end of content
|
|
630
|
+
if (cursorOffset === value.length && lines.length > 0) {
|
|
631
|
+
cursorLine = lines.length - 1;
|
|
632
|
+
cursorCol = lines[lines.length - 1].length;
|
|
633
|
+
}
|
|
634
|
+
// Calculate visible range
|
|
635
|
+
let startLine = 0;
|
|
636
|
+
if (lines.length > MAX_VISIBLE_LINES) {
|
|
637
|
+
if (cursorLine < MAX_VISIBLE_LINES) {
|
|
638
|
+
startLine = 0;
|
|
639
|
+
}
|
|
640
|
+
else {
|
|
641
|
+
startLine = cursorLine - MAX_VISIBLE_LINES + 1;
|
|
642
|
+
}
|
|
241
643
|
}
|
|
644
|
+
const endLine = Math.min(startLine + MAX_VISIBLE_LINES, lines.length);
|
|
645
|
+
const visibleLines = lines.slice(startLine, endLine);
|
|
646
|
+
return (React.createElement(Box, { flexDirection: "column", flexGrow: 1 },
|
|
647
|
+
startLine > 0 && React.createElement(Text, { color: "gray" }, "\u2191 ..."),
|
|
648
|
+
visibleLines.map((line, idx) => {
|
|
649
|
+
const actualLineIndex = startLine + idx;
|
|
650
|
+
const isCursorLine = actualLineIndex === cursorLine;
|
|
651
|
+
const isLastLine = actualLineIndex === lines.length - 1;
|
|
652
|
+
// Calculate absolute position of this line start
|
|
653
|
+
let lineStartPos = 0;
|
|
654
|
+
for (let k = 0; k < actualLineIndex; k++)
|
|
655
|
+
lineStartPos += lines[k].length + 1;
|
|
656
|
+
if (!isActive) {
|
|
657
|
+
if (line.length === 0) {
|
|
658
|
+
return React.createElement(Text, { key: idx }, " ");
|
|
659
|
+
}
|
|
660
|
+
return React.createElement(Text, { key: idx }, line);
|
|
661
|
+
}
|
|
662
|
+
// Render with selection and cursor
|
|
663
|
+
const chars = line.split('');
|
|
664
|
+
const renderedChars = chars.map((char, charIdx) => {
|
|
665
|
+
const absPos = lineStartPos + charIdx;
|
|
666
|
+
const isSelected = selection &&
|
|
667
|
+
absPos >= Math.min(selection.start, selection.end) &&
|
|
668
|
+
absPos < Math.max(selection.start, selection.end);
|
|
669
|
+
const isCursor = isCursorLine && charIdx === cursorCol;
|
|
670
|
+
if (isCursor) {
|
|
671
|
+
return React.createElement(Text, { key: charIdx, inverse: true, color: isSelected ? "yellow" : undefined }, char);
|
|
672
|
+
}
|
|
673
|
+
if (isSelected) {
|
|
674
|
+
return React.createElement(Text, { key: charIdx, backgroundColor: "white", color: "black" }, char);
|
|
675
|
+
}
|
|
676
|
+
return React.createElement(Text, { key: charIdx }, char);
|
|
677
|
+
});
|
|
678
|
+
// Handle cursor at end of line
|
|
679
|
+
if (isCursorLine && cursorCol === line.length) {
|
|
680
|
+
renderedChars.push(React.createElement(Text, { key: "cursor", inverse: true }, " "));
|
|
681
|
+
}
|
|
682
|
+
// Render Autocomplete Ghost Text
|
|
683
|
+
// Only on the last line, if suggestion exists and matches start
|
|
684
|
+
if (isLastLine && autocompleteSuggestion && autocompleteSuggestion.startsWith(value)) {
|
|
685
|
+
const suffix = autocompleteSuggestion.slice(value.length);
|
|
686
|
+
if (suffix) {
|
|
687
|
+
renderedChars.push(React.createElement(Text, { key: "ghost", color: "gray" }, suffix));
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
if (renderedChars.length === 0) {
|
|
691
|
+
return React.createElement(Text, { key: idx }, " ");
|
|
692
|
+
}
|
|
693
|
+
return React.createElement(Text, { key: idx }, renderedChars);
|
|
694
|
+
}),
|
|
695
|
+
endLine < lines.length && React.createElement(Text, { color: "gray" }, "\u2193 ...")));
|
|
242
696
|
};
|
|
243
|
-
return (React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: "#
|
|
244
|
-
React.createElement(Box, { marginY: 1, justifyContent: "space-between" },
|
|
697
|
+
return (React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: commandMode ? "#00cc66" : "#257aa5ff", paddingX: 1, paddingY: 0, width: "100%" },
|
|
698
|
+
React.createElement(Box, { marginY: 1, justifyContent: "space-between", width: "100%" },
|
|
245
699
|
React.createElement(Box, null,
|
|
246
700
|
subshellContext && subshellContext.type !== 'local' && (React.createElement(Breadcrumbs, { context: subshellContext })),
|
|
247
701
|
React.createElement(Text, { color: "#666666" }, "CWD: "),
|
|
@@ -252,13 +706,15 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
|
|
|
252
706
|
React.createElement(Text, { color: "#00ccff" }, model))),
|
|
253
707
|
React.createElement(Box, null,
|
|
254
708
|
React.createElement(Text, { color: "#666666" }, "Mode: "),
|
|
255
|
-
|
|
256
|
-
|
|
709
|
+
isAutoMode ? (React.createElement(Text, null,
|
|
710
|
+
React.createElement(Text, { color: "#00ccff", bold: true }, "Auto ["),
|
|
711
|
+
detectedIntent === 'command' ? (React.createElement(Text, { color: "#00cc66", bold: true }, "Terminal")) : (React.createElement(Text, { color: "#00ccff" }, "Agent")),
|
|
712
|
+
React.createElement(Text, { color: "#00ccff", bold: true }, "]"))) : commandMode ? (React.createElement(Text, { color: "#00cc66", bold: true }, "Terminal")) : planMode ? (React.createElement(Text, { color: "#ffaa00", bold: true }, "Plan")) : (React.createElement(Text, { color: "#00ccff" }, "Agent"))))),
|
|
713
|
+
React.createElement(Box, { flexDirection: "row", width: "100%" },
|
|
257
714
|
React.createElement(Text, { color: "#666666" }, "> "),
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
React.createElement(Text, { color: "#666666", dimColor: true }, commandMode ? ('Ctrl+D to exit command mode • Tab for autocomplete') : ('Ctrl+D for command mode • Ctrl+T to toggle auto-accept')),
|
|
715
|
+
renderInput()),
|
|
716
|
+
React.createElement(Box, { marginY: 1, justifyContent: "space-between", width: "100%" },
|
|
717
|
+
React.createElement(Text, { color: "#666666", dimColor: true }, isAutoMode ? ('Ctrl+D to cycle modes • Auto-detecting intent') : commandMode ? ('Ctrl+D to cycle modes • Tab for autocomplete') : ('Ctrl+D to cycle modes • Shift+Enter for new line')),
|
|
262
718
|
React.createElement(Box, { gap: 1 },
|
|
263
719
|
!commandMode && autoAcceptMode ? (React.createElement(Text, { color: "#00cc66", bold: true }, "[AUTO-ACCEPT: ON]")) : !commandMode ? (React.createElement(Text, { color: "#666666", dimColor: true }, "[AUTO-ACCEPT: OFF]")) : null,
|
|
264
720
|
!commandMode && (React.createElement(Box, { marginLeft: 1 },
|