codecane 1.0.156 → 1.0.171

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 (106) hide show
  1. package/dist/browser-runner.d.ts +2 -0
  2. package/dist/browser-runner.js +210 -135
  3. package/dist/browser-runner.js.map +1 -1
  4. package/dist/chat-storage.d.ts +1 -1
  5. package/dist/chat-storage.js +35 -31
  6. package/dist/chat-storage.js.map +1 -1
  7. package/dist/checkpoints.d.ts +64 -0
  8. package/dist/checkpoints.js +147 -0
  9. package/dist/checkpoints.js.map +1 -0
  10. package/dist/cli.d.ts +22 -16
  11. package/dist/cli.js +472 -367
  12. package/dist/cli.js.map +1 -1
  13. package/dist/client.d.ts +178 -25
  14. package/dist/client.js +252 -198
  15. package/dist/client.js.map +1 -1
  16. package/dist/code-map/tsconfig.tsbuildinfo +1 -1
  17. package/dist/common/actions.d.ts +2083 -443
  18. package/dist/common/actions.js +31 -78
  19. package/dist/common/actions.js.map +1 -1
  20. package/dist/common/browser-actions.d.ts +221 -141
  21. package/dist/common/browser-actions.js +25 -12
  22. package/dist/common/browser-actions.js.map +1 -1
  23. package/dist/common/constants/tools.d.ts +3 -0
  24. package/dist/common/constants/tools.js +24 -0
  25. package/dist/common/constants/tools.js.map +1 -0
  26. package/dist/common/constants.d.ts +14 -8
  27. package/dist/common/constants.js +7 -6
  28. package/dist/common/constants.js.map +1 -1
  29. package/dist/common/message-image-handling.d.ts +41 -0
  30. package/dist/common/message-image-handling.js +57 -0
  31. package/dist/common/message-image-handling.js.map +1 -0
  32. package/dist/common/project-file-tree.js +7 -7
  33. package/dist/common/project-file-tree.js.map +1 -1
  34. package/dist/common/types/agent-state.d.ts +461 -0
  35. package/dist/common/types/agent-state.js +30 -0
  36. package/dist/common/types/agent-state.js.map +1 -0
  37. package/dist/common/types/message.d.ts +311 -0
  38. package/dist/common/types/message.js +54 -0
  39. package/dist/common/types/message.js.map +1 -0
  40. package/dist/common/types/tools.d.ts +5 -0
  41. package/dist/common/types/tools.js +3 -0
  42. package/dist/common/types/tools.js.map +1 -0
  43. package/dist/common/util/__tests__/messages.test.js +70 -0
  44. package/dist/common/util/__tests__/messages.test.js.map +1 -0
  45. package/dist/common/util/changes.js +3 -3
  46. package/dist/common/util/changes.js.map +1 -1
  47. package/dist/common/util/credentials.d.ts +4 -4
  48. package/dist/common/util/file.d.ts +6 -2
  49. package/dist/common/util/file.js +30 -27
  50. package/dist/common/util/file.js.map +1 -1
  51. package/dist/common/util/git.js +1 -1
  52. package/dist/common/util/git.js.map +1 -1
  53. package/dist/common/util/lru-cache.d.ts +9 -0
  54. package/dist/common/util/lru-cache.js +42 -0
  55. package/dist/common/util/lru-cache.js.map +1 -0
  56. package/dist/common/util/messages.d.ts +6 -0
  57. package/dist/common/util/messages.js +22 -0
  58. package/dist/common/util/messages.js.map +1 -0
  59. package/dist/common/util/min-heap.d.ts +15 -0
  60. package/dist/common/util/min-heap.js +73 -0
  61. package/dist/common/util/min-heap.js.map +1 -0
  62. package/dist/common/util/process-stream.d.ts +8 -0
  63. package/dist/common/util/process-stream.js +102 -0
  64. package/dist/common/util/process-stream.js.map +1 -0
  65. package/dist/common/util/promise.d.ts +8 -0
  66. package/dist/common/util/promise.js +25 -2
  67. package/dist/common/util/promise.js.map +1 -1
  68. package/dist/common/util/string.d.ts +31 -0
  69. package/dist/common/util/string.js +71 -1
  70. package/dist/common/util/string.js.map +1 -1
  71. package/dist/common/websockets/websocket-schema.d.ts +3920 -938
  72. package/dist/config.d.ts +1 -0
  73. package/dist/config.js +3 -2
  74. package/dist/config.js.map +1 -1
  75. package/dist/credentials.d.ts +1 -0
  76. package/dist/credentials.js +7 -3
  77. package/dist/credentials.js.map +1 -1
  78. package/dist/index.js +3 -3
  79. package/dist/index.js.map +1 -1
  80. package/dist/menu.js +16 -12
  81. package/dist/menu.js.map +1 -1
  82. package/dist/project-files.d.ts +40 -2
  83. package/dist/project-files.js +95 -17
  84. package/dist/project-files.js.map +1 -1
  85. package/dist/tool-handlers.d.ts +22 -7
  86. package/dist/tool-handlers.js +110 -43
  87. package/dist/tool-handlers.js.map +1 -1
  88. package/dist/utils/logger.d.ts +1 -0
  89. package/dist/utils/logger.js +46 -0
  90. package/dist/utils/logger.js.map +1 -0
  91. package/dist/utils/process-xml-chunks.d.ts +37 -0
  92. package/dist/utils/process-xml-chunks.js +247 -0
  93. package/dist/utils/process-xml-chunks.js.map +1 -0
  94. package/dist/utils/spinner.d.ts +11 -0
  95. package/dist/utils/spinner.js +87 -0
  96. package/dist/utils/spinner.js.map +1 -0
  97. package/dist/utils/terminal.d.ts +3 -3
  98. package/dist/utils/terminal.js +23 -24
  99. package/dist/utils/terminal.js.map +1 -1
  100. package/dist/web-scraper.d.ts +1 -1
  101. package/dist/web-scraper.js +11 -7
  102. package/dist/web-scraper.js.map +1 -1
  103. package/package.json +3 -4
  104. package/dist/__tests__/browser-runner.test.js +0 -15
  105. package/dist/__tests__/browser-runner.test.js.map +0 -1
  106. /package/dist/{__tests__/browser-runner.test.d.ts → common/util/__tests__/messages.test.d.ts} +0 -0
