centaurus-cli 2.8.1 → 2.8.2

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.
@@ -2,91 +2,95 @@ import React, { useState } from 'react';
2
2
  import { Box, Text, useInput } from 'ink';
3
3
  import { wrapText } from '../../utils/markdown-parser.js';
4
4
  // Maximum number of lines to show before truncating in diff view
5
- // Reduced to 5 to prevent screen overflow and UI glitching
6
- const MAX_PREVIEW_LINES = 5;
5
+ // Using 10 lines to match FileCreationPreview
6
+ const MAX_PREVIEW_LINES = 10;
7
7
  // Maximum number of lines to show in full view before truncating
8
8
  const MAX_FULL_VIEW_LINES = 1000;
9
9
  export const DiffViewer = ({ diff, filePath, fullDiff }) => {
10
10
  const [showFull, setShowFull] = useState(false);
11
11
  useInput((input, key) => {
12
- if (fullDiff && input === 'o' && key.ctrl) {
12
+ if (input === 'o' && key.ctrl) {
13
13
  setShowFull(prev => !prev);
14
14
  }
15
15
  });
16
- const renderDiffContent = (diffText, maxLines) => {
17
- const lines = diffText.split('\n');
18
- // Parse diff to find starting line numbers
19
- let currentLineNum = 1; // Default if no header found
20
- // Filter out diff metadata lines and count changes
21
- const contentLines = [];
16
+ // Process content into displayable lines (wrapped and styled)
17
+ const renderDiffContent = (content, maxLines) => {
18
+ const lines = content.split('\n');
19
+ const processedLines = [];
20
+ // Parse diff logic
21
+ let currentLineNum = 1;
22
+ // Calculate max content width
23
+ // Terminal width - borders (2) - padding (2) - line number (5 with space)
24
+ const terminalWidth = process.stdout.columns || 80;
25
+ const maxContentWidth = Math.max(40, terminalWidth - 11);
22
26
  for (const line of lines) {
23
27
  const trimmed = line.trim();
24
- // Handle diff headers to update line number
28
+ // Handle diff headers and hunk headers
25
29
  if (line.startsWith('@@')) {
26
- // Parse @@ -old,count +new,count @@
27
30
  const match = line.match(/@@ \-\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
28
31
  if (match && match[1]) {
29
32
  currentLineNum = parseInt(match[1], 10);
30
33
  }
31
- continue; // Skip the header line itself in display
34
+ // In full view or if this is relevant context, we generally want to show it
35
+ // But for compactness in preview, sometimes hunks are skipped if we implemented smart slicing
36
+ // Here we just render everything linearly
32
37
  }
33
- // Skip other metadata
38
+ // Skip generic diff metadata if it's not useful code context
34
39
  if (line.startsWith('---') || line.startsWith('+++'))
35
40
  continue;
36
41
  if (line.startsWith('Index:'))
37
42
  continue;
38
43
  if (trimmed.startsWith('==='))
39
44
  continue;
40
- // Process content lines
45
+ let type = 'context';
46
+ let lineNum = undefined;
41
47
  if (line.startsWith('+')) {
42
- contentLines.push({ text: line.slice(1), type: 'added', lineNum: currentLineNum++ });
48
+ type = 'added';
49
+ lineNum = currentLineNum++;
43
50
  }
44
51
  else if (line.startsWith('-')) {
45
- contentLines.push({ text: line.slice(1), type: 'deleted' });
46
- // Do not increment currentLineNum for deletions in the new file
52
+ type = 'deleted';
53
+ // No line num increment for deleted lines in new file version
47
54
  }
48
55
  else {
49
- // Context line
50
- contentLines.push({ text: line.slice(1), type: 'context', lineNum: currentLineNum++ });
56
+ type = 'context';
57
+ lineNum = currentLineNum++;
51
58
  }
59
+ // Determine style
60
+ let style = { color: 'white', backgroundColor: undefined };
61
+ if (type === 'added')
62
+ style = { backgroundColor: undefined, color: 'green' }; // Clean look: colored text
63
+ if (type === 'deleted')
64
+ style = { backgroundColor: undefined, color: 'red' };
65
+ // Format line number
66
+ const lineNumStr = lineNum ? lineNum.toString().padStart(4, ' ') : ' ';
67
+ const cleanText = (line.startsWith('+') || line.startsWith('-') || line.startsWith(' ')) ? line.slice(1) : line;
68
+ // Wrap text
69
+ const wrapped = wrapText(cleanText || ' ', maxContentWidth, 0);
70
+ wrapped.forEach((wLine, idx) => {
71
+ processedLines.push({
72
+ text: wLine,
73
+ ...style,
74
+ lineNumStr: idx === 0 ? lineNumStr : ' '
75
+ });
76
+ });
52
77
  }
53
- // Count additions and deletions
54
- const addedCount = contentLines.filter(l => l.type === 'added').length;
55
- const deletedCount = contentLines.filter(l => l.type === 'deleted').length;
56
- // Determine if we need to truncate
57
- const totalLines = contentLines.length;
78
+ // Determine truncation
79
+ const totalLines = processedLines.length;
58
80
  const shouldTruncate = totalLines > maxLines;
59
- const displayLines = shouldTruncate ? contentLines.slice(0, maxLines) : contentLines;
81
+ const displayLines = shouldTruncate ? processedLines.slice(0, maxLines) : processedLines;
60
82
  const hiddenLines = shouldTruncate ? totalLines - maxLines : 0;
61
- const getLineStyle = (type) => {
62
- if (type === 'added')
63
- return { backgroundColor: 'green', color: 'black' };
64
- if (type === 'deleted')
65
- return { backgroundColor: 'red', color: 'black' };
66
- return { color: 'white' };
67
- };
68
- // Generate summary text
83
+ // Stats for summary
84
+ const addedCount = (content.match(/^\+/gm) || []).length;
85
+ const deletedCount = (content.match(/^\-/gm) || []).length;
69
86
  const addedText = addedCount === 1 ? '1 line added' : `${addedCount} lines added`;
70
87
  const deletedText = deletedCount === 1 ? '1 line deleted' : `${deletedCount} lines deleted`;
71
- // Calculate available width for content
72
- // Terminal width - borders (2) - padding (2) - line number (5 with space)
73
- const terminalWidth = process.stdout.columns || 80;
74
- const maxContentWidth = Math.max(40, terminalWidth - 11);
75
88
  return (React.createElement(React.Fragment, null,
76
- displayLines.map((line, idx) => {
77
- const style = getLineStyle(line.type);
78
- // Format line number: fixed width 4 chars
79
- const lineNumStr = line.lineNum ? line.lineNum.toString().padStart(4, ' ') : ' ';
80
- // Use wrapText utility to wrap long lines properly
81
- const text = line.text || ' ';
82
- const wrappedLines = wrapText(text, maxContentWidth, 0);
83
- return wrappedLines.map((wrappedLine, lineIdx) => (React.createElement(Box, { key: `${idx}-${lineIdx}`, flexDirection: "column" },
84
- React.createElement(Box, { flexDirection: "row" },
85
- React.createElement(Text, { color: "gray" },
86
- lineIdx === 0 ? lineNumStr : ' ',
87
- ' '),
88
- React.createElement(Text, { ...style }, wrappedLine)))));
89
- }).flat(),
89
+ displayLines.map((line, idx) => (React.createElement(Box, { key: idx, flexDirection: "row" },
90
+ React.createElement(Text, { color: "gray" },
91
+ line.lineNumStr,
92
+ " "),
93
+ React.createElement(Text, { color: line.color, backgroundColor: line.backgroundColor }, line.text)))),
90
94
  shouldTruncate && (React.createElement(Box, { marginTop: 1 },
91
95
  React.createElement(Text, { color: "#ffaa00", dimColor: true },
92
96
  "... ",
@@ -95,20 +99,27 @@ export const DiffViewer = ({ diff, filePath, fullDiff }) => {
95
99
  hiddenLines === 1 ? 'line' : 'lines',
96
100
  " not shown ..."))),
97
101
  React.createElement(Box, { marginTop: 1, marginBottom: 1 },
98
- React.createElement(Text, { color: "#666666" }, '─'.repeat(80))),
102
+ React.createElement(Text, { color: "#666666" }, '─'.repeat(terminalWidth - 4))),
99
103
  React.createElement(Box, null,
100
104
  React.createElement(Text, { color: "green" }, addedText),
101
105
  React.createElement(Text, { color: "#666666" }, ", "),
102
106
  React.createElement(Text, { color: "red" }, deletedText))));
103
107
  };
108
+ // Determine which content to show
109
+ // If showFull is true AND fullDiff is available, show fullDiff
110
+ // If showFull is true but no fullDiff, show expanded regular diff
111
+ // Otherwise show limited regular diff
112
+ const contentToShow = (showFull && fullDiff) ? fullDiff : diff;
113
+ const limit = showFull ? MAX_FULL_VIEW_LINES : MAX_PREVIEW_LINES;
104
114
  return (React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: "#ffaa00", paddingX: 1 },
105
115
  React.createElement(Box, { justifyContent: "space-between" },
106
- React.createElement(Text, { color: "#ffffff", bold: true }, showFull ? 'Full File Preview' : 'Diff Preview'),
107
- fullDiff && (React.createElement(Text, { color: "#666666", dimColor: true }, showFull ? 'Press Ctrl+O for Diff' : 'Press Ctrl+O for Full File'))),
116
+ React.createElement(Text, { color: "#ffffff", bold: true },
117
+ "Diff Preview: ",
118
+ filePath || 'Untitled'),
119
+ React.createElement(Box, null,
120
+ React.createElement(Text, { color: "#666666", dimColor: true }, showFull ? 'Ctrl+O: Collapse' : 'Ctrl+O: Full File'))),
108
121
  React.createElement(Box, { marginTop: 1, marginBottom: 1 },
109
- React.createElement(Text, { color: "#666666" }, '─'.repeat(80))),
110
- showFull && fullDiff
111
- ? renderDiffContent(fullDiff, MAX_FULL_VIEW_LINES)
112
- : renderDiffContent(diff, MAX_PREVIEW_LINES)));
122
+ React.createElement(Text, { color: "#666666" }, '─'.repeat((process.stdout.columns || 80) - 4))),
123
+ renderDiffContent(contentToShow, limit)));
113
124
  };
