capsule-ai-cli
Version:
The AI Model Orchestrator - Intelligent multi-model workflows with device-locked licensing
409 lines • 12.8 kB
JavaScript
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