package/dist/cli.js CHANGED
@@ -24,35 +24,24 @@ var __importStar = (this && this.__importStar) || function (mod) {
24
24
  };
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
26
  exports.CLI = void 0;
27
- const lodash_1 = require("lodash");
28
- const project_file_tree_1 = require("./common/project-file-tree");
29
- const changes_1 = require("./common/util/changes");
30
- const readline = __importStar(require("readline"));
31
- const picocolors_1 = require("picocolors");
27
+ const fs = __importStar(require("fs"));
32
28
  const path_1 = require("path");
33
- function rewriteLine(line) {
34
- // Only do line rewriting if we have an interactive TTY
35
- if (process.stdout.isTTY) {
36
- readline.clearLine(process.stdout, 0);
37
- readline.cursorTo(process.stdout, 0);
38
- process.stdout.write(line);
39
- }
40
- else {
41
- process.stdout.write(line + '\n');
42
- }
43
- }
44
- const config_1 = require("./config");
29
+ const picocolors_1 = require("picocolors");
30
+ const readline = __importStar(require("readline"));
31
+ const constants_1 = require("./common/constants");
32
+ const project_file_tree_1 = require("./common/project-file-tree");
33
+ const file_1 = require("./common/util/file");
34
+ const string_1 = require("./common/util/string");
45
35
  const chat_storage_1 = require("./chat-storage");
36
+ const checkpoints_1 = require("./checkpoints");
46
37
  const client_1 = require("./client");
38
+ const config_1 = require("./config");
47
39
  const menu_1 = require("./menu");
48
40
  const project_files_1 = require("./project-files");
49
41
  const tool_handlers_1 = require("./tool-handlers");
42
+ const spinner_1 = require("./utils/spinner");
50
43
  const terminal_1 = require("./utils/terminal");
51
- const constants_1 = require("./common/constants");
52
- const file_1 = require("./common/util/file");
53
44
  const web_scraper_1 = require("./web-scraper");
