fraude-code 0.1.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/README.md +68 -0
- package/dist/index.js +179297 -0
- package/package.json +88 -0
- package/src/agent/agent.ts +475 -0
- package/src/agent/contextManager.ts +141 -0
- package/src/agent/index.ts +14 -0
- package/src/agent/pendingChanges.ts +270 -0
- package/src/agent/prompts/AskPrompt.txt +10 -0
- package/src/agent/prompts/FastPrompt.txt +40 -0
- package/src/agent/prompts/PlannerPrompt.txt +51 -0
- package/src/agent/prompts/ReviewerPrompt.txt +57 -0
- package/src/agent/prompts/WorkerPrompt.txt +33 -0
- package/src/agent/subagents/askAgent.ts +37 -0
- package/src/agent/subagents/extractionAgent.ts +123 -0
- package/src/agent/subagents/fastAgent.ts +45 -0
- package/src/agent/subagents/managerAgent.ts +36 -0
- package/src/agent/subagents/relationAgent.ts +76 -0
- package/src/agent/subagents/researchSubAgent.ts +79 -0
- package/src/agent/subagents/reviewerSubAgent.ts +42 -0
- package/src/agent/subagents/workerSubAgent.ts +42 -0
- package/src/agent/tools/bashTool.ts +94 -0
- package/src/agent/tools/descriptions/bash.txt +47 -0
- package/src/agent/tools/descriptions/edit.txt +7 -0
- package/src/agent/tools/descriptions/glob.txt +4 -0
- package/src/agent/tools/descriptions/grep.txt +8 -0
- package/src/agent/tools/descriptions/lsp.txt +20 -0
- package/src/agent/tools/descriptions/plan.txt +3 -0
- package/src/agent/tools/descriptions/read.txt +9 -0
- package/src/agent/tools/descriptions/todo.txt +12 -0
- package/src/agent/tools/descriptions/write.txt +8 -0
- package/src/agent/tools/editTool.ts +44 -0
- package/src/agent/tools/globTool.ts +59 -0
- package/src/agent/tools/grepTool.ts +343 -0
- package/src/agent/tools/lspTool.ts +429 -0
- package/src/agent/tools/planTool.ts +118 -0
- package/src/agent/tools/readTool.ts +78 -0
- package/src/agent/tools/rememberTool.ts +91 -0
- package/src/agent/tools/testRunnerTool.ts +77 -0
- package/src/agent/tools/testTool.ts +44 -0
- package/src/agent/tools/todoTool.ts +224 -0
- package/src/agent/tools/writeTool.ts +33 -0
- package/src/commands/COMMANDS.ts +38 -0
- package/src/commands/cerebras/auth.ts +27 -0
- package/src/commands/cerebras/index.ts +31 -0
- package/src/commands/forget.ts +29 -0
- package/src/commands/google/auth.ts +24 -0
- package/src/commands/google/index.ts +31 -0
- package/src/commands/groq/add_model.ts +60 -0
- package/src/commands/groq/auth.ts +24 -0
- package/src/commands/groq/index.ts +33 -0
- package/src/commands/index.ts +65 -0
- package/src/commands/knowledge.ts +92 -0
- package/src/commands/log.ts +32 -0
- package/src/commands/mistral/auth.ts +27 -0
- package/src/commands/mistral/index.ts +31 -0
- package/src/commands/model/index.ts +145 -0
- package/src/commands/models/index.ts +16 -0
- package/src/commands/ollama/index.ts +29 -0
- package/src/commands/openrouter/add_model.ts +64 -0
- package/src/commands/openrouter/auth.ts +24 -0
- package/src/commands/openrouter/index.ts +33 -0
- package/src/commands/remember.ts +48 -0
- package/src/commands/serve.ts +31 -0
- package/src/commands/session/index.ts +21 -0
- package/src/commands/usage.ts +15 -0
- package/src/commands/visualize.ts +773 -0
- package/src/components/App.tsx +55 -0
- package/src/components/IntroComponent.tsx +70 -0
- package/src/components/LoaderComponent.tsx +68 -0
- package/src/components/OutputRenderer.tsx +88 -0
- package/src/components/SettingsRenderer.tsx +23 -0
- package/src/components/input/CommandSuggestions.tsx +41 -0
- package/src/components/input/FileSuggestions.tsx +61 -0
- package/src/components/input/InputBox.tsx +371 -0
- package/src/components/output/CheckpointView.tsx +13 -0
- package/src/components/output/CommandView.tsx +13 -0
- package/src/components/output/CommentView.tsx +12 -0
- package/src/components/output/ConfirmationView.tsx +179 -0
- package/src/components/output/ContextUsage.tsx +62 -0
- package/src/components/output/DiffView.tsx +202 -0
- package/src/components/output/ErrorView.tsx +14 -0
- package/src/components/output/InteractiveServerView.tsx +69 -0
- package/src/components/output/KnowledgeView.tsx +220 -0
- package/src/components/output/MarkdownView.tsx +15 -0
- package/src/components/output/ModelSelectView.tsx +71 -0
- package/src/components/output/ReasoningView.tsx +21 -0
- package/src/components/output/ToolCallView.tsx +45 -0
- package/src/components/settings/ModelList.tsx +250 -0
- package/src/components/settings/TokenUsage.tsx +274 -0
- package/src/config/schema.ts +19 -0
- package/src/config/settings.ts +229 -0
- package/src/index.tsx +100 -0
- package/src/parsers/tree-sitter-python.wasm +0 -0
- package/src/providers/providers.ts +71 -0
- package/src/services/PluginLoader.ts +123 -0
- package/src/services/cerebras.ts +69 -0
- package/src/services/embeddingService.ts +229 -0
- package/src/services/google.ts +65 -0
- package/src/services/graphSerializer.ts +248 -0
- package/src/services/groq.ts +23 -0
- package/src/services/knowledgeOrchestrator.ts +286 -0
- package/src/services/mistral.ts +79 -0
- package/src/services/ollama.ts +109 -0
- package/src/services/openrouter.ts +23 -0
- package/src/services/symbolExtractor.ts +277 -0
- package/src/store/useFraudeStore.ts +123 -0
- package/src/store/useSettingsStore.ts +38 -0
- package/src/theme.ts +26 -0
- package/src/types/Agent.ts +147 -0
- package/src/types/CommandDefinition.ts +8 -0
- package/src/types/Model.ts +94 -0
- package/src/types/OutputItem.ts +24 -0
- package/src/types/PluginContext.ts +55 -0
- package/src/types/TokenUsage.ts +5 -0
- package/src/types/assets.d.ts +4 -0
- package/src/utils/agentCognition.ts +1152 -0
- package/src/utils/fileSuggestions.ts +111 -0
- package/src/utils/index.ts +17 -0
- package/src/utils/initFraude.ts +8 -0
- package/src/utils/logger.ts +24 -0
- package/src/utils/lspClient.ts +1415 -0
- package/src/utils/paths.ts +24 -0
- package/src/utils/queryHandler.ts +227 -0
- package/src/utils/router.ts +278 -0
- package/src/utils/streamHandler.ts +132 -0
- package/src/utils/treeSitterQueries.ts +125 -0
- package/tsconfig.json +33 -0
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import { useState, useMemo, useCallback, useEffect } from "react";
|
|
2
|
+
import { useApp, Box, Text, useInput } from "ink";
|
|
3
|
+
import { TextInput } from "@inkjs/ui";
|
|
4
|
+
import QueryHandler from "@/utils/queryHandler";
|
|
5
|
+
import useSettingsStore from "@/store/useSettingsStore";
|
|
6
|
+
import useFraudeStore from "@/store/useFraudeStore";
|
|
7
|
+
import CommandCenter from "@/commands";
|
|
8
|
+
import { addHistory } from "@/config/settings";
|
|
9
|
+
import CommandSuggestions from "./CommandSuggestions";
|
|
10
|
+
import FileSuggestions from "./FileSuggestions";
|
|
11
|
+
import {
|
|
12
|
+
getFileSuggestions,
|
|
13
|
+
type FileSuggestion,
|
|
14
|
+
} from "@/utils/fileSuggestions";
|
|
15
|
+
import { shortenPath } from "@/utils";
|
|
16
|
+
|
|
17
|
+
import { getModelDisplayId } from "@/types/Model";
|
|
18
|
+
import { THEME } from "@/theme";
|
|
19
|
+
|
|
20
|
+
const InputBoxComponent = () => {
|
|
21
|
+
const { exit } = useApp();
|
|
22
|
+
const [inputKey, setInputKey] = useState(0);
|
|
23
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
24
|
+
const [currentInput, setCurrentInput] = useState("");
|
|
25
|
+
const [historyIndex, setHistoryIndex] = useState(-1);
|
|
26
|
+
const [allFiles, setAllFiles] = useState<FileSuggestion[]>([]);
|
|
27
|
+
const [scrollOffset, setScrollOffset] = useState(0);
|
|
28
|
+
const [ghostIndex, setGhostIndex] = useState(0);
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
getFileSuggestions(process.cwd()).then(setAllFiles);
|
|
32
|
+
}, []);
|
|
33
|
+
|
|
34
|
+
const history = useSettingsStore((state) => state.history);
|
|
35
|
+
const models = useSettingsStore((state) => state.models);
|
|
36
|
+
// Create display identifiers that include provider to differentiate same-name models
|
|
37
|
+
const modelNames = useMemo(
|
|
38
|
+
() => models.map((m) => getModelDisplayId(m)).sort(),
|
|
39
|
+
[models],
|
|
40
|
+
);
|
|
41
|
+
const MAX_VISIBLE_SUGGESTIONS = 5;
|
|
42
|
+
|
|
43
|
+
const suggestions = useMemo(() => {
|
|
44
|
+
const allCommands = CommandCenter.getAllCommands();
|
|
45
|
+
const suggestions = allCommands.map((s) => {
|
|
46
|
+
if (s.usage?.includes("<model-name>")) {
|
|
47
|
+
const options = [];
|
|
48
|
+
for (const modelName of modelNames) {
|
|
49
|
+
const renderedUsage = s.usage.replace("<model-name>", modelName);
|
|
50
|
+
options.push(renderedUsage);
|
|
51
|
+
}
|
|
52
|
+
return { ...s, renderedOptions: options };
|
|
53
|
+
}
|
|
54
|
+
return s;
|
|
55
|
+
});
|
|
56
|
+
return suggestions;
|
|
57
|
+
}, [modelNames]);
|
|
58
|
+
|
|
59
|
+
const fileTokenMatch = useMemo(
|
|
60
|
+
() => currentInput.match(/@([^ ]*)$/),
|
|
61
|
+
[currentInput],
|
|
62
|
+
);
|
|
63
|
+
const isFileMode = !!fileTokenMatch;
|
|
64
|
+
const fileQuery = fileTokenMatch ? fileTokenMatch[1] : "";
|
|
65
|
+
const filePrefix = fileTokenMatch
|
|
66
|
+
? currentInput.slice(0, fileTokenMatch.index! + 1)
|
|
67
|
+
: "";
|
|
68
|
+
|
|
69
|
+
const fileDropdownSuggestions = useMemo(() => {
|
|
70
|
+
if (!isFileMode || !allFiles.length) return [];
|
|
71
|
+
return allFiles.filter((f) =>
|
|
72
|
+
f.path.toLowerCase().includes(fileQuery!.toLowerCase()),
|
|
73
|
+
);
|
|
74
|
+
}, [isFileMode, allFiles, fileQuery]);
|
|
75
|
+
const dropdownSuggestions = useMemo(() => {
|
|
76
|
+
if (!currentInput.startsWith("/")) return [];
|
|
77
|
+
const filteredTemplates = suggestions
|
|
78
|
+
.filter((s) => {
|
|
79
|
+
if (s.renderedOptions && s.renderedOptions.length > 0) {
|
|
80
|
+
const check = s.renderedOptions.find((option) => {
|
|
81
|
+
return option.startsWith(currentInput);
|
|
82
|
+
});
|
|
83
|
+
return check != undefined;
|
|
84
|
+
}
|
|
85
|
+
return s.usage?.startsWith(currentInput) ?? false;
|
|
86
|
+
})
|
|
87
|
+
.slice(0, MAX_VISIBLE_SUGGESTIONS);
|
|
88
|
+
return filteredTemplates;
|
|
89
|
+
}, [suggestions, currentInput]);
|
|
90
|
+
|
|
91
|
+
const dynamicSuggestions = useMemo(() => {
|
|
92
|
+
const allSuggestions = suggestions.flatMap((s) => {
|
|
93
|
+
if (s.renderedOptions && s.renderedOptions.length > 0) {
|
|
94
|
+
return s.renderedOptions;
|
|
95
|
+
}
|
|
96
|
+
return s.usage || "";
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const dropdownSuggestion = dropdownSuggestions[selectedIndex];
|
|
100
|
+
if (dropdownSuggestion) {
|
|
101
|
+
// Put the dropdown suggestion first so it shows as ghost text
|
|
102
|
+
const renderedSuggestion =
|
|
103
|
+
dropdownSuggestion.renderedOptions &&
|
|
104
|
+
dropdownSuggestion.renderedOptions.length > 0
|
|
105
|
+
? dropdownSuggestion.renderedOptions.find((option) =>
|
|
106
|
+
option.startsWith(currentInput),
|
|
107
|
+
)
|
|
108
|
+
: dropdownSuggestion.usage || "";
|
|
109
|
+
if (renderedSuggestion) {
|
|
110
|
+
return [
|
|
111
|
+
renderedSuggestion,
|
|
112
|
+
...allSuggestions.filter((s) => s !== renderedSuggestion),
|
|
113
|
+
];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// File suggestions
|
|
118
|
+
if (isFileMode && fileDropdownSuggestions.length > 0) {
|
|
119
|
+
const selectedFile = fileDropdownSuggestions[selectedIndex];
|
|
120
|
+
if (selectedFile) {
|
|
121
|
+
return [filePrefix + selectedFile.path + " "];
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return allSuggestions;
|
|
126
|
+
}, [
|
|
127
|
+
suggestions,
|
|
128
|
+
dropdownSuggestions,
|
|
129
|
+
selectedIndex,
|
|
130
|
+
currentInput,
|
|
131
|
+
isFileMode,
|
|
132
|
+
fileDropdownSuggestions,
|
|
133
|
+
filePrefix,
|
|
134
|
+
]);
|
|
135
|
+
|
|
136
|
+
const hiddenSuggestions = useMemo(() => {
|
|
137
|
+
if (currentInput.startsWith("/")) {
|
|
138
|
+
return new Set(
|
|
139
|
+
dynamicSuggestions
|
|
140
|
+
.filter((s) => s.startsWith(currentInput))
|
|
141
|
+
.slice(0, ghostIndex),
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
return new Set();
|
|
145
|
+
}, [dynamicSuggestions, currentInput, ghostIndex]);
|
|
146
|
+
|
|
147
|
+
// Calculate what ghost text the TextInput is ACTUALLY showing
|
|
148
|
+
// This mirrors TextInput's internal logic: first suggestion that starts with input
|
|
149
|
+
const actualGhostTextSuggestion = useMemo(() => {
|
|
150
|
+
if (currentInput.length === 0) return null;
|
|
151
|
+
const match = dynamicSuggestions.find((s) => s.startsWith(currentInput));
|
|
152
|
+
if (!match) return null;
|
|
153
|
+
const currentArg = currentInput.split(" ").length;
|
|
154
|
+
const dropdownSuggestion = dropdownSuggestions[selectedIndex];
|
|
155
|
+
if (
|
|
156
|
+
dropdownSuggestion?.usage &&
|
|
157
|
+
dropdownSuggestion.usage.split(" ")[currentArg] !== "<model-name>"
|
|
158
|
+
) {
|
|
159
|
+
return dropdownSuggestion.usage.replace(/<[^>]+>.*$/, "");
|
|
160
|
+
}
|
|
161
|
+
return match.replace(/<[^>]+>.*$/, "");
|
|
162
|
+
}, [currentInput, dynamicSuggestions, selectedIndex, dropdownSuggestions]);
|
|
163
|
+
|
|
164
|
+
useInput((input, key) => {
|
|
165
|
+
if (key.tab) {
|
|
166
|
+
if (isFileMode && fileDropdownSuggestions.length > 0) {
|
|
167
|
+
const selectedFile = fileDropdownSuggestions[selectedIndex];
|
|
168
|
+
if (selectedFile) {
|
|
169
|
+
const suggestion = filePrefix + selectedFile.path + " ";
|
|
170
|
+
if (suggestion !== currentInput) {
|
|
171
|
+
setCurrentInput(suggestion);
|
|
172
|
+
setInputKey((k) => k + 1);
|
|
173
|
+
setHistoryIndex(-1);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (
|
|
180
|
+
actualGhostTextSuggestion &&
|
|
181
|
+
actualGhostTextSuggestion.toLowerCase() != currentInput.toLowerCase()
|
|
182
|
+
) {
|
|
183
|
+
setCurrentInput(actualGhostTextSuggestion);
|
|
184
|
+
setInputKey((k) => k + 1);
|
|
185
|
+
setHistoryIndex(-1);
|
|
186
|
+
} else {
|
|
187
|
+
useFraudeStore.setState({
|
|
188
|
+
executionMode: ((useFraudeStore.getState().executionMode + 1) % 3) as
|
|
189
|
+
| 0
|
|
190
|
+
| 1
|
|
191
|
+
| 2,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// If input starts with "/" and there are multiple suggestions, use arrow keys for command dropdown
|
|
198
|
+
if (currentInput.startsWith("/") && dropdownSuggestions.length > 1) {
|
|
199
|
+
if (key.upArrow && selectedIndex > 0) {
|
|
200
|
+
setSelectedIndex(selectedIndex - 1);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (key.downArrow && selectedIndex < dropdownSuggestions.length - 1) {
|
|
204
|
+
const newIndex = selectedIndex + 1;
|
|
205
|
+
setSelectedIndex(newIndex);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (currentInput.startsWith("/") && dropdownSuggestions.length === 1) {
|
|
211
|
+
const filtered = dynamicSuggestions.filter((s) =>
|
|
212
|
+
s.startsWith(currentInput),
|
|
213
|
+
);
|
|
214
|
+
const isModelArg =
|
|
215
|
+
dropdownSuggestions[0]?.usage.includes("<model-name>") ?? false;
|
|
216
|
+
if (isModelArg && filtered.length > 0) {
|
|
217
|
+
setGhostIndex((ghostIndex) => (ghostIndex + 1) % filtered.length);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Input starts with @ (or contains @ at end)
|
|
223
|
+
if (isFileMode && fileDropdownSuggestions.length > 1) {
|
|
224
|
+
const listLen = fileDropdownSuggestions.length;
|
|
225
|
+
if (key.upArrow) {
|
|
226
|
+
const newIndex = selectedIndex - 1;
|
|
227
|
+
if (newIndex >= 0) {
|
|
228
|
+
setSelectedIndex(newIndex);
|
|
229
|
+
if (newIndex < scrollOffset) {
|
|
230
|
+
setScrollOffset(newIndex);
|
|
231
|
+
} else if (newIndex >= scrollOffset + MAX_VISIBLE_SUGGESTIONS) {
|
|
232
|
+
setScrollOffset(newIndex - MAX_VISIBLE_SUGGESTIONS + 1);
|
|
233
|
+
}
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (key.downArrow) {
|
|
238
|
+
const newIndex = selectedIndex + 1;
|
|
239
|
+
if (newIndex < listLen) {
|
|
240
|
+
setSelectedIndex(newIndex);
|
|
241
|
+
if (newIndex >= scrollOffset + MAX_VISIBLE_SUGGESTIONS) {
|
|
242
|
+
setScrollOffset(newIndex - MAX_VISIBLE_SUGGESTIONS + 1);
|
|
243
|
+
} else if (newIndex < scrollOffset) {
|
|
244
|
+
setScrollOffset(newIndex);
|
|
245
|
+
}
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// // Otherwise, use arrow keys for history navigation
|
|
252
|
+
// // Note: history[0] is the most recent item in useFraudeStore
|
|
253
|
+
if (key.upArrow && history.length > 0) {
|
|
254
|
+
const newIndex =
|
|
255
|
+
historyIndex < history.length - 1 ? historyIndex + 1 : historyIndex;
|
|
256
|
+
setHistoryIndex(newIndex);
|
|
257
|
+
const historyItem = history[newIndex];
|
|
258
|
+
if (historyItem) {
|
|
259
|
+
setCurrentInput(historyItem);
|
|
260
|
+
setInputKey((k) => k + 1);
|
|
261
|
+
}
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
// Down arrow: go to newer history or clear input
|
|
265
|
+
if (key.downArrow) {
|
|
266
|
+
if (historyIndex > 0) {
|
|
267
|
+
const newIndex = historyIndex - 1;
|
|
268
|
+
setHistoryIndex(newIndex);
|
|
269
|
+
const historyItem = history[newIndex];
|
|
270
|
+
if (historyItem) {
|
|
271
|
+
setCurrentInput(historyItem);
|
|
272
|
+
setInputKey((k) => k + 1);
|
|
273
|
+
}
|
|
274
|
+
} else {
|
|
275
|
+
// Clear input when at the end of history or not in history
|
|
276
|
+
setHistoryIndex(-1);
|
|
277
|
+
setCurrentInput("");
|
|
278
|
+
setInputKey((k) => k + 1);
|
|
279
|
+
}
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
const handleChange = useCallback((value: string) => {
|
|
285
|
+
setCurrentInput(value);
|
|
286
|
+
setSelectedIndex(0);
|
|
287
|
+
setScrollOffset(0);
|
|
288
|
+
setGhostIndex(0);
|
|
289
|
+
}, []);
|
|
290
|
+
|
|
291
|
+
const processSubmit = () => {
|
|
292
|
+
const v = currentInput;
|
|
293
|
+
if (v.trim().toLowerCase() === "exit") {
|
|
294
|
+
exit();
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
if (v.trim() === "") return;
|
|
298
|
+
addHistory(v);
|
|
299
|
+
setCurrentInput("");
|
|
300
|
+
setInputKey((k) => k + 1); // Clear input by remounting TextInput
|
|
301
|
+
QueryHandler(v);
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
const getExecutionMode = (mode: 0 | 1 | 2) => {
|
|
305
|
+
switch (mode) {
|
|
306
|
+
case 0:
|
|
307
|
+
return "Fast";
|
|
308
|
+
case 1:
|
|
309
|
+
return "Planning";
|
|
310
|
+
case 2:
|
|
311
|
+
return "Ask";
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
const status = useFraudeStore((state) => state.status);
|
|
316
|
+
if (status === 3) return null;
|
|
317
|
+
|
|
318
|
+
return (
|
|
319
|
+
<Box flexDirection="column">
|
|
320
|
+
<Box paddingX={0}>
|
|
321
|
+
<Text bold color={THEME.primary}>
|
|
322
|
+
>{" "}
|
|
323
|
+
</Text>
|
|
324
|
+
<Box flexGrow={1}>
|
|
325
|
+
<TextInput
|
|
326
|
+
key={inputKey}
|
|
327
|
+
placeholder="Type a command or ask a question..."
|
|
328
|
+
onChange={handleChange}
|
|
329
|
+
suggestions={dynamicSuggestions.filter(
|
|
330
|
+
(s) => !hiddenSuggestions.has(s),
|
|
331
|
+
)}
|
|
332
|
+
defaultValue={currentInput}
|
|
333
|
+
onSubmit={processSubmit}
|
|
334
|
+
/>
|
|
335
|
+
</Box>
|
|
336
|
+
</Box>
|
|
337
|
+
<Box marginTop={1}>
|
|
338
|
+
{dropdownSuggestions.length > 0 ? (
|
|
339
|
+
<CommandSuggestions
|
|
340
|
+
selectedIndex={selectedIndex}
|
|
341
|
+
filteredTemplates={dropdownSuggestions}
|
|
342
|
+
/>
|
|
343
|
+
) : (
|
|
344
|
+
<Box width={70} justifyContent="space-between" paddingX={1}>
|
|
345
|
+
{fileDropdownSuggestions.length > 0 ? (
|
|
346
|
+
<FileSuggestions
|
|
347
|
+
selectedIndex={selectedIndex - scrollOffset}
|
|
348
|
+
suggestions={fileDropdownSuggestions.slice(
|
|
349
|
+
scrollOffset,
|
|
350
|
+
scrollOffset + MAX_VISIBLE_SUGGESTIONS,
|
|
351
|
+
)}
|
|
352
|
+
/>
|
|
353
|
+
) : (
|
|
354
|
+
<>
|
|
355
|
+
<Text color={THEME.dim}>{shortenPath(process.cwd())}</Text>
|
|
356
|
+
<Text color={THEME.primaryLight}>
|
|
357
|
+
<Text bold>
|
|
358
|
+
{getExecutionMode(useFraudeStore.getState().executionMode)}{" "}
|
|
359
|
+
[Tab]
|
|
360
|
+
</Text>
|
|
361
|
+
</Text>
|
|
362
|
+
</>
|
|
363
|
+
)}
|
|
364
|
+
</Box>
|
|
365
|
+
)}
|
|
366
|
+
</Box>
|
|
367
|
+
</Box>
|
|
368
|
+
);
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
export default InputBoxComponent;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Box, Text } from "ink";
|
|
2
|
+
import type { OutputItem } from "@/types/OutputItem";
|
|
3
|
+
import { THEME } from "@/theme";
|
|
4
|
+
|
|
5
|
+
const CheckpointView = ({ item }: { item: OutputItem }) => {
|
|
6
|
+
return (
|
|
7
|
+
<Box flexDirection="column">
|
|
8
|
+
{item.content && <Text color={THEME.primaryDim}>{item.content}</Text>}
|
|
9
|
+
</Box>
|
|
10
|
+
);
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default CheckpointView;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Box, Text } from "ink";
|
|
2
|
+
import { THEME } from "@/theme";
|
|
3
|
+
|
|
4
|
+
const CommandView = ({ command }: { command: string }) => {
|
|
5
|
+
return (
|
|
6
|
+
<Box marginY={0}>
|
|
7
|
+
<Text color={THEME.dim}>> </Text>
|
|
8
|
+
<Text color={THEME.text}>{command}</Text>
|
|
9
|
+
</Box>
|
|
10
|
+
);
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default CommandView;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Box, Text } from "ink";
|
|
2
|
+
import { THEME } from "@/theme";
|
|
3
|
+
|
|
4
|
+
const CommentView = ({ comment }: { comment: string }) => {
|
|
5
|
+
return (
|
|
6
|
+
<Box marginY={0}>
|
|
7
|
+
<Text color={THEME.dim}># {comment}</Text>
|
|
8
|
+
</Box>
|
|
9
|
+
);
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default CommentView;
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { Box, Text } from "ink";
|
|
2
|
+
import { Select, TextInput } from "@inkjs/ui";
|
|
3
|
+
import React, { useState, useMemo } from "react";
|
|
4
|
+
import pendingChanges from "@/agent/pendingChanges";
|
|
5
|
+
import DiffView from "./DiffView";
|
|
6
|
+
import QueryHandler from "@/utils/queryHandler";
|
|
7
|
+
import { projectPath } from "@/utils";
|
|
8
|
+
import { type PendingChange } from "@/agent/pendingChanges";
|
|
9
|
+
|
|
10
|
+
import useFraudeStore from "@/store/useFraudeStore";
|
|
11
|
+
import { THEME } from "@/theme";
|
|
12
|
+
|
|
13
|
+
export default function ConfirmationView() {
|
|
14
|
+
const [status, setStatus] = useState<
|
|
15
|
+
"pending" | "applied" | "rejected" | "viewing_file" | "revising"
|
|
16
|
+
>("pending");
|
|
17
|
+
const [selectedFile, setSelectedFile] = useState<string | null>(null);
|
|
18
|
+
|
|
19
|
+
// Get grouped changes directly from manager
|
|
20
|
+
const groupedChanges = useMemo(
|
|
21
|
+
() => pendingChanges.getAllChangesGrouped(),
|
|
22
|
+
[],
|
|
23
|
+
);
|
|
24
|
+
const filePaths = Object.keys(groupedChanges);
|
|
25
|
+
|
|
26
|
+
const handleMainSelect = (value: string) => {
|
|
27
|
+
if (value === "apply_all") {
|
|
28
|
+
pendingChanges.applyAll().then(() => {
|
|
29
|
+
setStatus("applied");
|
|
30
|
+
useFraudeStore.setState({ status: 0, statusText: "" });
|
|
31
|
+
});
|
|
32
|
+
} else if (value === "reject_all") {
|
|
33
|
+
pendingChanges.rejectAll();
|
|
34
|
+
setStatus("rejected");
|
|
35
|
+
useFraudeStore.setState({ status: 0, statusText: "" });
|
|
36
|
+
} else if (value.startsWith("view_")) {
|
|
37
|
+
const path = value.replace("view_", "");
|
|
38
|
+
setSelectedFile(path);
|
|
39
|
+
setStatus("viewing_file");
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const handleFileAction = (value: string) => {
|
|
44
|
+
if (value === "back") {
|
|
45
|
+
setSelectedFile(null);
|
|
46
|
+
setStatus("pending");
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
if (status === "applied") {
|
|
51
|
+
return (
|
|
52
|
+
<Box borderStyle="single" borderColor={THEME.success} paddingX={1}>
|
|
53
|
+
<Text color={THEME.success}>✓ Changes applied</Text>
|
|
54
|
+
</Box>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (status === "rejected") {
|
|
59
|
+
return (
|
|
60
|
+
<Box borderStyle="single" borderColor={THEME.error} paddingX={1}>
|
|
61
|
+
<Text color={THEME.error}>✗ Changes rejected</Text>
|
|
62
|
+
</Box>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// View specific file details
|
|
67
|
+
if (status === "viewing_file" && selectedFile) {
|
|
68
|
+
const changes = groupedChanges[selectedFile];
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<Box
|
|
72
|
+
flexDirection="column"
|
|
73
|
+
borderStyle="single"
|
|
74
|
+
borderColor={THEME.border}
|
|
75
|
+
paddingX={1}
|
|
76
|
+
>
|
|
77
|
+
<Text bold color={THEME.primary}>
|
|
78
|
+
{projectPath(selectedFile)}
|
|
79
|
+
</Text>
|
|
80
|
+
<Box flexDirection="column" marginY={0}>
|
|
81
|
+
{changes?.map((change) => (
|
|
82
|
+
<Box key={change.id} flexDirection="column">
|
|
83
|
+
<Text color={THEME.dim}>Type: {change.type}</Text>
|
|
84
|
+
<DiffView patches={[change.diff]} />
|
|
85
|
+
</Box>
|
|
86
|
+
))}
|
|
87
|
+
</Box>
|
|
88
|
+
<Box marginTop={1}>
|
|
89
|
+
<Select
|
|
90
|
+
options={[{ label: "← Back", value: "back" }]}
|
|
91
|
+
onChange={handleFileAction}
|
|
92
|
+
/>
|
|
93
|
+
</Box>
|
|
94
|
+
</Box>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Handle revision feedback
|
|
99
|
+
if (status === "revising") {
|
|
100
|
+
return (
|
|
101
|
+
<Box
|
|
102
|
+
flexDirection="column"
|
|
103
|
+
borderStyle="single"
|
|
104
|
+
borderColor={THEME.primary}
|
|
105
|
+
paddingX={1}
|
|
106
|
+
>
|
|
107
|
+
<Text bold color={THEME.primary}>
|
|
108
|
+
Request Revision
|
|
109
|
+
</Text>
|
|
110
|
+
<Text dimColor>Enter feedback to guide the agent:</Text>
|
|
111
|
+
<Box marginTop={1}>
|
|
112
|
+
<TextInput
|
|
113
|
+
onSubmit={(value) => {
|
|
114
|
+
if (!value.trim()) return;
|
|
115
|
+
useFraudeStore.setState({
|
|
116
|
+
status: 1,
|
|
117
|
+
statusText: "Revising...",
|
|
118
|
+
});
|
|
119
|
+
pendingChanges.clear();
|
|
120
|
+
QueryHandler(`Revising changes: ${value}`);
|
|
121
|
+
}}
|
|
122
|
+
placeholder="What should be changed?"
|
|
123
|
+
/>
|
|
124
|
+
</Box>
|
|
125
|
+
</Box>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Main Summary View
|
|
130
|
+
const options = [
|
|
131
|
+
{ label: "✓ Apply All Changes", value: "apply_all" },
|
|
132
|
+
{ label: "✗ Reject All Changes", value: "reject_all" },
|
|
133
|
+
{ label: "Request Revision", value: "revise_all" },
|
|
134
|
+
// Add options to inspect specific files
|
|
135
|
+
...filePaths.map((path) => ({
|
|
136
|
+
label: `Review ${projectPath(path)}`,
|
|
137
|
+
value: `view_${path}`,
|
|
138
|
+
})),
|
|
139
|
+
];
|
|
140
|
+
|
|
141
|
+
return (
|
|
142
|
+
<Box flexDirection="column">
|
|
143
|
+
<Text bold color={THEME.primary}>
|
|
144
|
+
Pending Changes
|
|
145
|
+
</Text>
|
|
146
|
+
<Box flexDirection="column" marginY={0}>
|
|
147
|
+
{filePaths.map((path) => {
|
|
148
|
+
const changes = groupedChanges[path];
|
|
149
|
+
const type = changes?.[0]?.type || "unknown";
|
|
150
|
+
const patches = changes?.map((c) => c.diff) || [];
|
|
151
|
+
|
|
152
|
+
return (
|
|
153
|
+
<Box key={path} flexDirection="column" marginTop={1}>
|
|
154
|
+
<Text>
|
|
155
|
+
{projectPath(path)} <Text color={THEME.dim}>({type})</Text>
|
|
156
|
+
</Text>
|
|
157
|
+
<Box paddingLeft={2}>
|
|
158
|
+
<DiffView patches={patches} />
|
|
159
|
+
</Box>
|
|
160
|
+
</Box>
|
|
161
|
+
);
|
|
162
|
+
})}
|
|
163
|
+
</Box>
|
|
164
|
+
<Box marginTop={1} flexDirection="column">
|
|
165
|
+
<Text color={THEME.dim}>Select action:</Text>
|
|
166
|
+
<Select
|
|
167
|
+
options={options}
|
|
168
|
+
onChange={(value) => {
|
|
169
|
+
if (value === "revise_all") {
|
|
170
|
+
setStatus("revising");
|
|
171
|
+
} else {
|
|
172
|
+
handleMainSelect(value);
|
|
173
|
+
}
|
|
174
|
+
}}
|
|
175
|
+
/>
|
|
176
|
+
</Box>
|
|
177
|
+
</Box>
|
|
178
|
+
);
|
|
179
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Box, Text } from "ink";
|
|
2
|
+
import { useMemo } from "react";
|
|
3
|
+
import useFraudeStore from "@/store/useFraudeStore";
|
|
4
|
+
import useSettingsStore from "@/store/useSettingsStore";
|
|
5
|
+
import { THEME } from "@/theme";
|
|
6
|
+
|
|
7
|
+
const ContextUsage = () => {
|
|
8
|
+
const { contextManager } = useFraudeStore();
|
|
9
|
+
const { primaryModel, models } = useSettingsStore();
|
|
10
|
+
|
|
11
|
+
const { usedTokens, maxTokens, percentage, progressBar } = useMemo(() => {
|
|
12
|
+
// Find the current model from the models list
|
|
13
|
+
const currentModel = models.find((m) => m.name === primaryModel);
|
|
14
|
+
const contextLength = currentModel?.details?.context_length || 128000; // Default fallback
|
|
15
|
+
|
|
16
|
+
const used = contextManager.estimateContextTokens();
|
|
17
|
+
const pct = Math.min((used / contextLength) * 100, 100);
|
|
18
|
+
|
|
19
|
+
// Create a simple visual progress bar
|
|
20
|
+
const barWidth = 20;
|
|
21
|
+
const filledWidth = Math.round((pct / 100) * barWidth);
|
|
22
|
+
const emptyWidth = barWidth - filledWidth;
|
|
23
|
+
const bar = "█".repeat(filledWidth) + "░".repeat(emptyWidth);
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
usedTokens: used,
|
|
27
|
+
maxTokens: contextLength,
|
|
28
|
+
percentage: pct,
|
|
29
|
+
progressBar: bar,
|
|
30
|
+
};
|
|
31
|
+
}, [contextManager, primaryModel, models]);
|
|
32
|
+
|
|
33
|
+
// Color based on usage percentage
|
|
34
|
+
const getColor = () => {
|
|
35
|
+
if (percentage >= 90) return THEME.error;
|
|
36
|
+
if (percentage >= 70) return THEME.primaryDim;
|
|
37
|
+
if (percentage >= 50) return THEME.warning;
|
|
38
|
+
return THEME.info;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const formatTokens = (tokens: number) => {
|
|
42
|
+
if (tokens >= 1000) {
|
|
43
|
+
return `${(tokens / 1000).toFixed(1)}k`;
|
|
44
|
+
}
|
|
45
|
+
return tokens.toString();
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<Box flexDirection="row" gap={1}>
|
|
50
|
+
<Text color={THEME.dim}>[</Text>
|
|
51
|
+
<Text color={getColor()}>{progressBar}</Text>
|
|
52
|
+
<Text color={THEME.dim}>]</Text>
|
|
53
|
+
<Box paddingLeft={1}>
|
|
54
|
+
<Text color={THEME.text}>{formatTokens(usedTokens)}</Text>
|
|
55
|
+
<Text color={THEME.dim}>/{formatTokens(maxTokens)}</Text>
|
|
56
|
+
<Text color={getColor()}> {percentage.toFixed(0)}%</Text>
|
|
57
|
+
</Box>
|
|
58
|
+
</Box>
|
|
59
|
+
);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export default ContextUsage;
|