104 lines
3.3 KiB
TypeScript
104 lines
3.3 KiB
TypeScript
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<void> {
|
|
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<IKvmTerminalCommandResult> {
|
|
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<string> {
|
|
const frame = await this.options.kvm.captureFrame();
|
|
const result = await this.options.ocrEngine.recognize(frame, {
|
|
crop: this.options.ocrCrop,
|
|
language: 'eng',
|
|
});
|
|
return result.text;
|
|
}
|
|
}
|