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.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
- process.on('exit', () => this.restoreCursor());
87
- process.on('SIGTERM', () => {
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
- this.rl.on('line', (line) => {
139
- this.handleInput(line);
88
+ }
89
+ setupSignalHandlers() {
90
+ process.on('exit', () => this.restoreCursor());
91
+ process.on('SIGTERM', () => {
92
+ this.restoreCursor();
93
+ process.exit(0);
140
94
  });
141
- this.rl.on('SIGINT', () => {
142
- // Kill any running terminal command
143
- if ((0, terminal_1.isCommandRunning)()) {
144
- (0, terminal_1.resetShell)();
145
- }
146
- // Clear the line buffer if it exists (readline internal API)
147
- if ('line' in this.rl) {
148
- this.rl.line = '';
149
- }
150
- if (this.isReceivingResponse) {
151
- this.handleStopResponse();
152
- }
153
- else {
154
- const now = Date.now();
155
- if (now - this.lastSigintTime < 5000) {
156
- 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;
157
130
  }
158
131
  else {
159
- this.lastSigintTime = now;
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
- this.detectPasting();
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
- handleUndo() {
264
- this.navigateFileVersion('undo');
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
- navigateFileVersion(direction) {
272
- const currentVersion = this.chatStorage.getCurrentVersion();
273
- const filePaths = Object.keys(currentVersion ? currentVersion.files : {});
274
- const currentFiles = (0, project_files_1.getExistingFiles)(filePaths);
275
- this.chatStorage.saveCurrentFileState(currentFiles);
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
- async autoCommitChanges() {
347
- if ((0, git_1.hasStagedChanges)()) {
348
- const stagedChanges = (0, git_1.getStagedChanges)();
349
- if (!stagedChanges)
350
- return;
351
- const commitMessage = await this.client.generateCommitMessage(stagedChanges);
352
- (0, git_1.commitChanges)(commitMessage);
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
- // 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) {
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
- else if (userInput === 'logout' || userInput === 'signout') {
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
- else if (userInput.startsWith('ref-')) {
208
+ if (userInput.startsWith('ref-')) {
400
209
  await this.client.handleReferralCode(userInput.trim());
401
- return;
210
+ return true;
402
211
  }
403
- else if (userInput === 'usage' || userInput === 'credits') {
212
+ if (userInput === 'usage' || userInput === 'credits') {
404
213
  this.client.getUsage();
405
- return;
214
+ return true;
406
215
  }
407
- else if (userInput === 'undo' || userInput === 'u') {
216
+ if (userInput === 'undo' || userInput === 'u') {
408
217
  this.handleUndo();
409
- return;
218
+ return true;
410
219
  }
411
- else if (userInput === 'redo' || userInput === 'r') {
220
+ if (userInput === 'redo' || userInput === 'r') {
412
221
  this.handleRedo();
413
- return;
222
+ return true;
414
223
  }
415
- else if (userInput === 'quit' ||
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
- else if (userInput === 'diff' ||
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((command) => userInput.toLowerCase().startsWith(command)) &&
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 see the changes made.');
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