114
125
  //# sourceMappingURL=DiffViewer.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"DiffViewer.js","sourceRoot":"","sources":["../../../src/ui/components/DiffViewer.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"DiffViewer.js","sourceRoot":"","sources":["../../../src/ui/components/DiffViewer.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ interface FileTagAutocompleteProps {
3
+ files: Array<{
4
+ name: string;
5
+ isDirectory: boolean;
6
+ }>;
7
+ selectedIndex: number;
8
+ }
9
+ export declare const FileTagAutocomplete: React.FC<FileTagAutocompleteProps>;
10
+ export {};
11
+ //# sourceMappingURL=FileTagAutocomplete.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FileTagAutocomplete.d.ts","sourceRoot":"","sources":["../../../src/ui/components/FileTagAutocomplete.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,UAAU,wBAAwB;IAC9B,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IACrD,aAAa,EAAE,MAAM,CAAC;CACzB;AAED,eAAO,MAAM,mBAAmB,EAAE,KAAK,CAAC,EAAE,CAAC,wBAAwB,CA+ClE,CAAC"}
@@ -0,0 +1,27 @@
1
+ import React from 'react';
2
+ import { Box, Text } from 'ink';
3
+ export const FileTagAutocomplete = ({ files, selectedIndex }) => {
4
+ if (files.length === 0)
5
+ return null;
6
+ // Limit to 6 suggestions
7
+ const displayFiles = files.slice(0, 6);
8
+ return (React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: "#00ccff", paddingX: 1, marginLeft: 1, marginTop: 0 },
9
+ React.createElement(Box, { marginBottom: 0 },
10
+ React.createElement(Text, { color: "#666666", dimColor: true }, "Files matching @...")),
11
+ displayFiles.map((file, index) => {
12
+ const isSelected = index === selectedIndex;
13
+ const icon = file.isDirectory ? '📁' : '📄';
14
+ return (React.createElement(Box, { key: file.name, paddingX: 1 },
15
+ React.createElement(Text, { color: isSelected ? '#00ccff' : '#aaaaaa', bold: isSelected, inverse: isSelected },
16
+ icon,
17
+ " ",
18
+ file.name,
19
+ file.isDirectory ? '/' : '')));
20
+ }),
21
+ files.length > 6 && (React.createElement(Box, { marginTop: 0 },
22
+ React.createElement(Text, { color: "#666666", dimColor: true },
23
+ "...and ",
24
+ files.length - 6,
25
+ " more")))));
26
+ };
27
+ //# sourceMappingURL=FileTagAutocomplete.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FileTagAutocomplete.js","sourceRoot":"","sources":["../../../src/ui/components/FileTagAutocomplete.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAOhC,MAAM,CAAC,MAAM,mBAAmB,GAAuC,CAAC,EACpE,KAAK,EACL,aAAa,EAChB,EAAE,EAAE;IACD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEpC,yBAAyB;IACzB,MAAM,YAAY,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAEvC,OAAO,CACH,oBAAC,GAAG,IACA,aAAa,EAAC,QAAQ,EACtB,WAAW,EAAC,OAAO,EACnB,WAAW,EAAC,SAAS,EACrB,QAAQ,EAAE,CAAC,EACX,UAAU,EAAE,CAAC,EACb,SAAS,EAAE,CAAC;QAEZ,oBAAC,GAAG,IAAC,YAAY,EAAE,CAAC;YAChB,oBAAC,IAAI,IAAC,KAAK,EAAC,SAAS,EAAC,QAAQ,gCAA2B,CACvD;QACL,YAAY,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;YAC9B,MAAM,UAAU,GAAG,KAAK,KAAK,aAAa,CAAC;YAC3C,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;YAE5C,OAAO,CACH,oBAAC,GAAG,IACA,GAAG,EAAE,IAAI,CAAC,IAAI,EACd,QAAQ,EAAE,CAAC;gBAEX,oBAAC,IAAI,IACD,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,EACzC,IAAI,EAAE,UAAU,EAChB,OAAO,EAAE,UAAU;oBAElB,IAAI;;oBAAG,IAAI,CAAC,IAAI;oBAAE,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAC3C,CACL,CACT,CAAC;QACN,CAAC,CAAC;QACD,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,CACjB,oBAAC,GAAG,IAAC,SAAS,EAAE,CAAC;YACb,oBAAC,IAAI,IAAC,KAAK,EAAC,SAAS,EAAC,QAAQ;;gBAAS,KAAK,CAAC,MAAM,GAAG,CAAC;wBAAa,CAClE,CACT,CACC,CACT,CAAC;AACN,CAAC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"InputBox.d.ts","sourceRoot":"","sources":["../../../src/ui/components/InputBox.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA+C,MAAM,OAAO,CAAC;AAIpE,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAUzD,UAAU,aAAa;IACrB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,OAAO,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,kBAAkB,EAAE,MAAM,IAAI,CAAC;IAC/B,mBAAmB,CAAC,EAAE,MAAM,IAAI,CAAC;IACjC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA+CD,eAAO,MAAM,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC,aAAa,CA06B3C,CAAC"}
1
+ {"version":3,"file":"InputBox.d.ts","sourceRoot":"","sources":["../../../src/ui/components/InputBox.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA+C,MAAM,OAAO,CAAC;AAIpE,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAWzD,UAAU,aAAa;IACrB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,OAAO,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,kBAAkB,EAAE,MAAM,IAAI,CAAC;IAC/B,mBAAmB,CAAC,EAAE,MAAM,IAAI,CAAC;IACjC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA+CD,eAAO,MAAM,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC,aAAa,CAgoC3C,CAAC"}
@@ -7,6 +7,7 @@ import { ContextWindowIndicator } from './ContextWindowIndicator.js';
7
7
  import { detectIntent } from '../../utils/input-classifier.js';
8
8
  import { CommandHistoryManager } from '../../utils/command-history.js';
9
9
  import { SlashCommandAutocomplete } from './SlashCommandAutocomplete.js';
10
+ import { FileTagAutocomplete } from './FileTagAutocomplete.js';
10
11
  import { filterCommands } from '../../config/slash-commands.js';
11
12
  const getVisualLines = (text, width) => {
12
13
  const logicalLines = text.split('\n');
@@ -67,6 +68,12 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
67
68
  const [slashAutocompleteVisible, setSlashAutocompleteVisible] = useState(false);
68
69
  const [slashAutocompleteCommands, setSlashAutocompleteCommands] = useState([]);
69
70
  const [slashAutocompleteSelectedIndex, setSlashAutocompleteSelectedIndex] = useState(0);
71
+ // File Tag Autocomplete State (@ symbol)
72
+ const [fileTagAutocompleteVisible, setFileTagAutocompleteVisible] = useState(false);
73
+ const [fileTagSuggestions, setFileTagSuggestions] = useState([]);
74
+ const [fileTagSelectedIndex, setFileTagSelectedIndex] = useState(0);
75
+ const [activeFileTagStart, setActiveFileTagStart] = useState(null);
76
+ const [confirmedFileTags, setConfirmedFileTags] = useState([]);
70
77
  // Configuration for scrolling
71
78
  const MAX_VISIBLE_LINES = 9;
72
79
  // Load history on mount
@@ -139,6 +146,82 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
139
146
  onToggleCommandMode();
140
147
  }
141
148
  }, [value, commandMode, onToggleCommandMode, isAutoMode]);
