UNPKG

capsule-ai-cli

Version:

The AI Model Orchestrator - Intelligent multi-model workflows with device-locked licensing

409 lines 12.8 kB
import { EventEmitter } from 'events'; export class InputHandler extends EventEmitter { inputBuffer = ''; multiLineBuffer = []; multiLineMode = false; escapeBuffer = ''; pasteBuffer = ''; inBracketedPaste = false; isPasting = false; pastedTexts = []; commandHistory = []; historyIndex = -1; tempInput = ''; constructor() { super(); } getInputBuffer() { return this.inputBuffer; } setInputBuffer(text) { this.inputBuffer = text; this.emit('input-changed', this.inputBuffer); } clearInput() { this.inputBuffer = ''; this.emit('input-changed', this.inputBuffer); } getMultiLineBuffer() { return this.multiLineBuffer; } isMultiLineMode() { return this.multiLineMode; } isPastingMode() { return this.isPasting; } processInput(data) { const key = typeof data === 'string' ? data : data.toString('utf8'); if (this.escapeBuffer.length > 0) { const combined = this.escapeBuffer + key; this.escapeBuffer = ''; this.handleKey(combined); return; } if (key.startsWith('\x1b[') && !/[a-zA-Z~]$/.test(key)) { this.escapeBuffer = key; return; } this.handleKey(key); } handleKey(key) { if (key.includes('\x1b[200~')) { this.handlePasteStart(key); return; } else if (key.includes('\x1b[201~')) { this.handlePasteEnd(key); return; } if (this.inBracketedPaste) { this.pasteBuffer += key; return; } const keyEvent = this.parseKey(key); this.emit('key', keyEvent); switch (keyEvent.name) { case 'return': case 'enter': this.handleEnter(); break; case 'shift-enter': this.handleShiftEnter(); break; case 'backspace': this.handleBackspace(); break; case 'tab': this.emit('tab'); break; case 'escape': this.handleEscape(); break; case 'up': this.emit('up-arrow'); break; case 'down': this.emit('down-arrow'); break; case 'left': this.emit('cursor-left'); break; case 'right': this.emit('cursor-right'); break; case 'pageup': this.emit('page-up'); break; case 'pagedown': this.emit('page-down'); break; default: if (key >= ' ' && key <= '~') { this.handleCharacter(key); } } } parseKey(key) { const event = { key, ctrl: false, shift: false, meta: false }; if (key === '\u0003') { event.name = 'ctrl-c'; event.ctrl = true; } else if (key === '\r' || key === '\n') { event.name = 'return'; } else if (key === '\x7f') { event.name = 'backspace'; } else if (key === '\t') { event.name = 'tab'; } else if (key === '\x1b[Z') { event.name = 'shift-tab'; event.shift = true; } else if (key === '\x1b') { event.name = 'escape'; } else if (key === '\x1b[A') { event.name = 'up'; } else if (key === '\x1b[B') { event.name = 'down'; } else if (key === '\x1b[C') { event.name = 'right'; } else if (key === '\x1b[D') { event.name = 'left'; } else if (key === '\x1b[5~') { event.name = 'pageup'; } else if (key === '\x1b[6~') { event.name = 'pagedown'; } else if (key === '\x1b[1;5A') { event.name = 'ctrl-up'; event.ctrl = true; } else if (key === '\x1b[1;5B') { event.name = 'ctrl-down'; event.ctrl = true; } else if (key === '\x0012') { event.name = 'ctrl-r'; event.ctrl = true; } else if (key === '\x1b\r' || key === '\x1b\n') { event.name = 'alt-enter'; event.meta = true; } else if (key === '\x1b[1;2A') { event.name = 'shift-up'; event.shift = true; } else if (key === '\x1b[1;2B') { event.name = 'shift-down'; event.shift = true; } else if (key === '\x1b[1;2C') { event.name = 'shift-right'; event.shift = true; } else if (key === '\x1b[1;2D') { event.name = 'shift-left'; event.shift = true; } else if (key === '\x1b[13;2u' || key === '\x1b[1;2~' || key === '\x1bOM') { event.name = 'shift-enter'; event.shift = true; } return event; } handleCharacter(char) { this.inputBuffer += char; this.emit('input-changed', this.inputBuffer); } handleBackspace() { if (this.inputBuffer.length > 0) { this.inputBuffer = this.inputBuffer.slice(0, -1); this.emit('input-changed', this.inputBuffer); } else if (this.multiLineMode && this.multiLineBuffer.length > 0) { const previousLine = this.multiLineBuffer.pop() || ''; this.inputBuffer = previousLine; if (this.multiLineBuffer.length === 0 && this.inputBuffer === '') { this.multiLineMode = false; this.emit('multi-line-end'); } this.emit('input-changed', this.inputBuffer); } } handleEnter() { if (this.multiLineMode) { if (this.inputBuffer === '' && this.multiLineBuffer.length > 0) { const fullText = this.multiLineBuffer.join('\n').trim(); this.multiLineMode = false; this.multiLineBuffer = []; this.inputBuffer = ''; if (fullText) { this.addToHistory(fullText); this.emit('multi-line-end'); this.emit('submit', fullText); } } else { this.multiLineBuffer.push(this.inputBuffer); this.inputBuffer = ''; this.emit('input-changed', this.inputBuffer); } } else { const text = this.inputBuffer.trim(); this.inputBuffer = ''; if (text) { this.addToHistory(text); this.emit('submit', text); } this.emit('input-changed', this.inputBuffer); } } handleShiftEnter() { if (!this.multiLineMode) { this.multiLineMode = true; this.emit('multi-line-start'); } this.multiLineBuffer.push(this.inputBuffer); this.inputBuffer = ''; this.emit('input-changed', this.inputBuffer); } handleEscape() { if (this.inputBuffer.length > 0 || this.multiLineBuffer.length > 0) { const wasMultiLine = this.multiLineMode; this.inputBuffer = ''; this.multiLineBuffer = []; this.multiLineMode = false; this.emit('input-changed', this.inputBuffer); if (wasMultiLine) { this.emit('multi-line-end'); } this.emit('escape-clear'); } else { this.emit('escape-pressed'); } } handlePasteStart(key) { const idx = key.indexOf('\x1b[200~'); if (idx > 0) { this.inputBuffer += key.substring(0, idx); } this.inBracketedPaste = true; this.isPasting = true; this.pasteBuffer = ''; const afterIdx = idx + 6; if (afterIdx < key.length) { this.pasteBuffer = key.substring(afterIdx); } this.emit('paste-start'); } handlePasteEnd(key) { const idx = key.indexOf('\x1b[201~'); if (idx > 0) { this.pasteBuffer += key.substring(0, idx); } this.inBracketedPaste = false; this.isPasting = false; if (this.pasteBuffer.length > 0) { this.processPastedText(this.pasteBuffer); } this.pasteBuffer = ''; this.emit('paste-end'); } processPastedText(text) { const normalized = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n'); const lines = normalized.split('\n'); let lineCount = lines.length; if (normalized.endsWith('\n') && lines[lines.length - 1] === '') { lineCount--; } const actualLines = lineCount < lines.length ? lines.slice(0, -1) : lines; if (lineCount > 5 || text.length > 1000) { this.pastedTexts.push(text); const pasteRef = `[Pasted text #${this.pastedTexts.length} +${lineCount} lines]`; this.inputBuffer = pasteRef; this.emit('paste', { text: pasteRef, lineCount, isMultiLine: true }); } else if (lineCount > 1) { if (!this.multiLineMode) { this.multiLineMode = true; this.emit('multi-line-start'); } this.inputBuffer += actualLines[0]; for (let i = 1; i < actualLines.length; i++) { this.multiLineBuffer.push(this.inputBuffer); this.inputBuffer = actualLines[i]; } this.emit('paste', { text, lineCount, isMultiLine: true }); } else { this.inputBuffer += text; this.emit('paste', { text, lineCount: 1, isMultiLine: false }); } this.emit('input-changed', this.inputBuffer); } handleHistoryUp() { if (this.commandHistory.length === 0) { this.emit('history-up-empty'); return; } if (this.historyIndex === -1) { this.tempInput = this.inputBuffer; this.historyIndex = this.commandHistory.length - 1; } else if (this.historyIndex > 0) { this.historyIndex--; } this.inputBuffer = this.commandHistory[this.historyIndex]; this.emit('input-changed', this.inputBuffer); } handleHistoryDown() { if (this.historyIndex === -1) { return; } if (this.historyIndex < this.commandHistory.length - 1) { this.historyIndex++; this.inputBuffer = this.commandHistory[this.historyIndex]; } else { this.historyIndex = -1; this.inputBuffer = this.tempInput; } this.emit('input-changed', this.inputBuffer); } addToHistory(command) { const lastCommand = this.commandHistory[this.commandHistory.length - 1]; if (lastCommand !== command) { this.commandHistory.push(command); if (this.commandHistory.length > 1000) { this.commandHistory.shift(); } } this.historyIndex = -1; this.tempInput = ''; } getPastedText(index) { if (index > 0 && index <= this.pastedTexts.length) { return this.pastedTexts[index - 1]; } return null; } enableMultiLineMode() { this.multiLineMode = true; this.emit('multi-line-start'); } reset() { this.inputBuffer = ''; this.multiLineBuffer = []; this.multiLineMode = false; this.escapeBuffer = ''; this.pasteBuffer = ''; this.inBracketedPaste = false; this.isPasting = false; this.historyIndex = -1; this.tempInput = ''; } setHistory(history) { this.commandHistory = [...history]; this.historyIndex = -1; } getState() { return { input: this.inputBuffer, multiLineBuffer: [...this.multiLineBuffer], isMultiLine: this.multiLineMode, isPasting: this.isPasting }; } } export const inputHandler = new InputHandler(); //# sourceMappingURL=input-handler.js.map