54
- const git_1 = require("./common/util/git");
55
- const string_1 = require("./common/util/string");
56
45
  class CLI {
57
46
  client;
58
47
  chatStorage;
@@ -62,8 +51,6 @@ class CLI {
62
51
  rl;
63
52
  isReceivingResponse = false;
64
53
  stopResponse = null;
65
- loadingInterval = null;
66
- lastChanges = [];
67
54
  lastSigintTime = 0;
68
55
  lastInputTime = 0;
69
56
  consecutiveFastInputs = 0;
@@ -73,375 +60,223 @@ class CLI {
73
60
  this.git = git;
74
61
  this.costMode = costMode;
75
62
  this.chatStorage = new chat_storage_1.ChatStorage();
76
- process.on('exit', () => this.restoreCursor());
77
- process.on('SIGTERM', () => {
78
- this.restoreCursor();
79
- process.exit(0);
80
- });
81
- this.rl = readline.createInterface({
82
- input: process.stdin,
83
- output: process.stdout,
84
- historySize: 1000,
85
- terminal: true,
86
- completer: (line) => {
87
- if (!this.client.fileContext?.fileTree)
88
- return [[], line];
89
- const tokenNames = Object.values(this.client.fileContext.fileTokenScores).flatMap((o) => Object.keys(o));
90
- const paths = (0, project_file_tree_1.getAllFilePaths)(this.client.fileContext.fileTree);
91
- const lastWord = line.split(' ').pop() || '';
92
- const matchingTokens = [...tokenNames, ...paths].filter((token) => token.startsWith(lastWord) || token.includes('/' + lastWord));
93
- if (matchingTokens.length > 1) {
94
- // Find common characters after lastWord
95
- const suffixes = matchingTokens.map((token) => {
96
- const index = token.indexOf(lastWord);
97
- return token.slice(index + lastWord.length);
98
- });
99
- let commonPrefix = '';
100
- const firstSuffix = suffixes[0];
101
- for (let i = 0; i < firstSuffix.length; i++) {
102
- const char = firstSuffix[i];
103
- if (suffixes.every((suffix) => suffix[i] === char)) {
104
- commonPrefix += char;
105
- }
106
- else {
107
- break;
108
- }
109
- }
110
- if (commonPrefix) {
111
- // Match the common prefix
112
- return [[lastWord + commonPrefix], lastWord];
113
- }
114
- }
115
- return [matchingTokens, lastWord];
116
- },
117
- });
63
+ this.setupSignalHandlers();
64
+ this.initReadlineInterface();
118
65
  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);
119
66
  this.readyPromise = Promise.all([
120
67
  readyPromise.then((results) => {
121
68
  const [_, fileContext] = results;
122
- this.client.initFileVersions(fileContext);
69
+ this.client.initAgentState(fileContext);
123
70
  return this.client.warmContextCache();
124
71
  }),
125
72
  this.client.connect(),
126
73
  ]);
127
74
  this.setPrompt();
128
- this.rl.on('line', (line) => {
129
- this.handleInput(line);
75
+ }
76
+ setupSignalHandlers() {
77
+ process.on('exit', () => spinner_1.Spinner.get().restoreCursor());
78
+ process.on('SIGTERM', () => {
79
+ spinner_1.Spinner.get().restoreCursor();
80
+ process.exit(0);
130
81
  });
131
- this.rl.on('SIGINT', () => {
132
- // Kill any running terminal command
133
- if ((0, terminal_1.isCommandRunning)()) {
134
- (0, terminal_1.resetShell)();
135
- }
136
- // Clear the line buffer if it exists (readline internal API)
137
- if ('line' in this.rl) {
138
- ;
139
- this.rl.line = '';
140
- }
141
- if (this.isReceivingResponse) {
142
- this.handleStopResponse();
143
- }
144
- else {
145
- const now = Date.now();
146
- if (now - this.lastSigintTime < 5000) {
147
- this.handleExit();
82
+ process.on('SIGTSTP', () => this.handleExit());
83
+ }
84
+ initReadlineInterface() {
85
+ this.rl = readline.createInterface({
86
+ input: process.stdin,
87
+ output: process.stdout,
88
+ historySize: 1000,
89
+ terminal: true,
90
+ completer: this.completer.bind(this),
91
+ });
92
+ this.rl.on('line', (line) => this.handleLine(line));
93
+ this.rl.on('SIGINT', () => this.handleSigint());
94
+ this.rl.on('close', () => this.handleExit());
95
+ process.stdin.on('keypress', (str, key) => this.handleKeyPress(str, key));
96
+ }
97
+ completer(line) {
98
+ if (!this.client.fileContext?.fileTree)
99
+ return [[], line];
100
+ const tokenNames = Object.values(this.client.fileContext.fileTokenScores).flatMap((o) => Object.keys(o));
101
+ const paths = (0, project_file_tree_1.getAllFilePaths)(this.client.fileContext.fileTree);
102
+ const lastWord = line.split(' ').pop() || '';
103
+ const lastWordLower = lastWord.toLowerCase();
104
+ const matchingTokens = [...tokenNames, ...paths].filter((token) => token.toLowerCase().startsWith(lastWordLower) ||
105
+ token.toLowerCase().includes('/' + lastWordLower));
106
+ if (matchingTokens.length > 1) {
107
+ const suffixes = matchingTokens.map((token) => {
108
+ const index = token.toLowerCase().indexOf(lastWordLower);
109
+ return token.slice(index + lastWord.length);
110
+ });
111
+ let commonPrefix = '';
112
+ const firstSuffix = suffixes[0];
113
+ for (let i = 0; i < firstSuffix.length; i++) {
114
+ const char = firstSuffix[i];
115
+ if (suffixes.every((suffix) => suffix[i] === char)) {
116
+ commonPrefix += char;
148
117
  }
149
118
  else {
150
- this.lastSigintTime = now;
151
- console.log('\nPress Ctrl-C again to exit');
152
- this.rl.prompt();
119
+ break;
153
120
  }
154
121
  }
155
- });
156
- this.rl.on('close', () => {
157
- this.handleExit();
158
- });
159
- process.on('SIGTSTP', () => {
160
- // Exit on Ctrl+Z
161
- this.handleExit();
162
- });
163
- process.stdin.on('keypress', (str, key) => {
164
- if (key.name === 'escape') {
165
- this.handleEscKey();
166
- }
167
- // Make double spaces into newlines
168
- if (!this.isPasting &&
169
- 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());
122
+ if (commonPrefix) {
123
+ return [[lastWord + commonPrefix], lastWord];
234
124
  }
235
125
  }
126
+ return [matchingTokens, lastWord];
236
127
  }
237
128
  setPrompt() {
238
- this.rl.setPrompt((0, picocolors_1.green)(`${(0, path_1.parse)((0, project_files_1.getProjectRoot)()).base} > `));
129
+ this.rl.setPrompt((0, picocolors_1.green)(`${(0, path_1.parse)((0, project_files_1.getProjectRoot)()).base} ${checkpoints_1.checkpointManager.getNextId()} > `));
130
+ }
131
+ promptWithCheckpointNumber() {
132
+ this.setPrompt();
133
+ this.rl.prompt();
239
134
  }
240
135
  async printInitialPrompt(initialInput) {
241
136
  if (this.client.user) {
242
137
  (0, menu_1.displayGreeting)(this.costMode, this.client.user.name);
243
138
  }
244
139
  else {
245
- console.log(`Welcome to Codebuff! Let's get your account set up.`);
140
+ console.log(`Welcome to Codebuff! Give us a sec to get your account set up...`);
246
141
  await this.client.login();
247
142
  return;
248
143
  }
249
- this.rl.prompt();
144
+ this.promptWithCheckpointNumber();
250
145
  if (initialInput) {
251
146
  process.stdout.write(initialInput + '\n');
252
147
  this.handleUserInput(initialInput);
253
148
  }
254
149
  }
255
- handleUndo() {
256
- this.navigateFileVersion('undo');
257
- this.rl.prompt();
258
- }
259
- handleRedo() {
260
- this.navigateFileVersion('redo');
261
- this.rl.prompt();
262
- }
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();
150
+ async printDiff() {
151
+ this.handleDiff();
152
+ this.promptWithCheckpointNumber();
288
153
  }
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
154
+ async handleLine(line) {
155
+ this.detectPasting();
156
+ if (this.isPasting) {
157
+ this.pastedContent += line + '\n';
336
158
  }
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;
159
+ else if (!this.isReceivingResponse) {
160
+ if (this.pastedContent) {
161
+ await this.handleUserInput((this.pastedContent + line).trim());
162
+ this.pastedContent = '';
163
+ }
164
+ else {
165
+ await this.handleUserInput(line.trim());
166
+ }
346
167
  }
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
168
  }
372
169
  async handleUserInput(userInput) {
373
170
  if (!userInput)
374
171
  return;
375
172
  userInput = userInput.trim();
376
- // Handle commands
173
+ if (await this.processCommand(userInput)) {
174
+ return;
175
+ }
176
+ await this.forwardUserInput(userInput);
177
+ }
178
+ async beforeProcessCommand(userInput) {
179
+ await this.readyPromise;
180
+ // Save the current agent state
181
+ checkpoints_1.checkpointManager.addCheckpoint(this.client.agentState, userInput);
182
+ }
183
+ async processCommand(userInput) {
184
+ await this.beforeProcessCommand(userInput);
377
185
  if (userInput === 'help' || userInput === 'h') {
378
186
  (0, menu_1.displayMenu)();
379
- this.rl.prompt();
380
- return;
187
+ this.promptWithCheckpointNumber();
188
+ return true;
381
189
  }
382
190
  if (userInput === 'login' || userInput === 'signin') {
383
191
  await this.client.login();
384
- return;
192
+ return true;
385
193
  }
386
- else if (userInput === 'logout' || userInput === 'signout') {
194
+ if (userInput === 'logout' || userInput === 'signout') {
387
195
  await this.client.logout();
388
- this.rl.prompt();
389
- return;
196
+ this.promptWithCheckpointNumber();
197
+ return true;
390
198
  }
391
- else if (userInput.startsWith('ref-')) {
199
+ if (userInput.startsWith('ref-')) {
392
200
  await this.client.handleReferralCode(userInput.trim());
393
- return;
201
+ return true;
394
202
  }
395
- else if (userInput === 'usage' || userInput === 'credits') {
203
+ if (userInput === 'usage' || userInput === 'credits') {
396
204
  this.client.getUsage();
397
- return;
205
+ return true;
398
206
  }
399
- else if (userInput === 'undo' || userInput === 'u') {
207
+ if (userInput === 'undo' || userInput === 'u') {
400
208
  this.handleUndo();
401
- return;
402
- }
403
- else if (userInput === 'redo' || userInput === 'r') {
404
- this.handleRedo();
405
- return;
209
+ return true;
406
210
  }
407
- else if (userInput === 'quit' ||
408
- userInput === 'exit' ||
409
- userInput === 'q') {
211
+ if (userInput === 'quit' || userInput === 'exit' || userInput === 'q') {
410
212
  this.handleExit();
411
- return;
213
+ return true;
412
214
  }
413
- else if (userInput === 'diff' ||
414
- userInput === 'doff' ||
415
- userInput === 'dif' ||
416
- userInput === 'iff' ||
417
- userInput === 'd') {
215
+ if (['diff', 'doff', 'dif', 'iff', 'd'].includes(userInput)) {
418
216
  this.handleDiff();
419
- this.rl.prompt();
420
- return;
217
+ this.promptWithCheckpointNumber();
218
+ return true;
219
+ }
220
+ if (userInput === 'uuddlrlrba' ||
221
+ userInput === 'konami' ||
222
+ userInput === 'codebuffy') {
223
+ this.showEasterEgg();
224
+ return true;
225
+ }
226
+ // Checkpoint commands
227
+ if (userInput === 'checkpoint list' || userInput === 'checkpoints') {
228
+ this.handleCheckpoints();
229
+ return true;
230
+ }
231
+ const checkpointDetailMatch = userInput.match(/^checkpoint\s+(\d+)$/);
232
+ if (checkpointDetailMatch) {
233
+ const id = parseInt(checkpointDetailMatch[1], 10);
234
+ this.handleCheckpointDetail(id);
235
+ return true;
236
+ }
237
+ const restoreMatch = userInput.match(/^restore\s+(\d+)$/);
238
+ if (restoreMatch) {
239
+ const id = parseInt(restoreMatch[1], 10);
240
+ this.handleRestoreCheckpoint(id);
241
+ return true;
242
+ }
243
+ if (userInput === 'checkpoint clear') {
244
+ this.handleClearCheckpoints();
245
+ return true;
421
246
  }
422
247
  const runPrefix = '/run ';
248
+ const bangPrefix = '!';
423
249
  const hasRunPrefix = userInput.startsWith(runPrefix);
250
+ const hasBangPrefix = userInput.startsWith(bangPrefix);
424
251
  if (hasRunPrefix ||
425
- (!constants_1.SKIPPED_TERMINAL_COMMANDS.some((command) => userInput.toLowerCase().startsWith(command)) &&
252
+ hasBangPrefix ||
253
+ (!constants_1.SKIPPED_TERMINAL_COMMANDS.some((cmd) => userInput.toLowerCase().startsWith(cmd)) &&
426
254
  !userInput.includes('error ') &&
427
255
  !userInput.includes("'") &&
428
256
  userInput.split(' ').length <= 5)) {
429
- const withoutRunPrefix = userInput.replace(runPrefix, '');
430
- const { result, stdout } = await (0, tool_handlers_1.handleRunTerminalCommand)({ command: withoutRunPrefix }, 'user', 'user');
257
+ let commandToRun = userInput;
258
+ if (hasRunPrefix) {
259
+ commandToRun = userInput.replace(runPrefix, '');
260
+ }
261
+ else if (hasBangPrefix) {
262
+ commandToRun = userInput.replace(bangPrefix, '');
263
+ }
264
+ const { result, stdout } = await (0, tool_handlers_1.handleRunTerminalCommand)({ command: commandToRun }, 'user', 'user', (0, project_files_1.getProjectRoot)());
431
265
  if (result !== 'command not found') {
432
- this.setPrompt();
433
- this.rl.prompt();
434
- return;
266
+ this.promptWithCheckpointNumber();
267
+ return true;
435
268
  }
436
- else if (hasRunPrefix) {
269
+ else if (hasRunPrefix || hasBangPrefix) {
437
270
  process.stdout.write(stdout);
438
- this.setPrompt();
439
- this.rl.prompt();
440
- return;
271
+ this.promptWithCheckpointNumber();
272
+ return true;
441
273
  }
442
274
  }
443
- this.startLoadingAnimation();
444
- await this.readyPromise;
275
+ return false;
276
+ }
277
+ async forwardUserInput(userInput) {
278
+ spinner_1.Spinner.get().start();
279
+ this.client.lastChanges = [];
445
280
  const currentChat = this.chatStorage.getCurrentChat();
446
281
  const { fileVersions } = currentChat;
447
282
  const currentFileVersion = fileVersions[fileVersions.length - 1]?.files ?? {};
@@ -466,60 +301,330 @@ class CLI {
466
301
  };
467
302
  this.chatStorage.addMessage(currentChat, newMessage);
468
303
  this.isReceivingResponse = true;
469
- const { response, changes, changesAlreadyApplied } = await this.sendUserInputAndAwaitResponse();
304
+ const { responsePromise, stopResponse } = await this.client.sendUserInput(userInput);
305
+ this.stopResponse = stopResponse;
306
+ await responsePromise;
307
+ this.stopResponse = null;
470
308
  this.isReceivingResponse = false;
471
- this.stopLoadingAnimation();
472
- const allChanges = [...changesAlreadyApplied, ...changes];
473
- const filesChanged = (0, lodash_1.uniq)(allChanges.map((change) => change.filePath));
474
- const allFilesChanged = this.chatStorage.saveFilesChanged(filesChanged);
475
- // Stage files about to be changed if flag was set
476
- if (this.git === 'stage' && changes.length > 0) {
477
- const didStage = (0, git_1.stagePatches)((0, project_files_1.getProjectRoot)(), changes);
478
- if (didStage) {
479
- console.log((0, picocolors_1.green)('\nStaged previous changes'));
309
+ spinner_1.Spinner.get().stop();
310
+ if (this.client.lastRequestCredits >= constants_1.REQUEST_CREDIT_SHOW_THRESHOLD) {
311
+ console.log(`\n${(0, string_1.pluralize)(this.client.lastRequestCredits, 'credit')} used for this request.`);
312
+ }
313
+ this.client.showUsageWarning();
314
+ console.log();
315
+ this.promptWithCheckpointNumber();
316
+ }
317
+ returnControlToUser() {
318
+ this.promptWithCheckpointNumber();
319
+ this.isReceivingResponse = false;
320
+ if (this.stopResponse) {
321
+ this.stopResponse();
322
+ }
323
+ spinner_1.Spinner.get().stop();
324
+ }
325
+ onWebSocketError() {
326
+ spinner_1.Spinner.get().stop();
327
+ this.isReceivingResponse = false;
328
+ if (this.stopResponse) {
329
+ this.stopResponse();
330
+ this.stopResponse = null;
331
+ }
332
+ console.error((0, picocolors_1.yellow)('\nCould not connect. Retrying...'));
333
+ }
334
+ onWebSocketReconnect() {
335
+ console.log((0, picocolors_1.green)('\nReconnected!'));
336
+ this.returnControlToUser();
337
+ }
338
+ handleUndo() {
339
+ // Get previous checkpoint number (not including undo command)
340
+ const checkpointId = checkpoints_1.checkpointManager.getLatestCheckpoint().id - 1;
341
+ if (checkpointId < 1) {
342
+ console.log((0, picocolors_1.red)('Nothing to undo.'));
343
+ return;
344
+ }
345
+ this.restoreAgentStateAndFiles(checkpoints_1.checkpointManager.getCheckpoint(checkpointId));
346
+ console.log((0, picocolors_1.green)(`Restored to checkpoint #${checkpointId}.`));
347
+ this.promptWithCheckpointNumber();
348
+ }
349
+ handleKeyPress(str, key) {
350
+ if (key.name === 'escape') {
351
+ this.handleEscKey();
352
+ }
353
+ if (!this.isPasting &&
354
+ str === ' ' &&
355
+ '_refreshLine' in this.rl &&
356
+ 'line' in this.rl &&
357
+ 'cursor' in this.rl) {
358
+ const rlAny = this.rl;
359
+ const { cursor, line } = rlAny;
360
+ const prevTwoChars = cursor > 1 ? line.slice(cursor - 2, cursor) : '';
361
+ if (prevTwoChars === ' ') {
362
+ rlAny.line = line.slice(0, cursor - 2) + '\n\n' + line.slice(cursor);
363
+ rlAny._refreshLine();
480
364
  }
481
365
  }
482
- const { created, modified } = (0, changes_1.applyChanges)((0, project_files_1.getProjectRoot)(), changes);
483
- if (created.length > 0 || modified.length > 0) {
484
- console.log();
366
+ this.detectPasting();
367
+ }
368
+ handleSigint() {
369
+ if ((0, terminal_1.isCommandRunning)()) {
370
+ (0, terminal_1.resetShell)((0, project_files_1.getProjectRoot)());
485
371
  }
486
- for (const file of created) {
487
- console.log((0, picocolors_1.green)(`- Created ${file}`));
372
+ if ('line' in this.rl) {
373
+ ;
374
+ this.rl.line = '';
488
375
  }
489
- for (const file of modified) {
490
- console.log((0, picocolors_1.green)(`- Updated ${file}`));
376
+ if (this.isReceivingResponse) {
377
+ this.handleStopResponse();
491
378
  }
492
- if (created.length > 0 || modified.length > 0) {
493
- if (this.client.lastRequestCredits > constants_1.REQUEST_CREDIT_SHOW_THRESHOLD) {
494
- console.log(`\n${(0, string_1.pluralize)(this.client.lastRequestCredits, 'credit')} used for this request.`);
379
+ else {
380
+ const now = Date.now();
381
+ if (now - this.lastSigintTime < 5000) {
382
+ this.handleExit();
383
+ }
384
+ else {
385
+ this.lastSigintTime = now;
386
+ console.log('\nPress Ctrl-C again to exit');
387
+ this.promptWithCheckpointNumber();
495
388
  }
496
- console.log('Complete! Type "diff" to see the changes made.');
497
- this.client.showUsageWarning();
498
389
  }
499
- console.log();
500
- this.lastChanges = allChanges;
501
- const assistantMessage = {
502
- role: 'assistant',
503
- content: response,
504
- };
505
- this.chatStorage.addMessage(this.chatStorage.getCurrentChat(), assistantMessage);
506
- const updatedFiles = (0, project_files_1.getExistingFiles)(allFilesChanged);
507
- this.chatStorage.addNewFileState(updatedFiles);
508
- this.rl.prompt();
509
390
  }
510
- async sendUserInputAndAwaitResponse() {
511
- const userInputId = `mc-input-` + Math.random().toString(36).substring(2, 15);
512
- const { responsePromise, stopResponse } = this.client.subscribeToResponse((chunk) => {
513
- process.stdout.write(chunk);
514
- }, userInputId, () => {
515
- this.stopLoadingAnimation();
516
- process.stdout.write((0, picocolors_1.green)((0, picocolors_1.underline)('\nCodebuff') + ':') + ' ');
391
+ handleEscKey() {
392
+ if (this.isReceivingResponse) {
393
+ this.handleStopResponse();
394
+ }
395
+ }
396
+ handleStopResponse() {
397
+ console.log((0, picocolors_1.yellow)('\n[Response stopped by user]'));
398
+ this.isReceivingResponse = false;
399
+ if (this.stopResponse) {
400
+ this.stopResponse();
401
+ }
402
+ spinner_1.Spinner.get().stop();
403
+ }
404
+ async showEasterEgg() {
405
+ const text = 'codebuffy';
406
+ // Utility: clear the terminal screen
407
+ function clearScreen() {
408
+ process.stdout.write('\u001b[2J\u001b[0;0H');
409
+ }
410
+ const termWidth = process.stdout.columns;
411
+ const termHeight = process.stdout.rows;
412
+ const baselineWidth = 80;
413
+ const baselineHeight = 24;
414
+ const scaleFactor = Math.min(termWidth / baselineWidth, termHeight / baselineHeight);
415
+ // Utility: Generate a set of points tracing a "C" shape using an arc.
416
+ function generateCPath(cx, cy, r, steps) {
417
+ const points = [];
418
+ // A typical "C" opens to the right: from 45° to 315° (in radians)
419
+ const startAngle = Math.PI / 4;
420
+ const endAngle = (7 * Math.PI) / 4;
421
+ const angleStep = (endAngle - startAngle) / steps;
422
+ for (let i = 0; i <= steps; i++) {
423
+ const angle = startAngle + i * angleStep;
424
+ const x = Math.floor(cx + r * Math.cos(angle));
425
+ const y = Math.floor(cy + r * Math.sin(angle));
426
+ points.push({ x, y });
427
+ }
428
+ return points;
429
+ }
430
+ // Utility: Generate points along a quadratic Bézier curve.
431
+ function quadraticBezier(P0, P1, P2, steps) {
432
+ const points = [];
433
+ for (let i = 0; i <= steps; i++) {
434
+ const t = i / steps;
435
+ const x = Math.round((1 - t) ** 2 * P0.x + 2 * (1 - t) * t * P1.x + t ** 2 * P2.x);
436
+ const y = Math.round((1 - t) ** 2 * P0.y + 2 * (1 - t) * t * P1.y + t ** 2 * P2.y);
437
+ points.push({ x, y });
438
+ }
439
+ return points;
440
+ }
441
+ // Generate a vertical line from startY to endY at a given x.
442
+ function generateVerticalLine(x, startY, endY) {
443
+ const points = [];
444
+ const step = startY < endY ? 1 : -1;
445
+ for (let y = startY; y !== endY; y += step) {
446
+ points.push({ x, y });
447
+ }
448
+ points.push({ x, y: endY });
449
+ return points;
450
+ }
451
+ // Generate a path approximating a B shape using two quadratic Bézier curves
452
+ // for the rounded bubbles, and then closing the shape with a vertical spine.
453
+ function generateBPath(bX, bYTop, bYBottom, bWidth, bGap, stepsPerCurve) {
454
+ let points = [];
455
+ const middle = Math.floor((bYTop + bYBottom) / 2);
456
+ // Upper bubble: from top-left (spine) out then back to the spine at the middle.
457
+ const upperStart = { x: bX, y: bYTop };
458
+ const upperControl = {
459
+ x: bX + bWidth + bGap - 10,
460
+ y: Math.floor((bYTop + middle) / 2),
461
+ };
462
+ const upperEnd = { x: bX, y: middle };
463
+ const upperCurve = quadraticBezier(upperStart, upperControl, upperEnd, stepsPerCurve);
464
+ // Lower bubble: from the middle to the bottom.
465
+ const lowerStart = { x: bX, y: middle };
466
+ const lowerControl = {
467
+ x: bX + bWidth + bGap,
468
+ y: Math.floor((middle + bYBottom) / 2),
469
+ };
470
+ const lowerEnd = { x: bX, y: bYBottom };
471
+ const lowerCurve = quadraticBezier(lowerStart, lowerControl, lowerEnd, stepsPerCurve);
472
+ // Combine the curves.
473
+ points = points.concat(upperCurve, lowerCurve);
474
+ // Add a vertical line from the bottom of the B back up to the top.
475
+ const closingLine = generateVerticalLine(bX, bYBottom, bYTop);
476
+ points = points.concat(closingLine);
477
+ return points;
478
+ }
479
+ // Dynamically scale parameters for the shapes.
480
+ // Use Math.max to ensure values don't get too small.
481
+ const cCenterX = Math.floor(termWidth * 0.3);
482
+ const cCenterY = Math.floor(termHeight / 2);
483
+ const cRadius = Math.max(2, Math.floor(8 * scaleFactor));
484
+ const cSteps = Math.max(10, Math.floor(30 * scaleFactor));
485
+ const bX = Math.floor(termWidth * 0.55);
486
+ const bYTop = Math.floor(termHeight / 2 - 7 * scaleFactor);
487
+ const bYBottom = Math.floor(termHeight / 2 + 7 * scaleFactor);
488
+ const bWidth = Math.max(2, Math.floor(8 * scaleFactor));
489
+ const bGap = Math.max(1, Math.floor(35 * scaleFactor));
490
+ const bStepsPerCurve = Math.max(10, Math.floor(20 * scaleFactor));
491
+ // Generate the paths.
492
+ const fullPath = [
493
+ ...generateCPath(cCenterX, cCenterY, cRadius, cSteps),
494
+ ...generateBPath(bX, bYTop, bYBottom, bWidth, bGap, bStepsPerCurve),
495
+ ];
496
+ // Array of picocolors functions for random colors.
497
+ const colors = [picocolors_1.red, picocolors_1.green, picocolors_1.yellow, picocolors_1.blue, picocolors_1.magenta, picocolors_1.cyan];
498
+ function getRandomColor() {
499
+ return colors[Math.floor(Math.random() * colors.length)];
500
+ }
501
+ // Animation state: index into the fullPath.
502
+ let index = 0;
503
+ let completedCycle = false;
504
+ // Main animation function
505
+ function animate() {
506
+ if (index >= fullPath.length) {
507
+ completedCycle = true;
508
+ return;
509
+ }
510
+ const { x, y } = fullPath[index];
511
+ const cursorPosition = `\u001b[${y + 1};${x + 1}H`;
512
+ process.stdout.write(cursorPosition + getRandomColor()(text));
513
+ index++;
514
+ }
515
+ clearScreen();
516
+ const interval = setInterval(() => {
517
+ animate();
518
+ if (completedCycle) {
519
+ clearInterval(interval);
520
+ clearScreen();
521
+ this.returnControlToUser();
522
+ }
523
+ }, 100);
524
+ }
525
+ handleExit() {
526
+ spinner_1.Spinner.get().restoreCursor();
527
+ console.log('\n\n');
528
+ console.log(`${(0, string_1.pluralize)(this.client.sessionCreditsUsed, 'credit')} used this session.`);
529
+ if (this.client.limit && this.client.usage && this.client.nextQuotaReset) {
530
+ const daysUntilReset = Math.max(0, Math.floor((this.client.nextQuotaReset.getTime() - Date.now()) /
531
+ (1000 * 60 * 60 * 24)));
532
+ console.log(`${Math.max(0, this.client.limit - this.client.usage)} / ${this.client.limit} credits remaining. Renews in ${(0, string_1.pluralize)(daysUntilReset, 'day')}.`);
533
+ }
534
+ console.log((0, picocolors_1.green)('Codebuff out!'));
535
+ process.exit(0);
536
+ }
537
+ detectPasting() {
538
+ const currentTime = Date.now();
539
+ const timeDiff = currentTime - this.lastInputTime;
540
+ if (timeDiff < 10) {
541
+ this.consecutiveFastInputs++;
542
+ if (this.consecutiveFastInputs >= 2) {
543
+ this.isPasting = true;
544
+ }
545
+ }
546
+ else {
547
+ this.consecutiveFastInputs = 0;
548
+ if (this.isPasting) {
549
+ this.isPasting = false;
550
+ }
551
+ }
552
+ this.lastInputTime = currentTime;
553
+ }
554
+ handleDiff() {
555
+ if (this.client.lastChanges.length === 0) {
556
+ console.log((0, picocolors_1.yellow)('No changes found in the last assistant response.'));
557
+ return;
558
+ }
559
+ this.client.lastChanges.forEach((change) => {
560
+ console.log((0, picocolors_1.bold)(`___${change.path}___`));
561
+ const lines = change.content
562
+ .split('\n')
563
+ .map((line) => (change.type === 'file' ? '+' + line : line));
564
+ lines.forEach((line) => {
565
+ if (line.startsWith('+')) {
566
+ console.log((0, picocolors_1.green)(line));
567
+ }
568
+ else if (line.startsWith('-')) {
569
+ console.log((0, picocolors_1.red)(line));
570
+ }
571
+ else if (line.startsWith('@@')) {
572
+ console.log((0, picocolors_1.cyan)(line));
573
+ }
574
+ else {
575
+ console.log(line);
576
+ }
577
+ });
517
578
  });
518
- this.stopResponse = stopResponse;
519
- this.client.sendUserInput([], userInputId);
520
- const result = await responsePromise;
521
- this.stopResponse = null;
522
- return result;
579
+ }
580
+ // Checkpoint command handlers
581
+ handleCheckpoints() {
582
+ console.log(checkpoints_1.checkpointManager.getCheckpointsAsString());
583
+ this.promptWithCheckpointNumber();
584
+ }
585
+ handleCheckpointDetail(id) {
586
+ const checkpoint = checkpoints_1.checkpointManager.getCheckpoint(id);
587
+ if (!checkpoint) {
588
+ console.log((0, picocolors_1.red)(`Checkpoint #${id} not found.`));
589
+ }
590
+ else {
591
+ console.log(checkpoints_1.checkpointManager.getCheckpointDetails(id));
592
+ }
593
+ this.promptWithCheckpointNumber();
594
+ }
595
+ async handleRestoreCheckpoint(id) {
596
+ const checkpoint = checkpoints_1.checkpointManager.getCheckpoint(id);
597
+ if (!checkpoint) {
598
+ console.log((0, picocolors_1.red)(`Checkpoint #${id} not found.`));
599
+ return;
600
+ }
601
+ this.restoreAgentStateAndFiles(checkpoint);
602
+ console.log((0, picocolors_1.green)(`Restored to checkpoint #${id}.`));
603
+ // Insert the original user input that created this checkpoint
604
+ this.promptWithCheckpointNumber();
605
+ this.rl.write(checkpoint.userInput);
606
+ }
607
+ restoreAgentStateAndFiles(checkpoint) {
608
+ // Restore the agentState
609
+ this.client.agentState = JSON.parse(checkpoint.agentStateString);
610
+ // Restore file state
611
+ const toChange = {
612
+ ...this.client.originalFileVersions,
613
+ ...checkpoint.fileVersions,
614
+ };
615
+ for (const [filePath, fileContents] of Object.entries(toChange)) {
616
+ if (fileContents === null) {
617
+ // delete file
618
+ fs.unlinkSync(filePath);
619
+ }
620
+ else {
621
+ fs.writeFileSync(filePath, fileContents);
622
+ }
623
+ }
624
+ }
625
+ async handleClearCheckpoints() {
626
+ checkpoints_1.checkpointManager.clearCheckpoints();
627
+ this.promptWithCheckpointNumber();
523
628
  }
524
629
  }
525
630
  exports.CLI = CLI;