149
+ // Helper function to get files matching a query from the current directory
150
+ const getMatchingFiles = (query) => {
151
+ try {
152
+ const cwd = currentWorkingDirectory || process.cwd();
153
+ if (!fs.existsSync(cwd))
154
+ return [];
155
+ const entries = fs.readdirSync(cwd, { withFileTypes: true });
156
+ const lowerQuery = query.toLowerCase();
157
+ return entries
158
+ .filter(entry => {
159
+ // Exclude hidden files (starting with .)
160
+ if (entry.name.startsWith('.'))
161
+ return false;
162
+ // Match case-insensitive
163
+ return entry.name.toLowerCase().includes(lowerQuery);
164
+ })
165
+ .map(entry => ({
166
+ name: entry.name,
167
+ isDirectory: entry.isDirectory()
168
+ }))
169
+ .sort((a, b) => {
170
+ // Prioritize files that start with the query
171
+ const aStarts = a.name.toLowerCase().startsWith(lowerQuery);
172
+ const bStarts = b.name.toLowerCase().startsWith(lowerQuery);
173
+ if (aStarts && !bStarts)
174
+ return -1;
175
+ if (!aStarts && bStarts)
176
+ return 1;
177
+ return a.name.localeCompare(b.name);
178
+ })
179
+ .slice(0, 10); // Get more than 6 initially, UI will limit display
180
+ }
181
+ catch (error) {
182
+ return [];
183
+ }
184
+ };
185
+ // File tag (@) detection effect
186
+ useEffect(() => {
187
+ // Don't show file tag autocomplete in command mode
188
+ if (commandMode) {
189
+ setFileTagAutocompleteVisible(false);
190
+ return;
191
+ }
192
+ // Find if cursor is within a potential file tag
193
+ // Look backwards from cursor to find @
194
+ let atPosition = -1;
195
+ for (let i = cursorOffset - 1; i >= 0; i--) {
196
+ const char = value[i];
197
+ // Stop if we hit whitespace or another special char
198
+ if (/[\s\n]/.test(char))
199
+ break;
200
+ if (char === '@') {
201
+ atPosition = i;
202
+ break;
203
+ }
204
+ }
205
+ if (atPosition === -1) {
206
+ setFileTagAutocompleteVisible(false);
207
+ setActiveFileTagStart(null);
208
+ return;
209
+ }
210
+ // Extract the query after @
211
+ const query = value.slice(atPosition + 1, cursorOffset);
212
+ // Get matching files
213
+ const matches = getMatchingFiles(query);
214
+ if (matches.length > 0) {
215
+ setFileTagSuggestions(matches);
216
+ setFileTagAutocompleteVisible(true);
217
+ setActiveFileTagStart(atPosition);
218
+ setFileTagSelectedIndex(0);
219
+ }
220
+ else {
221
+ setFileTagAutocompleteVisible(false);
222
+ setActiveFileTagStart(null);
223
+ }
224
+ }, [value, cursorOffset, commandMode, currentWorkingDirectory]);
142
225
  const pushToUndoStack = () => {
143
226
  setUndoStack(prev => [...prev, { value, cursorOffset }]);
144
227
  setRedoStack([]); // Clear redo stack on new action
@@ -209,6 +292,79 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
209
292
  return;
210
293
  }
