import { createWrappedKvmCommand, parseWrappedKvmCommandOutput } from './smartkvm.commandwrappers.js'; import type { IKvmTerminalCommandResult, IKvmTerminalOptions, } from './smartkvm.interfaces.js'; export class SmartKvmTerminal { private options: IKvmTerminalOptions; constructor(options: IKvmTerminalOptions) { this.options = options; } public async bootstrap(): Promise { const osHint = this.options.osHint ?? 'unknown'; switch (osHint) { case 'windows': await this.options.kvm.pressShortcut(['Meta', 'R']); await this.options.kvm.wait(500); await this.options.kvm.typeText('powershell -NoLogo'); await this.options.kvm.pressKey('Enter'); await this.options.kvm.wait(1500); break; case 'macos': await this.options.kvm.pressShortcut(['Meta', 'Space']); await this.options.kvm.wait(500); await this.options.kvm.typeText('Terminal'); await this.options.kvm.pressKey('Enter'); await this.options.kvm.wait(1500); break; case 'linux': await this.options.kvm.pressShortcut(['Control', 'Alt', 'T']); await this.options.kvm.wait(1500); break; case 'unknown': default: break; } } public async runCommand(command: string): Promise { const wrappedCommand = createWrappedKvmCommand(command, this.options.shellHint ?? 'unknown'); const commandTimeoutMs = this.options.commandTimeoutMs ?? 30000; const ocrPollIntervalMs = this.options.ocrPollIntervalMs ?? 500; const startedAt = Date.now(); let rawOcrText = ''; let attempts = 0; await this.options.kvm.focusViewer(); await this.options.kvm.typeText(wrappedCommand.textToType); await this.options.kvm.pressKey('Enter'); while (true) { attempts++; rawOcrText = await this.observeText(); const parsedResult = parseWrappedKvmCommandOutput({ commandId: wrappedCommand.commandId, startMarker: wrappedCommand.startMarker, endMarkerPrefix: wrappedCommand.endMarkerPrefix, rawText: rawOcrText, }); if (parsedResult.completed) { return { commandId: wrappedCommand.commandId, command, completed: true, timedOut: false, exitCode: parsedResult.exitCode, combinedText: parsedResult.combinedText, rawOcrText, }; } const reachedMaxAttempts = typeof this.options.ocrMaxAttempts === 'number' && attempts >= this.options.ocrMaxAttempts; const reachedTimeout = Date.now() - startedAt >= commandTimeoutMs; if (reachedMaxAttempts || reachedTimeout) { return { commandId: wrappedCommand.commandId, command, completed: false, timedOut: true, combinedText: rawOcrText, rawOcrText, }; } const remainingTimeMs = commandTimeoutMs - (Date.now() - startedAt); await this.options.kvm.wait(Math.min(ocrPollIntervalMs, Math.max(remainingTimeMs, 0))); } } public async observeText(): Promise { const frame = await this.options.kvm.captureFrame(); const result = await this.options.ocrEngine.recognize(frame, { crop: this.options.ocrCrop, language: 'eng', }); return result.text; } }