codebuff 1.0.149 → 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 +314 -291
- 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 +2 -1
- package/dist/index.js.map +1 -1
- package/dist/utils/terminal.js +3 -5
- 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/cli.js
CHANGED
|
@@ -40,17 +40,6 @@ 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");
|
|
@@ -79,52 +68,13 @@ class CLI {
|
|
|
79
68
|
consecutiveFastInputs = 0;
|
|
80
69
|
pastedContent = '';
|
|
81
70
|
isPasting = false;
|
|
71
|
+
// ==================== Initialization Methods ====================
|
|
82
72
|
constructor(readyPromise, { git, costMode }) {
|
|
83
73
|
this.git = git;
|
|
84
74
|
this.costMode = costMode;
|
|
85
75
|
this.chatStorage = new chat_storage_1.ChatStorage();
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
this.restoreCursor();
|
|
89
|
-
process.exit(0);
|
|
90
|
-
});
|
|
91
|
-
this.rl = readline.createInterface({
|
|
92
|
-
input: process.stdin,
|
|
93
|
-
output: process.stdout,
|
|
94
|
-
historySize: 1000,
|
|
95
|
-
terminal: true,
|
|
96
|
-
completer: (line) => {
|
|
97
|
-
if (!this.client.fileContext?.fileTree)
|
|
98
|
-
return [[], line];
|
|
99
|
-
const tokenNames = Object.values(this.client.fileContext.fileTokenScores).flatMap((o) => Object.keys(o));
|
|
100
|
-
const paths = (0, project_file_tree_1.getAllFilePaths)(this.client.fileContext.fileTree);
|
|
101
|
-
const lastWord = line.split(' ').pop() || '';
|
|
102
|
-
const matchingTokens = [...tokenNames, ...paths].filter((token) => token.startsWith(lastWord) || token.includes('/' + lastWord));
|
|
103
|
-
if (matchingTokens.length > 1) {
|
|
104
|
-
// Find common characters after lastWord
|
|
105
|
-
const suffixes = matchingTokens.map((token) => {
|
|
106
|
-
const index = token.indexOf(lastWord);
|
|
107
|
-
return token.slice(index + lastWord.length);
|
|
108
|
-
});
|
|
109
|
-
let commonPrefix = '';
|
|
110
|
-
const firstSuffix = suffixes[0];
|
|
111
|
-
for (let i = 0; i < firstSuffix.length; i++) {
|
|
112
|
-
const char = firstSuffix[i];
|
|
113
|
-
if (suffixes.every((suffix) => suffix[i] === char)) {
|
|
114
|
-
commonPrefix += char;
|
|
115
|
-
}
|
|
116
|
-
else {
|
|
117
|
-
break;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
if (commonPrefix) {
|
|
121
|
-
// Match the common prefix
|
|
122
|
-
return [[lastWord + commonPrefix], lastWord];
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
return [matchingTokens, lastWord];
|
|
126
|
-
},
|
|
127
|
-
});
|
|
76
|
+
this.setupSignalHandlers();
|
|
77
|
+
this.initReadlineInterface();
|
|
128
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);
|
|
129
79
|
this.readyPromise = Promise.all([
|
|
130
80
|
readyPromise.then((results) => {
|
|
@@ -135,116 +85,63 @@ class CLI {
|
|
|
135
85
|
this.client.connect(),
|
|
136
86
|
]);
|
|
137
87
|
this.setPrompt();
|
|
138
|
-
|
|
139
|
-
|
|
88
|
+
}
|
|
89
|
+
setupSignalHandlers() {
|
|
90
|
+
process.on('exit', () => this.restoreCursor());
|
|
91
|
+
process.on('SIGTERM', () => {
|
|
92
|
+
this.restoreCursor();
|
|
93
|
+
process.exit(0);
|
|
140
94
|
});
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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;
|
|
157
130
|
}
|
|
158
131
|
else {
|
|
159
|
-
|
|
160
|
-
console.log('\nPress Ctrl-C again to exit');
|
|
161
|
-
this.rl.prompt();
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
});
|
|
165
|
-
this.rl.on('close', () => {
|
|
166
|
-
this.handleExit();
|
|
167
|
-
});
|
|
168
|
-
process.on('SIGTSTP', () => {
|
|
169
|
-
// Exit on Ctrl+Z
|
|
170
|
-
this.handleExit();
|
|
171
|
-
});
|
|
172
|
-
process.stdin.on('keypress', (str, key) => {
|
|
173
|
-
if (key.name === 'escape') {
|
|
174
|
-
this.handleEscKey();
|
|
175
|
-
}
|
|
176
|
-
// Make double spaces into newlines
|
|
177
|
-
if (str === ' ' &&
|
|
178
|
-
'_refreshLine' in this.rl &&
|
|
179
|
-
'line' in this.rl &&
|
|
180
|
-
'cursor' in this.rl) {
|
|
181
|
-
const rl = this.rl;
|
|
182
|
-
const { cursor, line } = rl;
|
|
183
|
-
const prevTwoChars = cursor > 1 ? line.slice(cursor - 2, cursor) : '';
|
|
184
|
-
if (prevTwoChars === ' ') {
|
|
185
|
-
rl.line = line.slice(0, cursor - 2) + '\n\n' + line.slice(cursor);
|
|
186
|
-
rl._refreshLine();
|
|
132
|
+
break;
|
|
187
133
|
}
|
|
188
134
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}
|
|
192
|
-
returnControlToUser() {
|
|
193
|
-
this.rl.prompt();
|
|
194
|
-
this.isReceivingResponse = false;
|
|
195
|
-
if (this.stopResponse) {
|
|
196
|
-
this.stopResponse();
|
|
197
|
-
}
|
|
198
|
-
this.stopLoadingAnimation();
|
|
199
|
-
}
|
|
200
|
-
onWebSocketError() {
|
|
201
|
-
this.stopLoadingAnimation();
|
|
202
|
-
this.isReceivingResponse = false;
|
|
203
|
-
if (this.stopResponse) {
|
|
204
|
-
this.stopResponse();
|
|
205
|
-
this.stopResponse = null;
|
|
206
|
-
}
|
|
207
|
-
console.error((0, picocolors_1.yellow)('\nCould not connect. Retrying...'));
|
|
208
|
-
}
|
|
209
|
-
onWebSocketReconnect() {
|
|
210
|
-
console.log((0, picocolors_1.green)('\nReconnected!'));
|
|
211
|
-
this.returnControlToUser();
|
|
212
|
-
}
|
|
213
|
-
detectPasting() {
|
|
214
|
-
const currentTime = Date.now();
|
|
215
|
-
const timeDiff = currentTime - this.lastInputTime;
|
|
216
|
-
if (timeDiff < 10) {
|
|
217
|
-
this.consecutiveFastInputs++;
|
|
218
|
-
if (this.consecutiveFastInputs >= 2) {
|
|
219
|
-
this.isPasting = true;
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
else {
|
|
223
|
-
this.consecutiveFastInputs = 0;
|
|
224
|
-
if (this.isPasting) {
|
|
225
|
-
this.isPasting = false;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
this.lastInputTime = currentTime;
|
|
229
|
-
}
|
|
230
|
-
handleInput(line) {
|
|
231
|
-
this.detectPasting();
|
|
232
|
-
if (this.isPasting) {
|
|
233
|
-
this.pastedContent += line + '\n';
|
|
234
|
-
}
|
|
235
|
-
else if (!this.isReceivingResponse) {
|
|
236
|
-
if (this.pastedContent) {
|
|
237
|
-
this.handleUserInput((this.pastedContent + line).trim());
|
|
238
|
-
this.pastedContent = '';
|
|
239
|
-
}
|
|
240
|
-
else {
|
|
241
|
-
this.handleUserInput(line.trim());
|
|
135
|
+
if (commonPrefix) {
|
|
136
|
+
return [[lastWord + commonPrefix], lastWord];
|
|
242
137
|
}
|
|
243
138
|
}
|
|
139
|
+
return [matchingTokens, lastWord];
|
|
244
140
|
}
|
|
245
141
|
setPrompt() {
|
|
246
142
|
this.rl.setPrompt((0, picocolors_1.green)(`${(0, path_1.parse)((0, project_files_1.getProjectRoot)()).base} > `));
|
|
247
143
|
}
|
|
144
|
+
// ==================== Public Methods ====================
|
|
248
145
|
async printInitialPrompt(initialInput) {
|
|
249
146
|
if (this.client.user) {
|
|
250
147
|
(0, menu_1.displayGreeting)(this.costMode, this.client.user.name);
|
|
@@ -260,177 +157,83 @@ class CLI {
|
|
|
260
157
|
this.handleUserInput(initialInput);
|
|
261
158
|
}
|
|
262
159
|
}
|
|
263
|
-
|
|
264
|
-
this.
|
|
265
|
-
this.rl.prompt();
|
|
266
|
-
}
|
|
267
|
-
handleRedo() {
|
|
268
|
-
this.navigateFileVersion('redo');
|
|
160
|
+
async printDiff() {
|
|
161
|
+
this.handleDiff();
|
|
269
162
|
this.rl.prompt();
|
|
270
163
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
const navigated = this.chatStorage.navigateVersion(direction);
|
|
277
|
-
if (navigated) {
|
|
278
|
-
console.log(direction === 'undo'
|
|
279
|
-
? (0, picocolors_1.green)('Undo last change')
|
|
280
|
-
: (0, picocolors_1.green)('Redo last change'));
|
|
281
|
-
const files = this.applyAndDisplayCurrentFileVersion();
|
|
282
|
-
console.log((0, picocolors_1.green)('Loaded files:'), (0, picocolors_1.green)(Object.keys(files).join(', ')));
|
|
283
|
-
}
|
|
284
|
-
else {
|
|
285
|
-
console.log((0, picocolors_1.green)(`No more ${direction === 'undo' ? 'undo' : 'redo'}s`));
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
handleStopResponse() {
|
|
289
|
-
console.log((0, picocolors_1.yellow)('\n[Response stopped by user]'));
|
|
290
|
-
this.isReceivingResponse = false;
|
|
291
|
-
if (this.stopResponse) {
|
|
292
|
-
this.stopResponse();
|
|
293
|
-
}
|
|
294
|
-
this.stopLoadingAnimation();
|
|
295
|
-
this.restoreCursor();
|
|
296
|
-
}
|
|
297
|
-
restoreCursor() {
|
|
298
|
-
// Show cursor ANSI escape code
|
|
299
|
-
process.stdout.write('\u001B[?25h');
|
|
300
|
-
}
|
|
301
|
-
handleExit() {
|
|
302
|
-
this.restoreCursor();
|
|
303
|
-
console.log('\n\n');
|
|
304
|
-
console.log(`${(0, string_1.pluralize)(this.client.sessionCreditsUsed, 'credit')} used this session.`);
|
|
305
|
-
if (!!this.client.limit &&
|
|
306
|
-
!!this.client.usage &&
|
|
307
|
-
!!this.client.nextQuotaReset) {
|
|
308
|
-
const daysUntilReset = Math.max(0, Math.floor((this.client.nextQuotaReset.getTime() - Date.now()) /
|
|
309
|
-
(1000 * 60 * 60 * 24)));
|
|
310
|
-
console.log(`${Math.max(0, this.client.limit - this.client.usage)} / ${this.client.limit} credits remaining. Renews in ${(0, string_1.pluralize)(daysUntilReset, 'day')}.`);
|
|
311
|
-
}
|
|
312
|
-
console.log((0, picocolors_1.green)('Codebuff out!'));
|
|
313
|
-
process.exit(0);
|
|
314
|
-
}
|
|
315
|
-
handleEscKey() {
|
|
316
|
-
if (this.isReceivingResponse) {
|
|
317
|
-
this.handleStopResponse();
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
applyAndDisplayCurrentFileVersion() {
|
|
321
|
-
const currentVersion = this.chatStorage.getCurrentVersion();
|
|
322
|
-
if (currentVersion) {
|
|
323
|
-
(0, project_files_1.setFiles)(currentVersion.files);
|
|
324
|
-
return currentVersion.files;
|
|
325
|
-
}
|
|
326
|
-
return {};
|
|
327
|
-
}
|
|
328
|
-
startLoadingAnimation() {
|
|
329
|
-
const chars = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
330
|
-
let i = 0;
|
|
331
|
-
// Hide cursor while spinner is active
|
|
332
|
-
process.stdout.write('\u001B[?25l');
|
|
333
|
-
this.loadingInterval = setInterval(() => {
|
|
334
|
-
rewriteLine((0, picocolors_1.green)(`${chars[i]} Thinking...`));
|
|
335
|
-
i = (i + 1) % chars.length;
|
|
336
|
-
}, 100);
|
|
337
|
-
}
|
|
338
|
-
stopLoadingAnimation() {
|
|
339
|
-
if (this.loadingInterval) {
|
|
340
|
-
clearInterval(this.loadingInterval);
|
|
341
|
-
this.loadingInterval = null;
|
|
342
|
-
rewriteLine(''); // Clear the spinner line
|
|
343
|
-
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';
|
|
344
169
|
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
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
|
+
}
|
|
354
178
|
}
|
|
355
|
-
return undefined;
|
|
356
|
-
}
|
|
357
|
-
handleDiff() {
|
|
358
|
-
if (this.lastChanges.length === 0) {
|
|
359
|
-
console.log((0, picocolors_1.yellow)('No changes found in the last assistant response.'));
|
|
360
|
-
return;
|
|
361
|
-
}
|
|
362
|
-
this.lastChanges.forEach((change) => {
|
|
363
|
-
console.log('-', change.filePath);
|
|
364
|
-
const lines = change.content
|
|
365
|
-
.split('\n')
|
|
366
|
-
.map((line) => (change.type === 'file' ? '+' + line : line));
|
|
367
|
-
lines.forEach((line) => {
|
|
368
|
-
if (line.startsWith('+')) {
|
|
369
|
-
console.log((0, picocolors_1.green)(line));
|
|
370
|
-
}
|
|
371
|
-
else if (line.startsWith('-')) {
|
|
372
|
-
console.log((0, picocolors_1.red)(line));
|
|
373
|
-
}
|
|
374
|
-
else {
|
|
375
|
-
console.log(line);
|
|
376
|
-
}
|
|
377
|
-
});
|
|
378
|
-
});
|
|
379
179
|
}
|
|
380
180
|
async handleUserInput(userInput) {
|
|
381
181
|
if (!userInput)
|
|
382
182
|
return;
|
|
383
183
|
userInput = userInput.trim();
|
|
384
|
-
|
|
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) {
|
|
385
194
|
if (userInput === 'help' || userInput === 'h') {
|
|
386
195
|
(0, menu_1.displayMenu)();
|
|
387
196
|
this.rl.prompt();
|
|
388
|
-
return;
|
|
197
|
+
return true;
|
|
389
198
|
}
|
|
390
199
|
if (userInput === 'login' || userInput === 'signin') {
|
|
391
200
|
await this.client.login();
|
|
392
|
-
return;
|
|
201
|
+
return true;
|
|
393
202
|
}
|
|
394
|
-
|
|
203
|
+
if (userInput === 'logout' || userInput === 'signout') {
|
|
395
204
|
await this.client.logout();
|
|
396
205
|
this.rl.prompt();
|
|
397
|
-
return;
|
|
206
|
+
return true;
|
|
398
207
|
}
|
|
399
|
-
|
|
208
|
+
if (userInput.startsWith('ref-')) {
|
|
400
209
|
await this.client.handleReferralCode(userInput.trim());
|
|
401
|
-
return;
|
|
210
|
+
return true;
|
|
402
211
|
}
|
|
403
|
-
|
|
212
|
+
if (userInput === 'usage' || userInput === 'credits') {
|
|
404
213
|
this.client.getUsage();
|
|
405
|
-
return;
|
|
214
|
+
return true;
|
|
406
215
|
}
|
|
407
|
-
|
|
216
|
+
if (userInput === 'undo' || userInput === 'u') {
|
|
408
217
|
this.handleUndo();
|
|
409
|
-
return;
|
|
218
|
+
return true;
|
|
410
219
|
}
|
|
411
|
-
|
|
220
|
+
if (userInput === 'redo' || userInput === 'r') {
|
|
412
221
|
this.handleRedo();
|
|
413
|
-
return;
|
|
222
|
+
return true;
|
|
414
223
|
}
|
|
415
|
-
|
|
416
|
-
userInput === 'exit' ||
|
|
417
|
-
userInput === 'q') {
|
|
224
|
+
if (userInput === 'quit' || userInput === 'exit' || userInput === 'q') {
|
|
418
225
|
this.handleExit();
|
|
419
|
-
return;
|
|
226
|
+
return true;
|
|
420
227
|
}
|
|
421
|
-
|
|
422
|
-
userInput === 'doff' ||
|
|
423
|
-
userInput === 'dif' ||
|
|
424
|
-
userInput === 'iff' ||
|
|
425
|
-
userInput === 'd') {
|
|
228
|
+
if (['diff', 'doff', 'dif', 'iff', 'd'].includes(userInput)) {
|
|
426
229
|
this.handleDiff();
|
|
427
230
|
this.rl.prompt();
|
|
428
|
-
return;
|
|
231
|
+
return true;
|
|
429
232
|
}
|
|
430
233
|
const runPrefix = '/run ';
|
|
431
234
|
const hasRunPrefix = userInput.startsWith(runPrefix);
|
|
432
235
|
if (hasRunPrefix ||
|
|
433
|
-
(!constants_1.SKIPPED_TERMINAL_COMMANDS.some((
|
|
236
|
+
(!constants_1.SKIPPED_TERMINAL_COMMANDS.some((cmd) => userInput.toLowerCase().startsWith(cmd)) &&
|
|
434
237
|
!userInput.includes('error ') &&
|
|
435
238
|
!userInput.includes("'") &&
|
|
436
239
|
userInput.split(' ').length <= 5)) {
|
|
@@ -439,15 +242,22 @@ class CLI {
|
|
|
439
242
|
if (result !== 'command not found') {
|
|
440
243
|
this.setPrompt();
|
|
441
244
|
this.rl.prompt();
|
|
442
|
-
return;
|
|
245
|
+
return true;
|
|
443
246
|
}
|
|
444
247
|
else if (hasRunPrefix) {
|
|
445
248
|
process.stdout.write(stdout);
|
|
446
249
|
this.setPrompt();
|
|
447
250
|
this.rl.prompt();
|
|
448
|
-
return;
|
|
251
|
+
return true;
|
|
449
252
|
}
|
|
450
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) {
|
|
451
261
|
this.startLoadingAnimation();
|
|
452
262
|
await this.readyPromise;
|
|
453
263
|
const currentChat = this.chatStorage.getCurrentChat();
|
|
@@ -501,7 +311,7 @@ class CLI {
|
|
|
501
311
|
if (this.client.lastRequestCredits > constants_1.REQUEST_CREDIT_SHOW_THRESHOLD) {
|
|
502
312
|
console.log(`\n${(0, string_1.pluralize)(this.client.lastRequestCredits, 'credit')} used for this request.`);
|
|
503
313
|
}
|
|
504
|
-
console.log('Complete! Type "diff" to
|
|
314
|
+
console.log('Complete! Type "diff" to review changes or "undo" to revert.');
|
|
505
315
|
this.client.showUsageWarning();
|
|
506
316
|
}
|
|
507
317
|
console.log();
|
|
@@ -515,6 +325,28 @@ class CLI {
|
|
|
515
325
|
this.chatStorage.addNewFileState(updatedFiles);
|
|
516
326
|
this.rl.prompt();
|
|
517
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
|
+
}
|
|
518
350
|
async sendUserInputAndAwaitResponse() {
|
|
519
351
|
const userInputId = `mc-input-` + Math.random().toString(36).substring(2, 15);
|
|
520
352
|
const { responsePromise, stopResponse } = this.client.subscribeToResponse((chunk) => {
|
|
@@ -529,6 +361,197 @@ class CLI {
|
|
|
529
361
|
this.stopResponse = null;
|
|
530
362
|
return result;
|
|
531
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
|
+
}
|
|
532
555
|
}
|
|
533
556
|
exports.CLI = CLI;
|
|
534
557
|
//# sourceMappingURL=cli.js.map
|