@compilr-dev/cli 0.4.0

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 (152) hide show
  1. package/README.md +110 -0
  2. package/dist/agent.d.ts +62 -0
  3. package/dist/agent.js +317 -0
  4. package/dist/agents/registry.d.ts +66 -0
  5. package/dist/agents/registry.js +238 -0
  6. package/dist/agents/types.d.ts +40 -0
  7. package/dist/agents/types.js +94 -0
  8. package/dist/commands/custom-registry.d.ts +69 -0
  9. package/dist/commands/custom-registry.js +246 -0
  10. package/dist/commands/index.d.ts +7 -0
  11. package/dist/commands/index.js +7 -0
  12. package/dist/commands/types.d.ts +31 -0
  13. package/dist/commands/types.js +26 -0
  14. package/dist/commands.d.ts +63 -0
  15. package/dist/commands.js +324 -0
  16. package/dist/db/index.d.ts +42 -0
  17. package/dist/db/index.js +146 -0
  18. package/dist/db/repositories/document-repository.d.ts +63 -0
  19. package/dist/db/repositories/document-repository.js +184 -0
  20. package/dist/db/repositories/index.d.ts +9 -0
  21. package/dist/db/repositories/index.js +6 -0
  22. package/dist/db/repositories/project-repository.d.ts +132 -0
  23. package/dist/db/repositories/project-repository.js +337 -0
  24. package/dist/db/repositories/work-item-repository.d.ts +115 -0
  25. package/dist/db/repositories/work-item-repository.js +389 -0
  26. package/dist/db/schema.d.ts +83 -0
  27. package/dist/db/schema.js +143 -0
  28. package/dist/debug.d.ts +8 -0
  29. package/dist/debug.js +48 -0
  30. package/dist/index.d.ts +2 -0
  31. package/dist/index.js +348 -0
  32. package/dist/index.old.d.ts +7 -0
  33. package/dist/index.old.js +1014 -0
  34. package/dist/repl.d.ts +121 -0
  35. package/dist/repl.js +1878 -0
  36. package/dist/settings/index.d.ts +80 -0
  37. package/dist/settings/index.js +195 -0
  38. package/dist/shared-handlers.d.ts +63 -0
  39. package/dist/shared-handlers.js +57 -0
  40. package/dist/slash-autocomplete.d.ts +41 -0
  41. package/dist/slash-autocomplete.js +638 -0
  42. package/dist/state.d.ts +75 -0
  43. package/dist/state.js +130 -0
  44. package/dist/tabbed-menu.d.ts +11 -0
  45. package/dist/tabbed-menu.js +328 -0
  46. package/dist/templates/backlog-md.d.ts +7 -0
  47. package/dist/templates/backlog-md.js +94 -0
  48. package/dist/templates/claude-md.d.ts +7 -0
  49. package/dist/templates/claude-md.js +189 -0
  50. package/dist/templates/coding-standards.d.ts +7 -0
  51. package/dist/templates/coding-standards.js +299 -0
  52. package/dist/templates/compilr-md.d.ts +7 -0
  53. package/dist/templates/compilr-md.js +189 -0
  54. package/dist/templates/config-json.d.ts +38 -0
  55. package/dist/templates/config-json.js +39 -0
  56. package/dist/templates/gitignore.d.ts +7 -0
  57. package/dist/templates/gitignore.js +85 -0
  58. package/dist/templates/index.d.ts +19 -0
  59. package/dist/templates/index.js +302 -0
  60. package/dist/templates/package-json.d.ts +7 -0
  61. package/dist/templates/package-json.js +111 -0
  62. package/dist/templates/readme-md.d.ts +7 -0
  63. package/dist/templates/readme-md.js +161 -0
  64. package/dist/templates/tsconfig.d.ts +7 -0
  65. package/dist/templates/tsconfig.js +61 -0
  66. package/dist/templates/types.d.ts +33 -0
  67. package/dist/templates/types.js +24 -0
  68. package/dist/test-autocomplete.d.ts +7 -0
  69. package/dist/test-autocomplete.js +85 -0
  70. package/dist/test-tabbed-menu.d.ts +7 -0
  71. package/dist/test-tabbed-menu.js +25 -0
  72. package/dist/themes/colors.d.ts +49 -0
  73. package/dist/themes/colors.js +135 -0
  74. package/dist/themes/index.d.ts +23 -0
  75. package/dist/themes/index.js +24 -0
  76. package/dist/themes/registry.d.ts +60 -0
  77. package/dist/themes/registry.js +195 -0
  78. package/dist/themes/types.d.ts +82 -0
  79. package/dist/themes/types.js +7 -0
  80. package/dist/tool-selector.d.ts +71 -0
  81. package/dist/tool-selector.js +184 -0
  82. package/dist/tools/ask-user-simple.d.ts +19 -0
  83. package/dist/tools/ask-user-simple.js +86 -0
  84. package/dist/tools/ask-user.d.ts +32 -0
  85. package/dist/tools/ask-user.js +113 -0
  86. package/dist/tools/backlog.d.ts +53 -0
  87. package/dist/tools/backlog.js +709 -0
  88. package/dist/tools.d.ts +15 -0
  89. package/dist/tools.js +121 -0
  90. package/dist/ui/agents-overlay.d.ts +12 -0
  91. package/dist/ui/agents-overlay.js +501 -0
  92. package/dist/ui/arch-type-overlay.d.ts +20 -0
  93. package/dist/ui/arch-type-overlay.js +229 -0
  94. package/dist/ui/ask-user-overlay.d.ts +26 -0
  95. package/dist/ui/ask-user-overlay.js +647 -0
  96. package/dist/ui/ask-user-simple-overlay.d.ts +25 -0
  97. package/dist/ui/ask-user-simple-overlay.js +242 -0
  98. package/dist/ui/backlog-overlay.d.ts +17 -0
  99. package/dist/ui/backlog-overlay.js +786 -0
  100. package/dist/ui/commands-overlay.d.ts +11 -0
  101. package/dist/ui/commands-overlay.js +410 -0
  102. package/dist/ui/config-overlay.d.ts +34 -0
  103. package/dist/ui/config-overlay.js +977 -0
  104. package/dist/ui/conversation.d.ts +82 -0
  105. package/dist/ui/conversation.js +508 -0
  106. package/dist/ui/diff.d.ts +38 -0
  107. package/dist/ui/diff.js +182 -0
  108. package/dist/ui/ephemeral.d.ts +111 -0
  109. package/dist/ui/ephemeral.js +413 -0
  110. package/dist/ui/file-autocomplete.d.ts +45 -0
  111. package/dist/ui/file-autocomplete.js +237 -0
  112. package/dist/ui/footer.d.ts +153 -0
  113. package/dist/ui/footer.js +422 -0
  114. package/dist/ui/index.d.ts +12 -0
  115. package/dist/ui/index.js +15 -0
  116. package/dist/ui/init-overlay.d.ts +24 -0
  117. package/dist/ui/init-overlay.js +525 -0
  118. package/dist/ui/input-prompt-v2.d.ts +179 -0
  119. package/dist/ui/input-prompt-v2.js +991 -0
  120. package/dist/ui/input-prompt.d.ts +97 -0
  121. package/dist/ui/input-prompt.js +800 -0
  122. package/dist/ui/iteration-limit-overlay.d.ts +21 -0
  123. package/dist/ui/iteration-limit-overlay.js +150 -0
  124. package/dist/ui/keys-overlay.d.ts +14 -0
  125. package/dist/ui/keys-overlay.js +181 -0
  126. package/dist/ui/model-warning-overlay.d.ts +30 -0
  127. package/dist/ui/model-warning-overlay.js +171 -0
  128. package/dist/ui/overlay-controller.d.ts +25 -0
  129. package/dist/ui/overlay-controller.js +35 -0
  130. package/dist/ui/overlays.d.ts +47 -0
  131. package/dist/ui/overlays.js +627 -0
  132. package/dist/ui/permission-overlay.d.ts +16 -0
  133. package/dist/ui/permission-overlay.js +494 -0
  134. package/dist/ui/terminal.d.ts +117 -0
  135. package/dist/ui/terminal.js +237 -0
  136. package/dist/ui/todo-zone.d.ts +112 -0
  137. package/dist/ui/todo-zone.js +353 -0
  138. package/dist/ui/tools-overlay.d.ts +26 -0
  139. package/dist/ui/tools-overlay.js +278 -0
  140. package/dist/ui/tutorial-overlay.d.ts +10 -0
  141. package/dist/ui/tutorial-overlay.js +936 -0
  142. package/dist/ui/types.d.ts +103 -0
  143. package/dist/ui/types.js +33 -0
  144. package/dist/utils/credentials.d.ts +55 -0
  145. package/dist/utils/credentials.js +268 -0
  146. package/dist/utils/model-tiers.d.ts +37 -0
  147. package/dist/utils/model-tiers.js +118 -0
  148. package/dist/utils/project-memory.d.ts +47 -0
  149. package/dist/utils/project-memory.js +117 -0
  150. package/dist/utils/project-status.d.ts +56 -0
  151. package/dist/utils/project-status.js +237 -0
  152. package/package.json +66 -0