211
294
  }
295
+ // Handle file tag (@) autocomplete navigation
296
+ if (fileTagAutocompleteVisible) {
297
+ if (key.downArrow) {
298
+ setFileTagSelectedIndex(prev => Math.min(prev + 1, Math.min(fileTagSuggestions.length - 1, 5)));
299
+ return;
300
+ }
301
+ if (key.upArrow) {
302
+ setFileTagSelectedIndex(prev => Math.max(prev - 1, 0));
303
+ return;
304
+ }
305
+ if (key.return && input.length <= 1 && !key.shift && !key.ctrl) {
306
+ // Select the highlighted file
307
+ const selected = fileTagSuggestions[fileTagSelectedIndex];
308
+ if (selected && activeFileTagStart !== null) {
309
+ pushToUndoStack();
310
+ // Replace @query with @filename
311
+ const beforeTag = value.slice(0, activeFileTagStart);
312
+ const afterCursor = value.slice(cursorOffset);
313
+ const fileName = selected.name + (selected.isDirectory ? '/' : '');
314
+ const newValue = beforeTag + '@' + fileName + ' ' + afterCursor;
315
+ const newCursorPos = activeFileTagStart + 1 + fileName.length + 1;
316
+ setValue(newValue);
317
+ setCursorOffset(newCursorPos);
318
+ // Add to confirmed file tags
319
+ setConfirmedFileTags(prev => [
320
+ ...prev.filter(tag =>
321
+ // Remove any overlapping tags
322
+ !(tag.start >= activeFileTagStart && tag.start <= cursorOffset)),
323
+ {
324
+ start: activeFileTagStart,
325
+ end: activeFileTagStart + 1 + fileName.length,
326
+ fileName: selected.name
327
+ }
328
+ ]);
329
+ setFileTagAutocompleteVisible(false);
330
+ setActiveFileTagStart(null);
331
+ }
332
+ return;
333
+ }
334
+ if (key.escape) {
335
+ setFileTagAutocompleteVisible(false);
336
+ setActiveFileTagStart(null);
337
+ return;
338
+ }
339
+ // If Tab is pressed, also select the current file
340
+ if (key.tab && !key.shift) {
341
+ const selected = fileTagSuggestions[fileTagSelectedIndex];
342
+ if (selected && activeFileTagStart !== null) {
343
+ pushToUndoStack();
344
+ const beforeTag = value.slice(0, activeFileTagStart);
345
+ const afterCursor = value.slice(cursorOffset);
346
+ const fileName = selected.name + (selected.isDirectory ? '/' : '');
347
+ const newValue = beforeTag + '@' + fileName + afterCursor;
348
+ const newCursorPos = activeFileTagStart + 1 + fileName.length;
349
+ setValue(newValue);
350
+ setCursorOffset(newCursorPos);
351
+ // If it's a directory, keep dropdown open for subdirectory navigation
352
+ if (!selected.isDirectory) {
353
+ setConfirmedFileTags(prev => [
354
+ ...prev.filter(tag => !(tag.start >= activeFileTagStart && tag.start <= cursorOffset)),
355
+ {
356
+ start: activeFileTagStart,
357
+ end: activeFileTagStart + 1 + fileName.length,
358
+ fileName: selected.name
359
+ }
360
+ ]);
361
+ setFileTagAutocompleteVisible(false);
362
+ setActiveFileTagStart(null);
363
+ }
364
+ }
365
+ return;
366
+ }
367
+ }
212
368
  // DELETE WORD BACKWARDS - Check this FIRST before standard backspace/delete
