codebuff 1.0.148 → 1.0.150
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.d.ts +27 -10
- package/dist/cli.js +315 -284
- package/dist/cli.js.map +1 -1
- package/dist/common/browser-actions.d.ts +4409 -0
- package/dist/common/browser-actions.js +338 -0
- package/dist/common/browser-actions.js.map +1 -0
- package/dist/common/constants.d.ts +2 -2
- package/dist/common/constants.js +2 -2
- package/dist/common/constants.js.map +1 -1
- package/dist/common/util/file.js +5 -3
- package/dist/common/util/file.js.map +1 -1
- package/dist/common/util/min-heap.d.ts +15 -0
- package/dist/common/util/min-heap.js +73 -0
- package/dist/common/util/min-heap.js.map +1 -0
- package/dist/common/util/token-counter.d.ts +3 -0
- package/dist/common/util/token-counter.js +27 -0
- package/dist/common/util/token-counter.js.map +1 -0
- package/dist/create-template-project.d.ts +1 -1
- package/dist/create-template-project.js +20 -12
- package/dist/create-template-project.js.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/utils/terminal.d.ts +3 -1
- package/dist/utils/terminal.js +180 -135
- package/dist/utils/terminal.js.map +1 -1
- package/dist/web-scraper.d.ts +1 -1
- package/dist/web-scraper.js +9 -7
- package/dist/web-scraper.js.map +1 -1
- package/package.json +1 -2
- package/dist/__tests__/tool-handlers.test.d.ts +0 -1
- package/dist/__tests__/tool-handlers.test.js +0 -33
- package/dist/__tests__/tool-handlers.test.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -40,23 +40,13 @@ const changes_1 = require("./common/util/changes");
|
|
|
40
40
|
const readline = __importStar(require("readline"));
|
|
41
41
|
const picocolors_1 = require("picocolors");
|
|
42
42
|
const path_1 = require("path");
|
|
43
|
-
function rewriteLine(line) {
|
|
44
|
-
// Only do line rewriting if we have an interactive TTY
|
|
45
|
-
if (process.stdout.isTTY) {
|
|
46
|
-
readline.clearLine(process.stdout, 0);
|
|
47
|
-
readline.cursorTo(process.stdout, 0);
|
|
48
|
-
process.stdout.write(line);
|
|
49
|
-
}
|
|
50
|
-
else {
|
|
51
|
-
process.stdout.write(line + '\n');
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
43
|
const config_1 = require("./config");
|
|
55
44
|
const chat_storage_1 = require("./chat-storage");
|
|
56
45
|
const client_1 = require("./client");
|
|
57
46
|
const menu_1 = require("./menu");
|
|
58
47
|
const project_files_1 = require("./project-files");
|
|
59
48
|
const tool_handlers_1 = require("./tool-handlers");
|
|
49
|
+
const terminal_1 = require("./utils/terminal");
|
|
60
50
|
const constants_1 = require("./common/constants");
|
|
61
51
|
const file_1 = require("./common/util/file");
|
|
62
52
|
const web_scraper_1 = require("./web-scraper");
|
|
@@ -78,52 +68,13 @@ class CLI {
|
|
|
78
68
|
consecutiveFastInputs = 0;
|
|
79
69
|
pastedContent = '';
|
|
80
70
|
isPasting = false;
|
|
71
|
+
// ==================== Initialization Methods ====================
|
|
81
72
|
constructor(readyPromise, { git, costMode }) {
|
|
82
73
|
this.git = git;
|
|
83
74
|
this.costMode = costMode;
|
|
84
75
|
this.chatStorage = new chat_storage_1.ChatStorage();
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
this.restoreCursor();
|
|
88
|
-
process.exit(0);
|
|
89
|
-
});
|
|
90
|
-
this.rl = readline.createInterface({
|
|
91
|
-
input: process.stdin,
|
|
92
|
-
output: process.stdout,
|
|
93
|
-
historySize: 1000,
|
|
94
|
-
terminal: true,
|
|
95
|
-
completer: (line) => {
|
|
96
|
-
if (!this.client.fileContext?.fileTree)
|
|
97
|
-
return [[], line];
|
|
98
|
-
const tokenNames = Object.values(this.client.fileContext.fileTokenScores).flatMap((o) => Object.keys(o));
|
|
99
|
-
const paths = (0, project_file_tree_1.getAllFilePaths)(this.client.fileContext.fileTree);
|
|
100
|
-
const lastWord = line.split(' ').pop() || '';
|
|
101
|
-
const matchingTokens = [...tokenNames, ...paths].filter((token) => token.startsWith(lastWord) || token.includes('/' + lastWord));
|
|
102
|
-
if (matchingTokens.length > 1) {
|
|
103
|
-
// Find common characters after lastWord
|
|
104
|
-
const suffixes = matchingTokens.map((token) => {
|
|
105
|
-
const index = token.indexOf(lastWord);
|
|
106
|
-
return token.slice(index + lastWord.length);
|
|
107
|
-
});
|
|
108
|
-
let commonPrefix = '';
|
|
109
|
-
const firstSuffix = suffixes[0];
|
|
110
|
-
for (let i = 0; i < firstSuffix.length; i++) {
|
|
111
|
-
const char = firstSuffix[i];
|
|
112
|
-
if (suffixes.every((suffix) => suffix[i] === char)) {
|
|
113
|
-
commonPrefix += char;
|
|
114
|
-
}
|
|
115
|
-
else {
|
|
116
|
-
break;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
if (commonPrefix) {
|
|
120
|
-
// Match the common prefix
|
|
121
|
-
return [[lastWord + commonPrefix], lastWord];
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
return [matchingTokens, lastWord];
|
|
125
|
-
},
|
|
126
|
-
});
|
|
76
|
+
this.setupSignalHandlers();
|
|
77
|
+
this.initReadlineInterface();
|
|
127
78
|
this.client = new client_1.Client(config_1.websocketUrl, this.chatStorage, this.onWebSocketError.bind(this), this.onWebSocketReconnect.bind(this), this.returnControlToUser.bind(this), this.costMode, this.git, this.rl);
|
|
128
79
|
this.readyPromise = Promise.all([
|
|
129
80
|
readyPromise.then((results) => {
|
|
@@ -134,109 +85,63 @@ class CLI {
|
|
|
134
85
|
this.client.connect(),
|
|
135
86
|
]);
|
|
136
87
|
this.setPrompt();
|
|
137
|
-
|
|
138
|
-
|
|
88
|
+
}
|
|
89
|
+
setupSignalHandlers() {
|
|
90
|
+
process.on('exit', () => this.restoreCursor());
|
|
91
|
+
process.on('SIGTERM', () => {
|
|
92
|
+
this.restoreCursor();
|
|
93
|
+
process.exit(0);
|
|
139
94
|
});
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
95
|
+
process.on('SIGTSTP', () => this.handleExit());
|
|
96
|
+
}
|
|
97
|
+
initReadlineInterface() {
|
|
98
|
+
this.rl = readline.createInterface({
|
|
99
|
+
input: process.stdin,
|
|
100
|
+
output: process.stdout,
|
|
101
|
+
historySize: 1000,
|
|
102
|
+
terminal: true,
|
|
103
|
+
completer: this.completer.bind(this),
|
|
104
|
+
});
|
|
105
|
+
this.rl.on('line', (line) => this.handleLine(line));
|
|
106
|
+
this.rl.on('SIGINT', () => this.handleSigint());
|
|
107
|
+
this.rl.on('close', () => this.handleExit());
|
|
108
|
+
process.stdin.on('keypress', (str, key) => this.handleKeyPress(str, key));
|
|
109
|
+
}
|
|
110
|
+
completer(line) {
|
|
111
|
+
if (!this.client.fileContext?.fileTree)
|
|
112
|
+
return [[], line];
|
|
113
|
+
const tokenNames = Object.values(this.client.fileContext.fileTokenScores).flatMap((o) => Object.keys(o));
|
|
114
|
+
const paths = (0, project_file_tree_1.getAllFilePaths)(this.client.fileContext.fileTree);
|
|
115
|
+
const lastWord = line.split(' ').pop() || '';
|
|
116
|
+
const lastWordLower = lastWord.toLowerCase();
|
|
117
|
+
const matchingTokens = [...tokenNames, ...paths].filter((token) => token.toLowerCase().startsWith(lastWordLower) ||
|
|
118
|
+
token.toLowerCase().includes('/' + lastWordLower));
|
|
119
|
+
if (matchingTokens.length > 1) {
|
|
120
|
+
const suffixes = matchingTokens.map((token) => {
|
|
121
|
+
const index = token.toLowerCase().indexOf(lastWordLower);
|
|
122
|
+
return token.slice(index + lastWord.length);
|
|
123
|
+
});
|
|
124
|
+
let commonPrefix = '';
|
|
125
|
+
const firstSuffix = suffixes[0];
|
|
126
|
+
for (let i = 0; i < firstSuffix.length; i++) {
|
|
127
|
+
const char = firstSuffix[i];
|
|
128
|
+
if (suffixes.every((suffix) => suffix[i] === char)) {
|
|
129
|
+
commonPrefix += char;
|
|
149
130
|
}
|
|
150
131
|
else {
|
|
151
|
-
|
|
152
|
-
console.log('\nPress Ctrl-C again to exit');
|
|
153
|
-
this.rl.prompt();
|
|
132
|
+
break;
|
|
154
133
|
}
|
|
155
134
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
this.handleExit();
|
|
159
|
-
});
|
|
160
|
-
process.on('SIGTSTP', () => {
|
|
161
|
-
// Exit on Ctrl+Z
|
|
162
|
-
this.handleExit();
|
|
163
|
-
});
|
|
164
|
-
process.stdin.on('keypress', (str, key) => {
|
|
165
|
-
if (key.name === 'escape') {
|
|
166
|
-
this.handleEscKey();
|
|
167
|
-
}
|
|
168
|
-
// Make double spaces into newlines
|
|
169
|
-
if (str === ' ' &&
|
|
170
|
-
'_refreshLine' in this.rl &&
|
|
171
|
-
'line' in this.rl &&
|
|
172
|
-
'cursor' in this.rl) {
|
|
173
|
-
const rl = this.rl;
|
|
174
|
-
const { cursor, line } = rl;
|
|
175
|
-
const prevTwoChars = cursor > 1 ? line.slice(cursor - 2, cursor) : '';
|
|
176
|
-
if (prevTwoChars === ' ') {
|
|
177
|
-
rl.line = line.slice(0, cursor - 2) + '\n\n' + line.slice(cursor);
|
|
178
|
-
rl._refreshLine();
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
this.detectPasting();
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
returnControlToUser() {
|
|
185
|
-
this.rl.prompt();
|
|
186
|
-
this.isReceivingResponse = false;
|
|
187
|
-
if (this.stopResponse) {
|
|
188
|
-
this.stopResponse();
|
|
189
|
-
}
|
|
190
|
-
this.stopLoadingAnimation();
|
|
191
|
-
}
|
|
192
|
-
onWebSocketError() {
|
|
193
|
-
this.stopLoadingAnimation();
|
|
194
|
-
this.isReceivingResponse = false;
|
|
195
|
-
if (this.stopResponse) {
|
|
196
|
-
this.stopResponse();
|
|
197
|
-
this.stopResponse = null;
|
|
198
|
-
}
|
|
199
|
-
console.error((0, picocolors_1.yellow)('\nCould not connect. Retrying...'));
|
|
200
|
-
}
|
|
201
|
-
onWebSocketReconnect() {
|
|
202
|
-
console.log((0, picocolors_1.green)('\nReconnected!'));
|
|
203
|
-
this.returnControlToUser();
|
|
204
|
-
}
|
|
205
|
-
detectPasting() {
|
|
206
|
-
const currentTime = Date.now();
|
|
207
|
-
const timeDiff = currentTime - this.lastInputTime;
|
|
208
|
-
if (timeDiff < 10) {
|
|
209
|
-
this.consecutiveFastInputs++;
|
|
210
|
-
if (this.consecutiveFastInputs >= 2) {
|
|
211
|
-
this.isPasting = true;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
else {
|
|
215
|
-
this.consecutiveFastInputs = 0;
|
|
216
|
-
if (this.isPasting) {
|
|
217
|
-
this.isPasting = false;
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
this.lastInputTime = currentTime;
|
|
221
|
-
}
|
|
222
|
-
handleInput(line) {
|
|
223
|
-
this.detectPasting();
|
|
224
|
-
if (this.isPasting) {
|
|
225
|
-
this.pastedContent += line + '\n';
|
|
226
|
-
}
|
|
227
|
-
else if (!this.isReceivingResponse) {
|
|
228
|
-
if (this.pastedContent) {
|
|
229
|
-
this.handleUserInput((this.pastedContent + line).trim());
|
|
230
|
-
this.pastedContent = '';
|
|
231
|
-
}
|
|
232
|
-
else {
|
|
233
|
-
this.handleUserInput(line.trim());
|
|
135
|
+
if (commonPrefix) {
|
|
136
|
+
return [[lastWord + commonPrefix], lastWord];
|
|
234
137
|
}
|
|
235
138
|
}
|
|
139
|
+
return [matchingTokens, lastWord];
|
|
236
140
|
}
|
|
237
141
|
setPrompt() {
|
|
238
142
|
this.rl.setPrompt((0, picocolors_1.green)(`${(0, path_1.parse)((0, project_files_1.getProjectRoot)()).base} > `));
|
|
239
143
|
}
|
|
144
|
+
// ==================== Public Methods ====================
|
|
240
145
|
async printInitialPrompt(initialInput) {
|
|
241
146
|
if (this.client.user) {
|
|
242
147
|
(0, menu_1.displayGreeting)(this.costMode, this.client.user.name);
|
|
@@ -252,177 +157,83 @@ class CLI {
|
|
|
252
157
|
this.handleUserInput(initialInput);
|
|
253
158
|
}
|
|
254
159
|
}
|
|
255
|
-
|
|
256
|
-
this.
|
|
257
|
-
this.rl.prompt();
|
|
258
|
-
}
|
|
259
|
-
handleRedo() {
|
|
260
|
-
this.navigateFileVersion('redo');
|
|
160
|
+
async printDiff() {
|
|
161
|
+
this.handleDiff();
|
|
261
162
|
this.rl.prompt();
|
|
262
163
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
const navigated = this.chatStorage.navigateVersion(direction);
|
|
269
|
-
if (navigated) {
|
|
270
|
-
console.log(direction === 'undo'
|
|
271
|
-
? (0, picocolors_1.green)('Undo last change')
|
|
272
|
-
: (0, picocolors_1.green)('Redo last change'));
|
|
273
|
-
const files = this.applyAndDisplayCurrentFileVersion();
|
|
274
|
-
console.log((0, picocolors_1.green)('Loaded files:'), (0, picocolors_1.green)(Object.keys(files).join(', ')));
|
|
275
|
-
}
|
|
276
|
-
else {
|
|
277
|
-
console.log((0, picocolors_1.green)(`No more ${direction === 'undo' ? 'undo' : 'redo'}s`));
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
handleStopResponse() {
|
|
281
|
-
console.log((0, picocolors_1.yellow)('\n[Response stopped by user]'));
|
|
282
|
-
this.isReceivingResponse = false;
|
|
283
|
-
if (this.stopResponse) {
|
|
284
|
-
this.stopResponse();
|
|
285
|
-
}
|
|
286
|
-
this.stopLoadingAnimation();
|
|
287
|
-
this.restoreCursor();
|
|
288
|
-
}
|
|
289
|
-
restoreCursor() {
|
|
290
|
-
// Show cursor ANSI escape code
|
|
291
|
-
process.stdout.write('\u001B[?25h');
|
|
292
|
-
}
|
|
293
|
-
handleExit() {
|
|
294
|
-
this.restoreCursor();
|
|
295
|
-
console.log('\n\n');
|
|
296
|
-
console.log(`${(0, string_1.pluralize)(this.client.sessionCreditsUsed, 'credit')} used this session.`);
|
|
297
|
-
if (!!this.client.limit &&
|
|
298
|
-
!!this.client.usage &&
|
|
299
|
-
!!this.client.nextQuotaReset) {
|
|
300
|
-
const daysUntilReset = Math.max(0, Math.floor((this.client.nextQuotaReset.getTime() - Date.now()) /
|
|
301
|
-
(1000 * 60 * 60 * 24)));
|
|
302
|
-
console.log(`${Math.max(0, this.client.limit - this.client.usage)} / ${this.client.limit} credits remaining. Renews in ${(0, string_1.pluralize)(daysUntilReset, 'day')}.`);
|
|
303
|
-
}
|
|
304
|
-
console.log((0, picocolors_1.green)('Codebuff out!'));
|
|
305
|
-
process.exit(0);
|
|
306
|
-
}
|
|
307
|
-
handleEscKey() {
|
|
308
|
-
if (this.isReceivingResponse) {
|
|
309
|
-
this.handleStopResponse();
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
applyAndDisplayCurrentFileVersion() {
|
|
313
|
-
const currentVersion = this.chatStorage.getCurrentVersion();
|
|
314
|
-
if (currentVersion) {
|
|
315
|
-
(0, project_files_1.setFiles)(currentVersion.files);
|
|
316
|
-
return currentVersion.files;
|
|
317
|
-
}
|
|
318
|
-
return {};
|
|
319
|
-
}
|
|
320
|
-
startLoadingAnimation() {
|
|
321
|
-
const chars = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
322
|
-
let i = 0;
|
|
323
|
-
// Hide cursor while spinner is active
|
|
324
|
-
process.stdout.write('\u001B[?25l');
|
|
325
|
-
this.loadingInterval = setInterval(() => {
|
|
326
|
-
rewriteLine((0, picocolors_1.green)(`${chars[i]} Thinking...`));
|
|
327
|
-
i = (i + 1) % chars.length;
|
|
328
|
-
}, 100);
|
|
329
|
-
}
|
|
330
|
-
stopLoadingAnimation() {
|
|
331
|
-
if (this.loadingInterval) {
|
|
332
|
-
clearInterval(this.loadingInterval);
|
|
333
|
-
this.loadingInterval = null;
|
|
334
|
-
rewriteLine(''); // Clear the spinner line
|
|
335
|
-
this.restoreCursor(); // Show cursor after spinner stops
|
|
164
|
+
// ==================== Input Handling Methods ====================
|
|
165
|
+
async handleLine(line) {
|
|
166
|
+
this.detectPasting();
|
|
167
|
+
if (this.isPasting) {
|
|
168
|
+
this.pastedContent += line + '\n';
|
|
336
169
|
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
return commitMessage;
|
|
170
|
+
else if (!this.isReceivingResponse) {
|
|
171
|
+
if (this.pastedContent) {
|
|
172
|
+
await this.handleUserInput((this.pastedContent + line).trim());
|
|
173
|
+
this.pastedContent = '';
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
await this.handleUserInput(line.trim());
|
|
177
|
+
}
|
|
346
178
|
}
|
|
347
|
-
return undefined;
|
|
348
|
-
}
|
|
349
|
-
handleDiff() {
|
|
350
|
-
if (this.lastChanges.length === 0) {
|
|
351
|
-
console.log((0, picocolors_1.yellow)('No changes found in the last assistant response.'));
|
|
352
|
-
return;
|
|
353
|
-
}
|
|
354
|
-
this.lastChanges.forEach((change) => {
|
|
355
|
-
console.log('-', change.filePath);
|
|
356
|
-
const lines = change.content
|
|
357
|
-
.split('\n')
|
|
358
|
-
.map((line) => (change.type === 'file' ? '+' + line : line));
|
|
359
|
-
lines.forEach((line) => {
|
|
360
|
-
if (line.startsWith('+')) {
|
|
361
|
-
console.log((0, picocolors_1.green)(line));
|
|
362
|
-
}
|
|
363
|
-
else if (line.startsWith('-')) {
|
|
364
|
-
console.log((0, picocolors_1.red)(line));
|
|
365
|
-
}
|
|
366
|
-
else {
|
|
367
|
-
console.log(line);
|
|
368
|
-
}
|
|
369
|
-
});
|
|
370
|
-
});
|
|
371
179
|
}
|
|
372
180
|
async handleUserInput(userInput) {
|
|
373
181
|
if (!userInput)
|
|
374
182
|
return;
|
|
375
183
|
userInput = userInput.trim();
|
|
376
|
-
|
|
184
|
+
if (await this.processCommand(userInput)) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
await this.forwardUserInput(userInput);
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Checks if the input matches a built-in command.
|
|
191
|
+
* Returns true if the command has been handled.
|
|
192
|
+
*/
|
|
193
|
+
async processCommand(userInput) {
|
|
377
194
|
if (userInput === 'help' || userInput === 'h') {
|
|
378
195
|
(0, menu_1.displayMenu)();
|
|
379
196
|
this.rl.prompt();
|
|
380
|
-
return;
|
|
197
|
+
return true;
|
|
381
198
|
}
|
|
382
199
|
if (userInput === 'login' || userInput === 'signin') {
|
|
383
200
|
await this.client.login();
|
|
384
|
-
return;
|
|
201
|
+
return true;
|
|
385
202
|
}
|
|
386
|
-
|
|
203
|
+
if (userInput === 'logout' || userInput === 'signout') {
|
|
387
204
|
await this.client.logout();
|
|
388
205
|
this.rl.prompt();
|
|
389
|
-
return;
|
|
206
|
+
return true;
|
|
390
207
|
}
|
|
391
|
-
|
|
208
|
+
if (userInput.startsWith('ref-')) {
|
|
392
209
|
await this.client.handleReferralCode(userInput.trim());
|
|
393
|
-
return;
|
|
210
|
+
return true;
|
|
394
211
|
}
|
|
395
|
-
|
|
212
|
+
if (userInput === 'usage' || userInput === 'credits') {
|
|
396
213
|
this.client.getUsage();
|
|
397
|
-
return;
|
|
214
|
+
return true;
|
|
398
215
|
}
|
|
399
|
-
|
|
216
|
+
if (userInput === 'undo' || userInput === 'u') {
|
|
400
217
|
this.handleUndo();
|
|
401
|
-
return;
|
|
218
|
+
return true;
|
|
402
219
|
}
|
|
403
|
-
|
|
220
|
+
if (userInput === 'redo' || userInput === 'r') {
|
|
404
221
|
this.handleRedo();
|
|
405
|
-
return;
|
|
222
|
+
return true;
|
|
406
223
|
}
|
|
407
|
-
|
|
408
|
-
userInput === 'exit' ||
|
|
409
|
-
userInput === 'q') {
|
|
224
|
+
if (userInput === 'quit' || userInput === 'exit' || userInput === 'q') {
|
|
410
225
|
this.handleExit();
|
|
411
|
-
return;
|
|
226
|
+
return true;
|
|
412
227
|
}
|
|
413
|
-
|
|
414
|
-
userInput === 'doff' ||
|
|
415
|
-
userInput === 'dif' ||
|
|
416
|
-
userInput === 'iff' ||
|
|
417
|
-
userInput === 'd') {
|
|
228
|
+
if (['diff', 'doff', 'dif', 'iff', 'd'].includes(userInput)) {
|
|
418
229
|
this.handleDiff();
|
|
419
230
|
this.rl.prompt();
|
|
420
|
-
return;
|
|
231
|
+
return true;
|
|
421
232
|
}
|
|
422
233
|
const runPrefix = '/run ';
|
|
423
234
|
const hasRunPrefix = userInput.startsWith(runPrefix);
|
|
424
235
|
if (hasRunPrefix ||
|
|
425
|
-
(!constants_1.SKIPPED_TERMINAL_COMMANDS.some((
|
|
236
|
+
(!constants_1.SKIPPED_TERMINAL_COMMANDS.some((cmd) => userInput.toLowerCase().startsWith(cmd)) &&
|
|
426
237
|
!userInput.includes('error ') &&
|
|
427
238
|
!userInput.includes("'") &&
|
|
428
239
|
userInput.split(' ').length <= 5)) {
|
|
@@ -431,15 +242,22 @@ class CLI {
|
|
|
431
242
|
if (result !== 'command not found') {
|
|
432
243
|
this.setPrompt();
|
|
433
244
|
this.rl.prompt();
|
|
434
|
-
return;
|
|
245
|
+
return true;
|
|
435
246
|
}
|
|
436
247
|
else if (hasRunPrefix) {
|
|
437
248
|
process.stdout.write(stdout);
|
|
438
249
|
this.setPrompt();
|
|
439
250
|
this.rl.prompt();
|
|
440
|
-
return;
|
|
251
|
+
return true;
|
|
441
252
|
}
|
|
442
253
|
}
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Processes regular user input (non-command) by gathering file changes and scraped content,
|
|
258
|
+
* sending the consolidated user message, and then applying assistant response changes.
|
|
259
|
+
*/
|
|
260
|
+
async forwardUserInput(userInput) {
|
|
443
261
|
this.startLoadingAnimation();
|
|
444
262
|
await this.readyPromise;
|
|
445
263
|
const currentChat = this.chatStorage.getCurrentChat();
|
|
@@ -493,7 +311,7 @@ class CLI {
|
|
|
493
311
|
if (this.client.lastRequestCredits > constants_1.REQUEST_CREDIT_SHOW_THRESHOLD) {
|
|
494
312
|
console.log(`\n${(0, string_1.pluralize)(this.client.lastRequestCredits, 'credit')} used for this request.`);
|
|
495
313
|
}
|
|
496
|
-
console.log('Complete! Type "diff" to
|
|
314
|
+
console.log('Complete! Type "diff" to review changes or "undo" to revert.');
|
|
497
315
|
this.client.showUsageWarning();
|
|
498
316
|
}
|
|
499
317
|
console.log();
|
|
@@ -507,6 +325,28 @@ class CLI {
|
|
|
507
325
|
this.chatStorage.addNewFileState(updatedFiles);
|
|
508
326
|
this.rl.prompt();
|
|
509
327
|
}
|
|
328
|
+
// ==================== WebSocket and Response Handling ====================
|
|
329
|
+
returnControlToUser() {
|
|
330
|
+
this.rl.prompt();
|
|
331
|
+
this.isReceivingResponse = false;
|
|
332
|
+
if (this.stopResponse) {
|
|
333
|
+
this.stopResponse();
|
|
334
|
+
}
|
|
335
|
+
this.stopLoadingAnimation();
|
|
336
|
+
}
|
|
337
|
+
onWebSocketError() {
|
|
338
|
+
this.stopLoadingAnimation();
|
|
339
|
+
this.isReceivingResponse = false;
|
|
340
|
+
if (this.stopResponse) {
|
|
341
|
+
this.stopResponse();
|
|
342
|
+
this.stopResponse = null;
|
|
343
|
+
}
|
|
344
|
+
console.error((0, picocolors_1.yellow)('\nCould not connect. Retrying...'));
|
|
345
|
+
}
|
|
346
|
+
onWebSocketReconnect() {
|
|
347
|
+
console.log((0, picocolors_1.green)('\nReconnected!'));
|
|
348
|
+
this.returnControlToUser();
|
|
349
|
+
}
|
|
510
350
|
async sendUserInputAndAwaitResponse() {
|
|
511
351
|
const userInputId = `mc-input-` + Math.random().toString(36).substring(2, 15);
|
|
512
352
|
const { responsePromise, stopResponse } = this.client.subscribeToResponse((chunk) => {
|
|
@@ -521,6 +361,197 @@ class CLI {
|
|
|
521
361
|
this.stopResponse = null;
|
|
522
362
|
return result;
|
|
523
363
|
}
|
|
364
|
+
// ==================== File Version Navigation ====================
|
|
365
|
+
handleUndo() {
|
|
366
|
+
this.navigateFileVersion('undo');
|
|
367
|
+
this.rl.prompt();
|
|
368
|
+
}
|
|
369
|
+
handleRedo() {
|
|
370
|
+
this.navigateFileVersion('redo');
|
|
371
|
+
this.rl.prompt();
|
|
372
|
+
}
|
|
373
|
+
navigateFileVersion(direction) {
|
|
374
|
+
const currentVersion = this.chatStorage.getCurrentVersion();
|
|
375
|
+
const filePaths = Object.keys(currentVersion ? currentVersion.files : {});
|
|
376
|
+
const currentFiles = (0, project_files_1.getExistingFiles)(filePaths);
|
|
377
|
+
this.chatStorage.saveCurrentFileState(currentFiles);
|
|
378
|
+
const navigated = this.chatStorage.navigateVersion(direction);
|
|
379
|
+
if (navigated) {
|
|
380
|
+
console.log(direction === 'undo'
|
|
381
|
+
? (0, picocolors_1.green)('Undo last change')
|
|
382
|
+
: (0, picocolors_1.green)('Redo last change'));
|
|
383
|
+
const files = this.applyAndDisplayCurrentFileVersion();
|
|
384
|
+
console.log((0, picocolors_1.green)('Loaded files:'), (0, picocolors_1.green)(Object.keys(files).join(', ')));
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
console.log((0, picocolors_1.green)(`No more ${direction === 'undo' ? 'undo' : 'redo'}s`));
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
applyAndDisplayCurrentFileVersion() {
|
|
391
|
+
const currentVersion = this.chatStorage.getCurrentVersion();
|
|
392
|
+
if (currentVersion) {
|
|
393
|
+
(0, project_files_1.setFiles)(currentVersion.files);
|
|
394
|
+
return currentVersion.files;
|
|
395
|
+
}
|
|
396
|
+
return {};
|
|
397
|
+
}
|
|
398
|
+
// ==================== Terminal Animation and Cursor Management ====================
|
|
399
|
+
startLoadingAnimation() {
|
|
400
|
+
const chars = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
401
|
+
let i = 0;
|
|
402
|
+
// Hide cursor while spinner is active
|
|
403
|
+
process.stdout.write('\u001B[?25l');
|
|
404
|
+
this.loadingInterval = setInterval(() => {
|
|
405
|
+
this.rewriteLine((0, picocolors_1.green)(`${chars[i]} Thinking...`));
|
|
406
|
+
i = (i + 1) % chars.length;
|
|
407
|
+
}, 100);
|
|
408
|
+
}
|
|
409
|
+
stopLoadingAnimation() {
|
|
410
|
+
if (this.loadingInterval) {
|
|
411
|
+
clearInterval(this.loadingInterval);
|
|
412
|
+
this.loadingInterval = null;
|
|
413
|
+
this.rewriteLine(''); // Clear the spinner line
|
|
414
|
+
this.restoreCursor(); // Show cursor after spinner stops
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
rewriteLine(line) {
|
|
418
|
+
if (process.stdout.isTTY) {
|
|
419
|
+
readline.clearLine(process.stdout, 0);
|
|
420
|
+
readline.cursorTo(process.stdout, 0);
|
|
421
|
+
process.stdout.write(line);
|
|
422
|
+
}
|
|
423
|
+
else {
|
|
424
|
+
process.stdout.write(line + '\n');
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
restoreCursor() {
|
|
428
|
+
process.stdout.write('\u001B[?25h');
|
|
429
|
+
}
|
|
430
|
+
// ==================== Keyboard and SIGINT Handling ====================
|
|
431
|
+
handleKeyPress(str, key) {
|
|
432
|
+
if (key.name === 'escape') {
|
|
433
|
+
this.handleEscKey();
|
|
434
|
+
}
|
|
435
|
+
// Convert double spaces into newlines
|
|
436
|
+
if (!this.isPasting &&
|
|
437
|
+
str === ' ' &&
|
|
438
|
+
'_refreshLine' in this.rl &&
|
|
439
|
+
'line' in this.rl &&
|
|
440
|
+
'cursor' in this.rl) {
|
|
441
|
+
const rlAny = this.rl;
|
|
442
|
+
const { cursor, line } = rlAny;
|
|
443
|
+
const prevTwoChars = cursor > 1 ? line.slice(cursor - 2, cursor) : '';
|
|
444
|
+
if (prevTwoChars === ' ') {
|
|
445
|
+
rlAny.line = line.slice(0, cursor - 2) + '\n\n' + line.slice(cursor);
|
|
446
|
+
rlAny._refreshLine();
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
this.detectPasting();
|
|
450
|
+
}
|
|
451
|
+
handleSigint() {
|
|
452
|
+
if ((0, terminal_1.isCommandRunning)()) {
|
|
453
|
+
(0, terminal_1.resetShell)();
|
|
454
|
+
}
|
|
455
|
+
if ('line' in this.rl) {
|
|
456
|
+
;
|
|
457
|
+
this.rl.line = '';
|
|
458
|
+
}
|
|
459
|
+
if (this.isReceivingResponse) {
|
|
460
|
+
this.handleStopResponse();
|
|
461
|
+
}
|
|
462
|
+
else {
|
|
463
|
+
const now = Date.now();
|
|
464
|
+
if (now - this.lastSigintTime < 5000) {
|
|
465
|
+
this.handleExit();
|
|
466
|
+
}
|
|
467
|
+
else {
|
|
468
|
+
this.lastSigintTime = now;
|
|
469
|
+
console.log('\nPress Ctrl-C again to exit');
|
|
470
|
+
this.rl.prompt();
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
handleEscKey() {
|
|
475
|
+
if (this.isReceivingResponse) {
|
|
476
|
+
this.handleStopResponse();
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
handleStopResponse() {
|
|
480
|
+
console.log((0, picocolors_1.yellow)('\n[Response stopped by user]'));
|
|
481
|
+
this.isReceivingResponse = false;
|
|
482
|
+
if (this.stopResponse) {
|
|
483
|
+
this.stopResponse();
|
|
484
|
+
}
|
|
485
|
+
this.stopLoadingAnimation();
|
|
486
|
+
this.restoreCursor();
|
|
487
|
+
}
|
|
488
|
+
// ==================== Exit Handling ====================
|
|
489
|
+
handleExit() {
|
|
490
|
+
this.restoreCursor();
|
|
491
|
+
console.log('\n\n');
|
|
492
|
+
console.log(`${(0, string_1.pluralize)(this.client.sessionCreditsUsed, 'credit')} used this session.`);
|
|
493
|
+
if (this.client.limit && this.client.usage && this.client.nextQuotaReset) {
|
|
494
|
+
const daysUntilReset = Math.max(0, Math.floor((this.client.nextQuotaReset.getTime() - Date.now()) /
|
|
495
|
+
(1000 * 60 * 60 * 24)));
|
|
496
|
+
console.log(`${Math.max(0, this.client.limit - this.client.usage)} / ${this.client.limit} credits remaining. Renews in ${(0, string_1.pluralize)(daysUntilReset, 'day')}.`);
|
|
497
|
+
}
|
|
498
|
+
console.log((0, picocolors_1.green)('Codebuff out!'));
|
|
499
|
+
process.exit(0);
|
|
500
|
+
}
|
|
501
|
+
// ==================== Pasting Detection ====================
|
|
502
|
+
detectPasting() {
|
|
503
|
+
const currentTime = Date.now();
|
|
504
|
+
const timeDiff = currentTime - this.lastInputTime;
|
|
505
|
+
if (timeDiff < 10) {
|
|
506
|
+
this.consecutiveFastInputs++;
|
|
507
|
+
if (this.consecutiveFastInputs >= 2) {
|
|
508
|
+
this.isPasting = true;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
else {
|
|
512
|
+
this.consecutiveFastInputs = 0;
|
|
513
|
+
if (this.isPasting) {
|
|
514
|
+
this.isPasting = false;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
this.lastInputTime = currentTime;
|
|
518
|
+
}
|
|
519
|
+
// ==================== Auto Commit Handling ====================
|
|
520
|
+
async autoCommitChanges() {
|
|
521
|
+
if ((0, git_1.hasStagedChanges)()) {
|
|
522
|
+
const stagedChanges = (0, git_1.getStagedChanges)();
|
|
523
|
+
if (!stagedChanges)
|
|
524
|
+
return;
|
|
525
|
+
const commitMessage = await this.client.generateCommitMessage(stagedChanges);
|
|
526
|
+
(0, git_1.commitChanges)(commitMessage);
|
|
527
|
+
return commitMessage;
|
|
528
|
+
}
|
|
529
|
+
return undefined;
|
|
530
|
+
}
|
|
531
|
+
// ==================== Diff Handling ====================
|
|
532
|
+
handleDiff() {
|
|
533
|
+
if (this.lastChanges.length === 0) {
|
|
534
|
+
console.log((0, picocolors_1.yellow)('No changes found in the last assistant response.'));
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
this.lastChanges.forEach((change) => {
|
|
538
|
+
console.log('-', change.filePath);
|
|
539
|
+
const lines = change.content
|
|
540
|
+
.split('\n')
|
|
541
|
+
.map((line) => (change.type === 'file' ? '+' + line : line));
|
|
542
|
+
lines.forEach((line) => {
|
|
543
|
+
if (line.startsWith('+')) {
|
|
544
|
+
console.log((0, picocolors_1.green)(line));
|
|
545
|
+
}
|
|
546
|
+
else if (line.startsWith('-')) {
|
|
547
|
+
console.log((0, picocolors_1.red)(line));
|
|
548
|
+
}
|
|
549
|
+
else {
|
|
550
|
+
console.log(line);
|
|
551
|
+
}
|
|
552
|
+
});
|
|
553
|
+
});
|
|
554
|
+
}
|
|
524
555
|
}
|
|
525
556
|
exports.CLI = CLI;
|
|
526
557
|
//# sourceMappingURL=cli.js.map
|