@@ -0,0 +1,991 @@
1
+ /**
2
+ * Input Prompt v2 (Event-Driven)
3
+ *
4
+ * Refactored input handling with event-driven architecture.
5
+ * - Emits events instead of blocking with getInput()
6
+ * - Supports queue mode for capturing input during agent execution
7
+ * - Always captures keystrokes (no blocking)
8
+ *
9
+ * Events:
10
+ * - 'submit' - User pressed Enter with input
11
+ * - 'command' - User submitted a slash command
12
+ * - 'cancel' - User pressed Ctrl+C
13
+ * - 'escape' - User pressed Esc (for aborting agent)
14
+ * - 'change' - Input text changed
15
+ */
16
+ import { EventEmitter } from 'events';
17
+ import chalk from 'chalk';
18
+ import { getStyles } from '../themes/index.js';
19
+ import * as terminal from './terminal.js';
20
+ import { getAutocompleteCommands } from '../commands.js';
21
+ import { getFileMatches, extractAtMention, replaceAtMention, } from './file-autocomplete.js';
22
+ // =============================================================================
23
+ // Constants
24
+ // =============================================================================
25
+ const MAX_VISIBLE_COMMANDS = 10;
26
+ // =============================================================================
27
+ // Default Commands (from central registry)
28
+ // =============================================================================
29
+ export const DEFAULT_COMMANDS = getAutocompleteCommands();
30
+ // =============================================================================
31
+ // Helper Functions
32
+ // =============================================================================
33
+ /**
34
+ * Strip ANSI codes from string
35
+ */
36
+ export function stripAnsi(str) {
37
+ // eslint-disable-next-line no-control-regex
38
+ return str.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, '');
39
+ }
40
+ /**
41
+ * Calculate physical lines for wrapped text
42
+ */
43
+ function calcPhysicalLines(text, startCol, termWidth) {
44
+ if (text.length === 0)
45
+ return 1;
46
+ const totalLen = startCol + text.length;
47
+ return Math.ceil(totalLen / termWidth) || 1;
48
+ }
49
+ /**
50
+ * Calculate fuzzy match score for a query against a target string.
51
+ * Higher score = better match.
52
+ * Returns -1 if no match.
53
+ */
54
+ function fuzzyMatchScore(query, target) {
55
+ const queryLower = query.toLowerCase();
56
+ const targetLower = target.toLowerCase();
57
+ // Exact prefix match - highest priority (score 1000+)
58
+ if (targetLower.startsWith(queryLower)) {
59
+ return 1000 + (100 - target.length); // Shorter commands rank higher
60
+ }
61
+ // Contiguous substring match - high priority (score 500+)
62
+ if (targetLower.includes(queryLower)) {
63
+ const index = targetLower.indexOf(queryLower);
64
+ return 500 + (100 - index); // Earlier matches rank higher
65
+ }
66
+ // Fuzzy match - characters appear in order (score 100+)
67
+ let queryIdx = 0;
68
+ let consecutiveBonus = 0;
69
+ let lastMatchIdx = -1;
70
+ for (let i = 0; i < targetLower.length && queryIdx < queryLower.length; i++) {
71
+ if (targetLower[i] === queryLower[queryIdx]) {
72
+ // Bonus for consecutive matches
73
+ if (lastMatchIdx === i - 1) {
74
+ consecutiveBonus += 10;
75
+ }
76
+ lastMatchIdx = i;
77
+ queryIdx++;
78
+ }
79
+ }
80
+ // All query characters found in order
81
+ if (queryIdx === queryLower.length) {
82
+ return 100 + consecutiveBonus + (100 - target.length);
83
+ }
84
+ // No match
85
+ return -1;
86
+ }
87
+ /**
88
+ * Filter and rank commands matching input using fuzzy matching
89
+ */
90
+ function filterCommands(input, commands) {
91
+ // Score all commands
92
+ const scored = commands
93
+ .map((cmd) => ({
94
+ cmd,
95
+ score: fuzzyMatchScore(input, cmd.command),
96
+ }))
97
+ .filter((item) => item.score >= 0);
98
+ // Sort by score (highest first)
99
+ scored.sort((a, b) => b.score - a.score);
100
+ return scored.map((item) => item.cmd);
101
+ }
102
+ // =============================================================================
103
+ // Input Prompt Class
104
+ // =============================================================================
105
+ export class InputPrompt extends EventEmitter {
106
+ // Configuration
107
+ prompt;
108
+ promptLen;
109
+ showSeparators;
110
+ commands;
111
+ // Input state
112
+ state = {
113
+ lines: [''],
114
+ currentLine: 0,
115
+ cursorPos: 0,
116
+ };
117
+ // Command autocomplete (for /commands)
118
+ autocomplete = {
119
+ active: false,
120
+ matches: [],
121
+ selectedIndex: 0,
122
+ scrollOffset: 0,
123
+ };
124
+ // File autocomplete (for @paths)
125
+ fileAutocomplete = {
126
+ active: false,
127
+ matches: [],
128
+ selectedIndex: 0,
129
+ scrollOffset: 0,
130
+ partial: '',
131
+ };
132
+ // History
133
+ history = [];
134
+ historyIndex = -1;
135
+ savedInput = '';
136
+ // Queue mode
137
+ queueMode = false;
138
+ queuedInputs = [];
139
+ // Rendering tracking
140
+ renderedLines = 0;
141
+ dropdownLines = 0;
142
+ linesAboveCursor = 0;
143
+ hasSeparators = false;
144
+ // Control
145
+ isRunning = false;
146
+ // Double Esc detection
147
+ lastEscTime = 0;
148
+ DOUBLE_ESC_THRESHOLD_MS = 500; // 500ms window for double Esc
149
+ // Suggestion (ghost text for next action)
150
+ suggestion = null;
151
+ constructor(options = {}) {
152
+ super();
153
+ this.prompt = options.prompt ?? getStyles().primary('❯ ');
154
+ this.promptLen = stripAnsi(this.prompt).length;
155
+ this.showSeparators = options.showSeparators ?? true;
156
+ this.commands = options.commands ?? DEFAULT_COMMANDS;
157
+ }
158
+ // ===========================================================================
159
+ // Public API
160
+ // ===========================================================================
161
+ /**
162
+ * Update the prompt string (for dynamic theme changes)
163
+ */
164
+ setPrompt(prompt) {
165
+ this.prompt = prompt;
166
+ this.promptLen = stripAnsi(prompt).length;
167
+ }
168
+ /**
169
+ * Set a suggestion for the next action (ghost text)
170
+ */
171
+ setSuggestion(action) {
172
+ this.suggestion = action;
173
+ }
174
+ /**
175
+ * Get the current suggestion
176
+ */
177
+ getSuggestion() {
178
+ return this.suggestion;
179
+ }
180
+ /**
181
+ * Clear the current suggestion
182
+ */
183
+ clearSuggestion() {
184
+ this.suggestion = null;
185
+ }
186
+ /**
187
+ * Start input capture (non-blocking, event-driven)
188
+ */
189
+ start() {
190
+ if (this.isRunning)
191
+ return;
192
+ this.isRunning = true;
193
+ terminal.enableRawMode();
194
+ this.resetState();
195
+ process.stdin.on('data', this.handleData);
196
+ }
197
+ /**
198
+ * Stop input capture
199
+ */
200
+ stop() {
201
+ if (!this.isRunning)
202
+ return;
203
+ this.isRunning = false;
204
+ terminal.disableRawMode();
205
+ process.stdin.removeListener('data', this.handleData);
206
+ }
207
+ /**
208
+ * Check if running
209
+ */
210
+ isActive() {
211
+ return this.isRunning;
212
+ }
213
+ /**
214
+ * Enable/disable queue mode
215
+ * In queue mode, Enter adds to queue instead of emitting 'submit'
216
+ */
217
+ setQueueMode(enabled) {
218
+ this.queueMode = enabled;
219
+ }
220
+ /**
221
+ * Check if in queue mode
222
+ */
223
+ isQueueMode() {
224
+ return this.queueMode;
225
+ }
226
+ /**
227
+ * Get all queued inputs (FIFO order)
228
+ */
229
+ getQueuedInputs() {
230
+ return [...this.queuedInputs];
231
+ }
232
+ /**
233
+ * Pop the first queued input
234
+ */
235
+ popQueuedInput() {
236
+ return this.queuedInputs.shift() ?? null;
237
+ }
238
+ /**
239
+ * Check if there are queued inputs
240
+ */
241
+ hasQueuedInput() {
242
+ return this.queuedInputs.length > 0;
243
+ }
244
+ /**
245
+ * Get number of queued inputs
246
+ */
247
+ getQueueLength() {
248
+ return this.queuedInputs.length;
249
+ }
250
+ /**
251
+ * Clear the queue
252
+ */
253
+ clearQueue() {
254
+ this.queuedInputs = [];
255
+ }
256
+ /**
257
+ * Get current buffer value
258
+ */
259
+ getValue() {
260
+ return this.state.lines.join('\n');
261
+ }
262
+ /**
263
+ * Set buffer value
264
+ */
265
+ setValue(value) {
266
+ this.state.lines = value.split('\n');
267
+ this.state.currentLine = this.state.lines.length - 1;
268
+ this.state.cursorPos = this.state.lines[this.state.currentLine].length;
269
+ this.updateAutocomplete();
270
+ }
271
+ /**
272
+ * Clear input buffer
273
+ */
274
+ clearInput() {
275
+ this.state = {
276
+ lines: [''],
277
+ currentLine: 0,
278
+ cursorPos: 0,
279
+ };
280
+ this.autocomplete = {
281
+ active: false,
282
+ matches: [],
283
+ selectedIndex: 0,
284
+ scrollOffset: 0,
285
+ };
286
+ this.fileAutocomplete = {
287
+ active: false,
288
+ matches: [],
289
+ selectedIndex: 0,
290
+ scrollOffset: 0,
291
+ partial: '',
292
+ };
293
+ }
294
+ /**
295
+ * Add command to history
296
+ */
297
+ addToHistory(input) {
298
+ if (input.trim() && (this.history.length === 0 || this.history[this.history.length - 1] !== input)) {
299
+ this.history.push(input);
300
+ }
301
+ }
302
+ /**
303
+ * Render the input prompt - returns array of lines
304
+ * Does NOT write to terminal (Footer handles that)
305
+ */
306
+ render() {
307
+ const lines = [];
308
+ // Top separator
309
+ if (this.showSeparators) {
310
+ lines.push(this.getSeparatorLine());
311
+ }
312
+ // Input lines
313
+ const s = getStyles();
314
+ for (let i = 0; i < this.state.lines.length; i++) {
315
+ const linePrompt = i === 0 ? this.prompt : s.muted(' \\ ');
316
+ const lineContent = this.state.lines[i];
317
+ // Show suggestion as ghost text on first line when input is empty
318
+ if (i === 0 && lineContent === '' && this.suggestion && !this.autocomplete.active && !this.fileAutocomplete.active) {
319
+ const hint = s.muted(' (tab to accept)');
320
+ lines.push(linePrompt + s.muted(this.suggestion) + hint);
321
+ }
322
+ else {
323
+ lines.push(linePrompt + lineContent);
324
+ }
325
+ }
326
+ // Bottom separator
327
+ if (this.showSeparators) {
328
+ lines.push(this.getSeparatorLine());
329
+ }
330
+ return lines;
331
+ }
332
+ /**
333
+ * Get the cursor position info for rendering
334
+ */
335
+ getCursorInfo() {
336
+ const termWidth = terminal.getTerminalWidth();
337
+ const currentLinePromptLen = this.state.currentLine === 0 ? this.promptLen : 5;
338
+ const cursorAbsPos = currentLinePromptLen + this.state.cursorPos;
339
+ // Calculate which row within input (accounting for separators)
340
+ let row = this.showSeparators ? 1 : 0; // Start after top separator
341
+ for (let i = 0; i < this.state.currentLine; i++) {
342
+ const lp = i === 0 ? this.promptLen : 5;
343
+ row += calcPhysicalLines(this.state.lines[i], lp, termWidth);
344
+ }
345
+ row += Math.floor(cursorAbsPos / termWidth);
346
+ const col = cursorAbsPos % termWidth;
347
+ return { row, col };
348
+ }
349
+ /**
350
+ * Get autocomplete dropdown lines (if active)
351
+ */
352
+ getAutocompleteLines() {
353
+ const s = getStyles();
354
+ // File autocomplete takes priority
355
+ if (this.fileAutocomplete.active && this.fileAutocomplete.matches.length > 0) {
356
+ const lines = [];
357
+ const offset = this.fileAutocomplete.scrollOffset;
358
+ const visible = this.fileAutocomplete.matches.slice(offset, offset + MAX_VISIBLE_COMMANDS);
359
+ const total = this.fileAutocomplete.matches.length;
360
+ // Show scroll indicator if there are more items above
361
+ if (offset > 0) {
362
+ lines.push(s.muted(` ↑ ${String(offset)} more above`));
363
+ }
364
+ for (let i = 0; i < visible.length; i++) {
365
+ const file = visible[i];
366
+ const actualIndex = offset + i;
367
+ const isSelected = actualIndex === this.fileAutocomplete.selectedIndex;
368
+ const prefix = isSelected ? s.primary('❯ ') : ' ';
369
+ const icon = file.isDirectory ? s.warning('📁 ') : s.info('📄 ');
370
+ const name = isSelected ? s.primary(chalk.bold(file.path)) : file.path;
371
+ lines.push(`${prefix}${icon}${name}`);
372
+ }
373
+ // Show scroll indicator if there are more items below
374
+ const remaining = total - offset - visible.length;
375
+ if (remaining > 0) {
376
+ lines.push(s.muted(` ↓ ${String(remaining)} more below`));
377
+ }
378
+ return lines;
379
+ }
380
+ // Command autocomplete
381
+ if (!this.autocomplete.active || this.autocomplete.matches.length === 0) {
382
+ return [];
383
+ }
384
+ const lines = [];
385
+ const offset = this.autocomplete.scrollOffset;
386
+ const visible = this.autocomplete.matches.slice(offset, offset + MAX_VISIBLE_COMMANDS);
387
+ const total = this.autocomplete.matches.length;
388
+ // Show scroll indicator if there are more items above
389
+ if (offset > 0) {
390
+ lines.push(s.muted(` ↑ ${String(offset)} more above`));
391
+ }
392
+ for (let i = 0; i < visible.length; i++) {
393
+ const cmd = visible[i];
394
+ const actualIndex = offset + i;
395
+ const isSelected = actualIndex === this.autocomplete.selectedIndex;
396
+ const prefix = isSelected ? s.primary('❯ ') : ' ';
397
+ const name = isSelected ? s.primary(chalk.bold(cmd.command)) : cmd.command;
398
+ const desc = s.muted(` - ${cmd.description}`);
399
+ lines.push(`${prefix}${name}${desc}`);
400
+ }
401
+ // Show scroll indicator if there are more items below
402
+ const remaining = total - offset - visible.length;
403
+ if (remaining > 0) {
404
+ lines.push(s.muted(` ↓ ${String(remaining)} more below`));
405
+ }
406
+ return lines;
407
+ }
408
+ /**
409
+ * Check if autocomplete is showing
410
+ */
411
+ isAutocompleteActive() {
412
+ return ((this.autocomplete.active && this.autocomplete.matches.length > 0) ||
413
+ (this.fileAutocomplete.active && this.fileAutocomplete.matches.length > 0));
414
+ }
415
+ /**
416
+ * Get height of rendered content
417
+ */
418
+ getHeight() {
419
+ let height = this.state.lines.length;
420
+ if (this.showSeparators)
421
+ height += 2;
422
+ return height;
423
+ }
424
+ // ===========================================================================
425
+ // Legacy API (for backwards compatibility during migration)
426
+ // ===========================================================================
427
+ /**
428
+ * @deprecated Use event-driven start() instead
429
+ * Kept for backwards compatibility during migration
430
+ */
431
+ async getInput() {
432
+ return new Promise((resolve) => {
433
+ const onSubmit = (input) => {
434
+ cleanup();
435
+ resolve({ action: 'submit', value: input });
436
+ };
437
+ const onCommand = (command, args) => {
438
+ cleanup();
439
+ resolve({ action: 'command', command, args });
440
+ };
441
+ const onCancel = () => {
442
+ cleanup();
443
+ resolve({ action: 'cancel' });
444
+ };
445
+ const cleanup = () => {
446
+ this.removeListener('submit', onSubmit);
447
+ this.removeListener('command', onCommand);
448
+ this.removeListener('cancel', onCancel);
449
+ };
450
+ this.on('submit', onSubmit);
451
+ this.on('command', onCommand);
452
+ this.on('cancel', onCancel);
453
+ if (!this.isRunning) {
454
+ this.start();
455
+ }
456
+ });
457
+ }
458
+ // ===========================================================================
459
+ // Private: State Management
460
+ // ===========================================================================
461
+ resetState() {
462
+ this.state = {
463
+ lines: [''],
464
+ currentLine: 0,
465
+ cursorPos: 0,
466
+ };
467
+ this.autocomplete = {
468
+ active: false,
469
+ matches: [],
470
+ selectedIndex: 0,
471
+ scrollOffset: 0,
472
+ };
473
+ this.fileAutocomplete = {
474
+ active: false,
475
+ matches: [],
476
+ selectedIndex: 0,
477
+ scrollOffset: 0,
478
+ partial: '',
479
+ };
480
+ this.historyIndex = -1;
481
+ this.savedInput = '';
482
+ this.renderedLines = 0;
483
+ this.dropdownLines = 0;
484
+ this.linesAboveCursor = 0;
485
+ this.hasSeparators = false;
486
+ }
487
+ updateAutocomplete() {
488
+ const fullInput = this.getValue();
489
+ const currentLine = this.state.lines[this.state.currentLine];
490
+ const cursorPos = this.state.cursorPos;
491
+ // Check for @ file path autocomplete first
492
+ const atMention = extractAtMention(currentLine, cursorPos);
493
+ if (atMention !== null) {
494
+ // File autocomplete mode
495
+ this.autocomplete.active = false;
496
+ this.autocomplete.matches = [];
497
+ this.autocomplete.selectedIndex = 0;
498
+ this.autocomplete.scrollOffset = 0;
499
+ this.fileAutocomplete.active = true;
500
+ this.fileAutocomplete.partial = atMention;
501
+ this.fileAutocomplete.matches = getFileMatches(atMention);
502
+ if (this.fileAutocomplete.selectedIndex >= this.fileAutocomplete.matches.length) {
503
+ this.fileAutocomplete.selectedIndex = Math.max(0, this.fileAutocomplete.matches.length - 1);
504
+ this.fileAutocomplete.scrollOffset = 0;
505
+ }
506
+ return;
507
+ }
508
+ // Reset file autocomplete
509
+ this.fileAutocomplete.active = false;
510
+ this.fileAutocomplete.matches = [];
511
+ this.fileAutocomplete.selectedIndex = 0;
512
+ this.fileAutocomplete.scrollOffset = 0;
513
+ this.fileAutocomplete.partial = '';
514
+ // Check for / command autocomplete
515
+ if (fullInput.startsWith('/') && this.state.lines.length === 1) {
516
+ this.autocomplete.active = true;
517
+ // Refresh commands dynamically to include newly created custom commands
518
+ const freshCommands = getAutocompleteCommands();
519
+ this.autocomplete.matches = filterCommands(fullInput, freshCommands);
520
+ if (this.autocomplete.selectedIndex >= this.autocomplete.matches.length) {
521
+ this.autocomplete.selectedIndex = Math.max(0, this.autocomplete.matches.length - 1);
522
+ this.autocomplete.scrollOffset = 0;
523
+ }
524
+ }
525
+ else {
526
+ this.autocomplete.active = false;
527
+ this.autocomplete.matches = [];
528
+ this.autocomplete.selectedIndex = 0;
529
+ this.autocomplete.scrollOffset = 0;
530
+ }
531
+ }
532
+ getSeparatorLine() {
533
+ const s = getStyles();
534
+ return s.muted('─'.repeat(terminal.getTerminalWidth()));
535
+ }
536
+ // ===========================================================================
537
+ // Private: Input Handling
538
+ // ===========================================================================
539
+ handleData = (data) => {
540
+ if (!this.isRunning)
541
+ return;
542
+ const key = data.toString();
543
+ this.handleKey(key, data);
544
+ };
545
+ handleKey(key, data) {
546
+ // Detect special keys
547
+ const isEscape = data.length === 1 && data[0] === 0x1b;
548
+ const isEnter = key === '\r' || key === '\n';
549
+ const isBackspace = key === '\x7f' || key === '\b';
550
+ const isTab = key === '\t';
551
+ const isShiftTab = data.length === 3 && data[0] === 0x1b && data[1] === 0x5b && data[2] === 0x5a;
552
+ const isCtrlC = key === '\x03';
553
+ const isUpArrow = data.length === 3 && data[0] === 0x1b && data[1] === 0x5b && data[2] === 0x41;
554
+ const isDownArrow = data.length === 3 && data[0] === 0x1b && data[1] === 0x5b && data[2] === 0x42;
555
+ const isLeftArrow = data.length === 3 && data[0] === 0x1b && data[1] === 0x5b && data[2] === 0x44;
556
+ const isRightArrow = data.length === 3 && data[0] === 0x1b && data[1] === 0x5b && data[2] === 0x43;
557
+ // Mac Option + Arrow (word navigation)
558
+ const isOptionLeft = (data.length === 6 &&
559
+ data[0] === 0x1b &&
560
+ data[1] === 0x5b &&
561
+ data[2] === 0x31 &&
562
+ data[3] === 0x3b &&
563
+ data[4] === 0x33 &&
564
+ data[5] === 0x44) ||
565
+ (data.length === 2 && data[0] === 0x1b && data[1] === 0x62);
566
+ const isOptionRight = (data.length === 6 &&
567
+ data[0] === 0x1b &&
568
+ data[1] === 0x5b &&
569
+ data[2] === 0x31 &&
570
+ data[3] === 0x3b &&
571
+ data[4] === 0x33 &&
572
+ data[5] === 0x43) ||
573
+ (data.length === 2 && data[0] === 0x1b && data[1] === 0x66);
574
+ // Home/End (Cmd+Left/Right on Mac, or Home/End keys)
575
+ const isHome = key === '\x01' || (data.length === 3 && data[0] === 0x1b && data[1] === 0x5b && data[2] === 0x48);
576
+ const isEnd = key === '\x05' || (data.length === 3 && data[0] === 0x1b && data[1] === 0x5b && data[2] === 0x46);
577
+ // Ctrl+C - cancel
578
+ if (isCtrlC) {
579
+ this.emit('cancel');
580
+ return;
581
+ }
582
+ // Escape - close autocomplete, double Esc clears input, or emit escape for agent abort
583
+ if (isEscape) {
584
+ const now = Date.now();
585
+ const isDoubleEsc = now - this.lastEscTime < this.DOUBLE_ESC_THRESHOLD_MS;
586
+ this.lastEscTime = now;
587
+ if (this.fileAutocomplete.active) {
588
+ // First priority: close file autocomplete
589
+ this.fileAutocomplete.active = false;
590
+ this.fileAutocomplete.matches = [];
591
+ this.emit('change', this.getValue());
592
+ }
593
+ else if (this.autocomplete.active) {
594
+ // Second priority: close command autocomplete
595
+ this.autocomplete.active = false;
596
+ this.autocomplete.matches = [];
597
+ this.emit('change', this.getValue());
598
+ }
599
+ else if (isDoubleEsc && this.getValue().length > 0) {
600
+ // Third priority: double Esc clears input (if there's content)
601
+ this.clearInput();
602
+ this.emit('change', this.getValue());
603
+ }
604
+ else {
605
+ // Fourth priority: emit escape for agent abort
606
+ this.emit('escape');
607
+ }
608
+ return;
609
+ }
610
+ // Enter
611
+ if (isEnter) {
612
+ this.handleEnter();
613
+ return;
614
+ }
615
+ // Shift+Tab - cycle modes
616
+ if (isShiftTab) {
617
+ this.emit('modeChange');
618
+ return;
619
+ }
620
+ // Tab - accept autocomplete or insert spaces
621
+ if (isTab) {
622
+ this.handleTab();
623
+ return;
624
+ }
625
+ // Arrow keys
626
+ if (isUpArrow) {
627
+ this.handleArrowUp();
628
+ return;
629
+ }
630
+ if (isDownArrow) {
631
+ this.handleArrowDown();
632
+ return;
633
+ }
634
+ if (isLeftArrow) {
635
+ this.handleArrowLeft();
636
+ return;
637
+ }
638
+ if (isRightArrow) {
639
+ this.handleArrowRight();
640
+ return;
641
+ }
642
+ // Word navigation
643
+ if (isOptionLeft) {
644
+ this.handleWordLeft();
645
+ return;
646
+ }
647
+ if (isOptionRight) {
648
+ this.handleWordRight();
649
+ return;
650
+ }
651
+ // Home/End
652
+ if (isHome) {
653
+ this.state.cursorPos = 0;
654
+ this.emit('change', this.getValue());
655
+ return;
656
+ }
657
+ if (isEnd) {
658
+ this.state.cursorPos = this.state.lines[this.state.currentLine].length;
659
+ this.emit('change', this.getValue());
660
+ return;
661
+ }
662
+ // Backspace
663
+ if (isBackspace) {
664
+ this.handleBackspace();
665
+ return;
666
+ }
667
+ // Regular character(s) - handles typing and paste
668
+ if (key.length >= 1) {
669
+ const printable = key
670
+ .split('')
671
+ .filter((c) => c.charCodeAt(0) >= 32)
672
+ .join('');
673
+ if (printable.length > 0) {
674
+ // Clear suggestion when user starts typing
675
+ if (this.suggestion) {
676
+ this.suggestion = null;
677
+ }
678
+ const line = this.state.lines[this.state.currentLine];
679
+ this.state.lines[this.state.currentLine] =
680
+ line.slice(0, this.state.cursorPos) + printable + line.slice(this.state.cursorPos);
681
+ this.state.cursorPos += printable.length;
682
+ this.historyIndex = -1;
683
+ this.updateAutocomplete();
684
+ this.emit('change', this.getValue());
685
+ }
686
+ }
687
+ }
688
+ handleEnter() {
689
+ const currentLine = this.state.lines[this.state.currentLine];
690
+ // Continuation with backslash
691
+ if (currentLine.endsWith('\\')) {
692
+ this.state.lines[this.state.currentLine] = currentLine.slice(0, -1);
693
+ this.state.lines.push('');
694
+ this.state.currentLine++;
695
+ this.state.cursorPos = 0;
696
+ this.autocomplete.active = false;
697
+ this.emit('change', this.getValue());
698
+ return;
699
+ }
700
+ // File autocomplete selection - accept the path and continue
701
+ if (this.fileAutocomplete.active && this.fileAutocomplete.matches.length > 0) {
702
+ const selectedFile = this.fileAutocomplete.matches[this.fileAutocomplete.selectedIndex];
703
+ const result = replaceAtMention(currentLine, this.state.cursorPos, selectedFile.path);
704
+ this.state.lines[this.state.currentLine] = result.input;
705
+ this.state.cursorPos = result.cursorPos;
706
+ this.fileAutocomplete.active = false;
707
+ this.fileAutocomplete.matches = [];
708
+ // Don't return - fall through to submit
709
+ }
710
+ // Command autocomplete selection - accept the command and continue to execute it
711
+ if (this.autocomplete.active && this.autocomplete.matches.length > 0) {
712
+ this.state.lines[0] = this.autocomplete.matches[this.autocomplete.selectedIndex].command;
713
+ this.state.cursorPos = this.state.lines[0].length;
714
+ this.autocomplete.active = false;
715
+ // Don't return - fall through to execute the command
716
+ }
717
+ const input = this.getValue();
718
+ // Empty input - ignore
719
+ if (!input.trim()) {
720
+ return;
721
+ }
722
+ // Add to history
723
+ this.addToHistory(input);
724
+ // Queue mode - add to queue instead of emitting
725
+ if (this.queueMode) {
726
+ this.queuedInputs.push(input);
727
+ this.clearInput();
728
+ this.emit('change', this.getValue());
729
+ return;
730
+ }
731
+ // Check if it's a command
732
+ if (input.startsWith('/')) {
733
+ const parts = input.slice(1).split(/\s+/);
734
+ const command = parts[0];
735
+ const args = parts.slice(1).join(' ');
736
+ this.clearInput();
737
+ this.emit('command', command, args);
738
+ }
739
+ else {
740
+ this.clearInput();
741
+ this.emit('submit', input);
742
+ }
743
+ }
744
+ handleTab() {
745
+ // Accept suggestion if present and input is empty
746
+ if (this.suggestion && this.getValue() === '') {
747
+ this.state.lines[0] = this.suggestion;
748
+ this.state.cursorPos = this.suggestion.length;
749
+ this.suggestion = null;
750
+ this.emit('change', this.getValue());
751
+ return;
752
+ }
753
+ // File autocomplete completion
754
+ if (this.fileAutocomplete.active && this.fileAutocomplete.matches.length > 0) {
755
+ const selectedFile = this.fileAutocomplete.matches[this.fileAutocomplete.selectedIndex];
756
+ const currentLine = this.state.lines[this.state.currentLine];
757
+ const result = replaceAtMention(currentLine, this.state.cursorPos, selectedFile.path);
758
+ this.state.lines[this.state.currentLine] = result.input;
759
+ this.state.cursorPos = result.cursorPos;
760
+ this.fileAutocomplete.active = false;
761
+ this.fileAutocomplete.matches = [];
762
+ this.updateAutocomplete(); // Check if still in @ context (e.g., directory selected)
763
+ this.emit('change', this.getValue());
764
+ return;
765
+ }
766
+ // Command autocomplete completion
767
+ if (this.autocomplete.active && this.autocomplete.matches.length > 0) {
768
+ this.state.lines[this.state.currentLine] =
769
+ this.autocomplete.matches[this.autocomplete.selectedIndex].command;
770
+ this.state.cursorPos = this.state.lines[this.state.currentLine].length;
771
+ this.autocomplete.active = false;
772
+ this.autocomplete.matches = [];
773
+ }
774
+ else {
775
+ // Insert 2 spaces
776
+ const line = this.state.lines[this.state.currentLine];
777
+ this.state.lines[this.state.currentLine] =
778
+ line.slice(0, this.state.cursorPos) + ' ' + line.slice(this.state.cursorPos);
779
+ this.state.cursorPos += 2;
780
+ this.historyIndex = -1;
781
+ this.updateAutocomplete();
782
+ }
783
+ this.emit('change', this.getValue());
784
+ }
785
+ handleArrowUp() {
786
+ // File autocomplete navigation
787
+ if (this.fileAutocomplete.active && this.fileAutocomplete.selectedIndex > 0) {
788
+ this.fileAutocomplete.selectedIndex--;
789
+ // Adjust scroll offset if selection goes above visible area
790
+ if (this.fileAutocomplete.selectedIndex < this.fileAutocomplete.scrollOffset) {
791
+ this.fileAutocomplete.scrollOffset = this.fileAutocomplete.selectedIndex;
792
+ }
793
+ this.emit('change', this.getValue());
794
+ return;
795
+ }
796
+ // Command autocomplete navigation
797
+ if (this.autocomplete.active && this.autocomplete.selectedIndex > 0) {
798
+ this.autocomplete.selectedIndex--;
799
+ // Adjust scroll offset if selection goes above visible area
800
+ if (this.autocomplete.selectedIndex < this.autocomplete.scrollOffset) {
801
+ this.autocomplete.scrollOffset = this.autocomplete.selectedIndex;
802
+ }
803
+ this.emit('change', this.getValue());
804
+ return;
805
+ }
806
+ const termWidth = terminal.getTerminalWidth();
807
+ const currentLinePromptLen = this.state.currentLine === 0 ? this.promptLen : 5;
808
+ const cursorAbsPos = currentLinePromptLen + this.state.cursorPos;
809
+ const currentPhysicalRow = Math.floor(cursorAbsPos / termWidth);
810
+ const cursorColInRow = cursorAbsPos % termWidth;
811
+ // Navigate within wrapped line
812
+ if (currentPhysicalRow > 0) {
813
+ const newAbsPos = (currentPhysicalRow - 1) * termWidth + cursorColInRow;
814
+ this.state.cursorPos = Math.max(0, newAbsPos - currentLinePromptLen);
815
+ this.state.cursorPos = Math.min(this.state.cursorPos, this.state.lines[this.state.currentLine].length);
816
+ this.emit('change', this.getValue());
817
+ return;
818
+ }
819
+ // Navigate to previous logical line
820
+ if (this.state.currentLine > 0) {
821
+ this.state.currentLine--;
822
+ const prevLinePromptLen = this.state.currentLine === 0 ? this.promptLen : 5;
823
+ const prevLineLen = this.state.lines[this.state.currentLine].length;
824
+ const prevLinePhysical = calcPhysicalLines(this.state.lines[this.state.currentLine], prevLinePromptLen, termWidth);
825
+ const lastRowStart = (prevLinePhysical - 1) * termWidth;
826
+ const targetAbsPos = lastRowStart + cursorColInRow;
827
+ this.state.cursorPos = Math.max(0, targetAbsPos - prevLinePromptLen);
828
+ this.state.cursorPos = Math.min(this.state.cursorPos, prevLineLen);
829
+ this.emit('change', this.getValue());
830
+ return;
831
+ }
832
+ // History navigation (at top of input)
833
+ if (!this.autocomplete.active && this.history.length > 0) {
834
+ if (this.historyIndex === -1) {
835
+ this.savedInput = this.getValue();
836
+ }
837
+ if (this.historyIndex < this.history.length - 1) {
838
+ this.historyIndex++;
839
+ const historyEntry = this.history[this.history.length - 1 - this.historyIndex];
840
+ this.state.lines = [historyEntry];
841
+ this.state.currentLine = 0;
842
+ this.state.cursorPos = historyEntry.length;
843
+ this.emit('change', this.getValue());
844
+ }
845
+ }
846
+ }
847
+ handleArrowDown() {
848
+ // File autocomplete navigation
849
+ if (this.fileAutocomplete.active &&
850
+ this.fileAutocomplete.selectedIndex < this.fileAutocomplete.matches.length - 1) {
851
+ this.fileAutocomplete.selectedIndex++;
852
+ // Adjust scroll offset if selection goes below visible area
853
+ const maxVisibleIndex = this.fileAutocomplete.scrollOffset + MAX_VISIBLE_COMMANDS - 1;
854
+ if (this.fileAutocomplete.selectedIndex > maxVisibleIndex) {
855
+ this.fileAutocomplete.scrollOffset = this.fileAutocomplete.selectedIndex - MAX_VISIBLE_COMMANDS + 1;
856
+ }
857
+ this.emit('change', this.getValue());
858
+ return;
859
+ }
860
+ // Command autocomplete navigation
861
+ if (this.autocomplete.active &&
862
+ this.autocomplete.selectedIndex < this.autocomplete.matches.length - 1) {
863
+ this.autocomplete.selectedIndex++;
864
+ // Adjust scroll offset if selection goes below visible area
865
+ const maxVisibleIndex = this.autocomplete.scrollOffset + MAX_VISIBLE_COMMANDS - 1;
866
+ if (this.autocomplete.selectedIndex > maxVisibleIndex) {
867
+ this.autocomplete.scrollOffset = this.autocomplete.selectedIndex - MAX_VISIBLE_COMMANDS + 1;
868
+ }
869
+ this.emit('change', this.getValue());
870
+ return;
871
+ }
872
+ const termWidth = terminal.getTerminalWidth();
873
+ const currentLinePromptLen = this.state.currentLine === 0 ? this.promptLen : 5;
874
+ const cursorAbsPos = currentLinePromptLen + this.state.cursorPos;
875
+ const currentPhysicalRow = Math.floor(cursorAbsPos / termWidth);
876
+ const cursorColInRow = cursorAbsPos % termWidth;
877
+ const currentLinePhysical = calcPhysicalLines(this.state.lines[this.state.currentLine], currentLinePromptLen, termWidth);
878
+ // Navigate within wrapped line
879
+ if (currentPhysicalRow < currentLinePhysical - 1) {
880
+ const newAbsPos = (currentPhysicalRow + 1) * termWidth + cursorColInRow;
881
+ this.state.cursorPos = Math.max(0, newAbsPos - currentLinePromptLen);
882
+ this.state.cursorPos = Math.min(this.state.cursorPos, this.state.lines[this.state.currentLine].length);
883
+ this.emit('change', this.getValue());
884
+ return;
885
+ }
886
+ // Navigate to next logical line
887
+ if (this.state.currentLine < this.state.lines.length - 1) {
888
+ this.state.currentLine++;
889
+ const nextLinePromptLen = this.state.currentLine === 0 ? this.promptLen : 5;
890
+ const nextLineLen = this.state.lines[this.state.currentLine].length;
891
+ this.state.cursorPos = Math.max(0, cursorColInRow - nextLinePromptLen);
892
+ this.state.cursorPos = Math.min(this.state.cursorPos, nextLineLen);
893
+ this.emit('change', this.getValue());
894
+ return;
895
+ }
896
+ // History forward
897
+ if (this.historyIndex >= 0) {
898
+ this.historyIndex--;
899
+ if (this.historyIndex === -1) {
900
+ this.state.lines = this.savedInput.split('\n');
901
+ this.state.currentLine = this.state.lines.length - 1;
902
+ this.state.cursorPos = this.state.lines[this.state.currentLine].length;
903
+ }
904
+ else {
905
+ const historyEntry = this.history[this.history.length - 1 - this.historyIndex];
906
+ this.state.lines = [historyEntry];
907
+ this.state.currentLine = 0;
908
+ this.state.cursorPos = historyEntry.length;
909
+ }
910
+ this.updateAutocomplete();
911
+ this.emit('change', this.getValue());
912
+ }
913
+ }
914
+ handleArrowLeft() {
915
+ if (this.state.cursorPos > 0) {
916
+ this.state.cursorPos--;
917
+ this.emit('change', this.getValue());
918
+ }
919
+ else if (this.state.currentLine > 0) {
920
+ this.state.currentLine--;
921
+ this.state.cursorPos = this.state.lines[this.state.currentLine].length;
922
+ this.emit('change', this.getValue());
923
+ }
924
+ }
925
+ handleArrowRight() {
926
+ if (this.state.cursorPos < this.state.lines[this.state.currentLine].length) {
927
+ this.state.cursorPos++;
928
+ this.emit('change', this.getValue());
929
+ }
930
+ else if (this.state.currentLine < this.state.lines.length - 1) {
931
+ this.state.currentLine++;
932
+ this.state.cursorPos = 0;
933
+ this.emit('change', this.getValue());
934
+ }
935
+ }
936
+ handleWordLeft() {
937
+ const line = this.state.lines[this.state.currentLine];
938
+ if (this.state.cursorPos > 0) {
939
+ let pos = this.state.cursorPos;
940
+ while (pos > 0 && line[pos - 1] === ' ')
941
+ pos--;
942
+ while (pos > 0 && line[pos - 1] !== ' ')
943
+ pos--;
944
+ this.state.cursorPos = pos;
945
+ this.emit('change', this.getValue());
946
+ }
947
+ else if (this.state.currentLine > 0) {
948
+ this.state.currentLine--;
949
+ this.state.cursorPos = this.state.lines[this.state.currentLine].length;
950
+ this.emit('change', this.getValue());
951
+ }
952
+ }
953
+ handleWordRight() {
954
+ const line = this.state.lines[this.state.currentLine];
955
+ if (this.state.cursorPos < line.length) {
956
+ let pos = this.state.cursorPos;
957
+ while (pos < line.length && line[pos] !== ' ')
958
+ pos++;
959
+ while (pos < line.length && line[pos] === ' ')
960
+ pos++;
961
+ this.state.cursorPos = pos;
962
+ this.emit('change', this.getValue());
963
+ }
964
+ else if (this.state.currentLine < this.state.lines.length - 1) {
965
+ this.state.currentLine++;
966
+ this.state.cursorPos = 0;
967
+ this.emit('change', this.getValue());
968
+ }
969
+ }
970
+ handleBackspace() {
971
+ this.historyIndex = -1;
972
+ if (this.state.cursorPos > 0) {
973
+ const line = this.state.lines[this.state.currentLine];
974
+ this.state.lines[this.state.currentLine] =
975
+ line.slice(0, this.state.cursorPos - 1) + line.slice(this.state.cursorPos);
976
+ this.state.cursorPos--;
977
+ this.updateAutocomplete();
978
+ this.emit('change', this.getValue());
979
+ }
980
+ else if (this.state.currentLine > 0) {
981
+ const currentLine = this.state.lines[this.state.currentLine];
982
+ const prevLine = this.state.lines[this.state.currentLine - 1];
983
+ this.state.lines[this.state.currentLine - 1] = prevLine + currentLine;
984
+ this.state.lines.splice(this.state.currentLine, 1);
985
+ this.state.currentLine--;
986
+ this.state.cursorPos = prevLine.length;
987
+ this.updateAutocomplete();
988
+ this.emit('change', this.getValue());
989
+ }
990
+ }
991
+ }