213
369
  // Triggers on any of these conditions:
214
370
  // 1. Ctrl+W (char code 23) - Standard Unix terminal shortcut
@@ -728,7 +884,22 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
728
884
  if (commandMode) {
729
885
  CommandHistoryManager.getInstance().addCommand(trimmedValue, currentDir);
730
886
  }
731
- onSubmit(trimmedValue);
887
+ // Resolve file tags (@filename -> absolute path)
888
+ let resolvedValue = trimmedValue;
889
+ if (!commandMode) {
890
+ const cwd = currentWorkingDirectory || process.cwd();
891
+ // Find all @filename patterns (@ followed by non-whitespace)
892
+ resolvedValue = trimmedValue.replace(/@([^\s@]+)/g, (match, fileName) => {
893
+ // Check if file exists
894
+ const filePath = path.resolve(cwd, fileName);
895
+ if (fs.existsSync(filePath)) {
896
+ return filePath;
897
+ }
898
+ // If file doesn't exist, keep original
899
+ return match;
900
+ });
901
+ }
902
+ onSubmit(resolvedValue);
732
903
  setValue('');
733
904
  setCursorOffset(0);
734
905
  setCompletions([]);
@@ -739,6 +910,7 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
739
910
  setRedoStack([]);
740
911
  setSelection(null);
