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.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
- process.on('exit', () => this.restoreCursor());
86
- process.on('SIGTERM', () => {
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
- this.rl.on('line', (line) => {
138
- this.handleInput(line);
88
+ }
89
+ setupSignalHandlers() {
90
+ process.on('exit', () => this.restoreCursor());
91
+ process.on('SIGTERM', () => {
92
+ this.restoreCursor();
93
+ process.exit(0);
139
94
  });
140
- this.rl.on('SIGINT', () => {
141
- if (this.isReceivingResponse) {
142
- this.handleStopResponse();
143
- }
144
- else {
145
- const now = Date.now();
146
- if (now - this.lastSigintTime < 3000) {
147
- // 3 second window
148
- this.handleExit();
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
- this.lastSigintTime = now;
152
- console.log('\nPress Ctrl-C again to exit');
153
- this.rl.prompt();
132
+ break;
154
133
  }
155
134
  }
156
- });
157
- this.rl.on('close', () => {
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
- handleUndo() {
256
- this.navigateFileVersion('undo');
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
- navigateFileVersion(direction) {
264
- const currentVersion = this.chatStorage.getCurrentVersion();
265
- const filePaths = Object.keys(currentVersion ? currentVersion.files : {});
266
- const currentFiles = (0, project_files_1.getExistingFiles)(filePaths);
267
- this.chatStorage.saveCurrentFileState(currentFiles);
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
- async autoCommitChanges() {
339
- if ((0, git_1.hasStagedChanges)()) {
340
- const stagedChanges = (0, git_1.getStagedChanges)();
341
- if (!stagedChanges)
342
- return;
343
- const commitMessage = await this.client.generateCommitMessage(stagedChanges);
344
- (0, git_1.commitChanges)(commitMessage);
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
- // Handle commands
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
- else if (userInput === 'logout' || userInput === 'signout') {
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
- else if (userInput.startsWith('ref-')) {
208
+ if (userInput.startsWith('ref-')) {
392
209
  await this.client.handleReferralCode(userInput.trim());
393
- return;
210
+ return true;
394
211
  }
395
- else if (userInput === 'usage' || userInput === 'credits') {
212
+ if (userInput === 'usage' || userInput === 'credits') {
396
213
  this.client.getUsage();
397
- return;
214
+ return true;
398
215
  }
399
- else if (userInput === 'undo' || userInput === 'u') {
216
+ if (userInput === 'undo' || userInput === 'u') {
400
217
  this.handleUndo();
401
- return;
218
+ return true;
402
219
  }
403
- else if (userInput === 'redo' || userInput === 'r') {
220
+ if (userInput === 'redo' || userInput === 'r') {
404
221
  this.handleRedo();
405
- return;
222
+ return true;
406
223
  }
407
- else if (userInput === 'quit' ||
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
- else if (userInput === 'diff' ||
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((command) => userInput.toLowerCase().startsWith(command)) &&
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 see the changes made.');
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