codebuff 1.0.149 → 1.0.151

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