741
912
  setAutocompleteSuggestion(null);
913
+ setConfirmedFileTags([]); // Clear confirmed tags on submit
742
914
  }
743
915
  };
744
916
  // Rendering Logic with Scrolling
@@ -800,6 +972,12 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
800
972
  const isSelected = selection &&
801
973
  absPos >= Math.min(selection.start, selection.end) &&
802
974
  absPos < Math.max(selection.start, selection.end);
975
+ // Check if this character is part of an active file tag (being typed after @)
976
+ const isInActiveFileTag = activeFileTagStart !== null &&
977
+ absPos >= activeFileTagStart &&
978
+ absPos < cursorOffset;
979
+ // Check if this character is part of a confirmed file tag
980
+ const isInConfirmedFileTag = confirmedFileTags.some(tag => absPos >= tag.start && absPos < tag.end);
803
981
  const isCursor = isCursorLine && charIdx === cursorCol;
804
982
  if (isCursor) {
805
983
  return React.createElement(Text, { key: charIdx, inverse: true, color: isSelected ? "yellow" : undefined }, char);
@@ -807,6 +985,10 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
807
985
  if (isSelected) {
808
986
  return React.createElement(Text, { key: charIdx, backgroundColor: "white", color: "black" }, char);
809
987
  }
988
+ // Highlight file tags with cyan color
989
+ if (isInConfirmedFileTag || isInActiveFileTag) {
990
+ return React.createElement(Text, { key: charIdx, color: "#00ccff", bold: true }, char);
991
+ }
810
992
  return React.createElement(Text, { key: charIdx }, char);
811
993
  });
812
994
  // Handle cursor at end of line
@@ -854,6 +1036,7 @@ export const InputBox = React.memo(({ onSubmit, placeholder = 'Ask anything...',
854
1036
  !commandMode && autoAcceptMode ? (React.createElement(Text, { color: "#00cc66", bold: true }, "[AUTO-ACCEPT: ON]")) : !commandMode ? (React.createElement(Text, { color: "#666666", dimColor: true }, "[AUTO-ACCEPT: OFF]")) : null,
855
1037
  !commandMode && (React.createElement(Box, { marginLeft: 1 },
856
1038
  React.createElement(ContextWindowIndicator, { currentTokens: currentTokens, maxTokens: maxTokens }))))),
857
- slashAutocompleteVisible && (React.createElement(SlashCommandAutocomplete, { commands: slashAutocompleteCommands, selectedIndex: slashAutocompleteSelectedIndex }))));
1039
+ slashAutocompleteVisible && (React.createElement(SlashCommandAutocomplete, { commands: slashAutocompleteCommands, selectedIndex: slashAutocompleteSelectedIndex })),
1040
+ fileTagAutocompleteVisible && (React.createElement(FileTagAutocomplete, { files: fileTagSuggestions, selectedIndex: fileTagSelectedIndex }))));
858
1041
  });
859
1042
  //# sourceMappingURL=InputBox.js.map