claude-kvm-native 1.0.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.
- package/LICENSE +21 -0
- package/README.md +244 -0
- package/index.js +441 -0
- package/lib/capture.js +204 -0
- package/lib/hid.js +248 -0
- package/lib/ssh.js +162 -0
- package/lib/types.js +138 -0
- package/lib/vnc.js +1332 -0
- package/package.json +51 -0
- package/tools/control.js +55 -0
- package/tools/index.js +48 -0
- package/tools/keyboard.js +56 -0
- package/tools/mouse.js +61 -0
- package/tools/screen.js +67 -0
- package/tools/ssh.js +62 -0
- package/tools/vlm.js +59 -0
- package/utils/keysym.js +158 -0
package/lib/hid.js
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
/**
|
|
3
|
+
* █████╗ ██████╗ █████╗ ███████╗
|
|
4
|
+
* ██╔══██╗██╔══██╗██╔══██╗██╔════╝
|
|
5
|
+
* ███████║██████╔╝███████║███████╗
|
|
6
|
+
* ██╔══██║██╔══██╗██╔══██║╚════██║
|
|
7
|
+
* ██║ ██║██║ ██║██║ ██║███████║
|
|
8
|
+
* ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝
|
|
9
|
+
*
|
|
10
|
+
* Copyright (c) 2025 Rıza Emre ARAS <r.emrearas@proton.me>
|
|
11
|
+
*
|
|
12
|
+
* This file is part of Claude KVM.
|
|
13
|
+
* Released under the MIT License — see LICENSE for details.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { charToKeysym, namedKeyToKeysym } from '../utils/keysym.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* HID Controller — KVM-style input via VNC.
|
|
20
|
+
*
|
|
21
|
+
* Tracks cursor position. Uses direct teleport for move.
|
|
22
|
+
* Smooth interpolation only for drag operations.
|
|
23
|
+
* Click operations use the current tracked position.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
// VNC button masks
|
|
27
|
+
const BUTTON = {
|
|
28
|
+
LEFT: 1,
|
|
29
|
+
MIDDLE: 2,
|
|
30
|
+
RIGHT: 4,
|
|
31
|
+
SCROLL_UP: 8,
|
|
32
|
+
SCROLL_DOWN: 16,
|
|
33
|
+
SCROLL_LEFT: 32,
|
|
34
|
+
SCROLL_RIGHT: 64,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export class HIDController {
|
|
38
|
+
/**
|
|
39
|
+
* @param {import('./types.js').ClaudeKVMConfig} config
|
|
40
|
+
* @param {import('./vnc.js').VNCClient} vncClient
|
|
41
|
+
*/
|
|
42
|
+
constructor(config, vncClient) {
|
|
43
|
+
this.vnc = vncClient;
|
|
44
|
+
this.clickHoldMs = config.hid.click_hold_ms;
|
|
45
|
+
this.keyHoldMs = config.hid.key_hold_ms;
|
|
46
|
+
this.typingDelay = config.hid.typing_delay_ms;
|
|
47
|
+
this.scrollEventsPerStep = config.hid.scroll_events_per_step ?? 5;
|
|
48
|
+
|
|
49
|
+
/** @type {number} Current cursor X (native resolution) */
|
|
50
|
+
this.cursorX = 0;
|
|
51
|
+
/** @type {number} Current cursor Y (native resolution) */
|
|
52
|
+
this.cursorY = 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ── Cursor Position ──────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Get current cursor position in native coordinates.
|
|
59
|
+
* @returns {import('./types.js').CursorPosition}
|
|
60
|
+
*/
|
|
61
|
+
getCursorPosition() {
|
|
62
|
+
return { x: this.cursorX, y: this.cursorY };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ── Mouse: Move ──────────────────────────────────────────
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Teleport cursor to target position (single pointer event).
|
|
69
|
+
* @param {number} x - Target X (native resolution)
|
|
70
|
+
* @param {number} y - Target Y (native resolution)
|
|
71
|
+
*/
|
|
72
|
+
async mouseMove(x, y) {
|
|
73
|
+
this.vnc.pointerEvent(x, y, 0);
|
|
74
|
+
this.cursorX = x;
|
|
75
|
+
this.cursorY = y;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ── Mouse: Click (at current position) ───────────────────
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Click at current cursor position.
|
|
82
|
+
* @param {'left' | 'middle' | 'right'} [button='left']
|
|
83
|
+
*/
|
|
84
|
+
async mouseClick(button = 'left') {
|
|
85
|
+
const mask = button === 'right' ? BUTTON.RIGHT :
|
|
86
|
+
button === 'middle' ? BUTTON.MIDDLE : BUTTON.LEFT;
|
|
87
|
+
|
|
88
|
+
this.vnc.pointerEvent(this.cursorX, this.cursorY, mask);
|
|
89
|
+
await sleep(this.clickHoldMs);
|
|
90
|
+
this.vnc.pointerEvent(this.cursorX, this.cursorY, 0);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Double-click at current cursor position. */
|
|
94
|
+
async mouseDoubleClick() {
|
|
95
|
+
await this.mouseClick('left');
|
|
96
|
+
await sleep(50);
|
|
97
|
+
await this.mouseClick('left');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ── Mouse: Drag ──────────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Drag from current position to target (smooth).
|
|
104
|
+
* @param {number} endX - Target X (native resolution)
|
|
105
|
+
* @param {number} endY - Target Y (native resolution)
|
|
106
|
+
*/
|
|
107
|
+
async mouseDrag(endX, endY) {
|
|
108
|
+
// Press at current position
|
|
109
|
+
this.vnc.pointerEvent(this.cursorX, this.cursorY, BUTTON.LEFT);
|
|
110
|
+
await sleep(100);
|
|
111
|
+
|
|
112
|
+
// Smooth drag to target
|
|
113
|
+
const steps = Math.max(5, Math.ceil(
|
|
114
|
+
Math.hypot(endX - this.cursorX, endY - this.cursorY) / 30
|
|
115
|
+
));
|
|
116
|
+
const startX = this.cursorX;
|
|
117
|
+
const startY = this.cursorY;
|
|
118
|
+
|
|
119
|
+
for (let i = 1; i <= steps; i++) {
|
|
120
|
+
const t = i / steps;
|
|
121
|
+
const ix = Math.round(startX + (endX - startX) * t);
|
|
122
|
+
const iy = Math.round(startY + (endY - startY) * t);
|
|
123
|
+
this.vnc.pointerEvent(ix, iy, BUTTON.LEFT);
|
|
124
|
+
await sleep(12);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Release
|
|
128
|
+
this.vnc.pointerEvent(endX, endY, 0);
|
|
129
|
+
this.cursorX = endX;
|
|
130
|
+
this.cursorY = endY;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ── Mouse: Scroll ────────────────────────────────────────
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Scroll at current cursor position.
|
|
137
|
+
* @param {'up' | 'down' | 'left' | 'right'} direction
|
|
138
|
+
* @param {number} [amount=3]
|
|
139
|
+
*/
|
|
140
|
+
async scroll(direction, amount = 3) {
|
|
141
|
+
const buttonMask = direction === 'up' ? BUTTON.SCROLL_UP :
|
|
142
|
+
direction === 'down' ? BUTTON.SCROLL_DOWN :
|
|
143
|
+
direction === 'left' ? BUTTON.SCROLL_LEFT :
|
|
144
|
+
BUTTON.SCROLL_RIGHT;
|
|
145
|
+
|
|
146
|
+
for (let i = 0; i < amount; i++) {
|
|
147
|
+
for (let j = 0; j < this.scrollEventsPerStep; j++) {
|
|
148
|
+
this.vnc.pointerEvent(this.cursorX, this.cursorY, buttonMask);
|
|
149
|
+
await sleep(10);
|
|
150
|
+
this.vnc.pointerEvent(this.cursorX, this.cursorY, 0);
|
|
151
|
+
await sleep(10);
|
|
152
|
+
}
|
|
153
|
+
await sleep(30);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ── Keyboard ─────────────────────────────────────────────
|
|
158
|
+
|
|
159
|
+
/** @param {string} key */
|
|
160
|
+
async keyPress(key) {
|
|
161
|
+
const keysym = namedKeyToKeysym(key);
|
|
162
|
+
if (!keysym) {
|
|
163
|
+
console.warn(`HID: unknown key "${key}"`);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
this.vnc.keyEvent(keysym, true);
|
|
167
|
+
await sleep(this.keyHoldMs);
|
|
168
|
+
this.vnc.keyEvent(keysym, false);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** @param {string} combo - Keys separated by '+' */
|
|
172
|
+
async keyCombo(combo) {
|
|
173
|
+
const keys = combo.split('+').map(k => k.trim().toLowerCase());
|
|
174
|
+
/** @type {number[]} */
|
|
175
|
+
const keysyms = [];
|
|
176
|
+
|
|
177
|
+
for (const k of keys) {
|
|
178
|
+
const ks = namedKeyToKeysym(k);
|
|
179
|
+
if (ks) {
|
|
180
|
+
keysyms.push(ks);
|
|
181
|
+
} else {
|
|
182
|
+
const charKs = charToKeysym(k);
|
|
183
|
+
if (charKs) keysyms.push(charKs.keysym);
|
|
184
|
+
else { console.warn(`HID: unknown key in combo "${k}"`); return; }
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
for (const ks of keysyms) {
|
|
189
|
+
this.vnc.keyEvent(ks, true);
|
|
190
|
+
await sleep(50);
|
|
191
|
+
}
|
|
192
|
+
await sleep(80);
|
|
193
|
+
for (let i = keysyms.length - 1; i >= 0; i--) {
|
|
194
|
+
this.vnc.keyEvent(keysyms[i], false);
|
|
195
|
+
await sleep(50);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** @param {string} text */
|
|
200
|
+
async typeText(text) {
|
|
201
|
+
for (const ch of text) {
|
|
202
|
+
const mapping = charToKeysym(ch);
|
|
203
|
+
if (!mapping) {
|
|
204
|
+
console.warn(`HID: unmapped char '${ch}' (${ch.charCodeAt(0)})`);
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const { keysym, shift } = mapping;
|
|
209
|
+
const delay = this.typingDelay.min +
|
|
210
|
+
Math.random() * (this.typingDelay.max - this.typingDelay.min);
|
|
211
|
+
|
|
212
|
+
if (shift) {
|
|
213
|
+
this.vnc.keyEvent(0xFFE1, true);
|
|
214
|
+
await sleep(20);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
this.vnc.keyEvent(keysym, true);
|
|
218
|
+
await sleep(this.keyHoldMs);
|
|
219
|
+
this.vnc.keyEvent(keysym, false);
|
|
220
|
+
|
|
221
|
+
if (shift) {
|
|
222
|
+
await sleep(20);
|
|
223
|
+
this.vnc.keyEvent(0xFFE1, false);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
await sleep(delay);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Paste text via clipboard, or typeText fallback on macOS.
|
|
232
|
+
* Apple VNC doesn't bridge ClientCutText to the system pasteboard.
|
|
233
|
+
* @param {string} text
|
|
234
|
+
*/
|
|
235
|
+
async pasteText(text) {
|
|
236
|
+
if (this.vnc.isMacOS) {
|
|
237
|
+
await this.typeText(text);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
this.vnc.setClipboard(text);
|
|
241
|
+
await sleep(100);
|
|
242
|
+
await this.keyCombo('ctrl+v');
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function sleep(ms) {
|
|
247
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
248
|
+
}
|
package/lib/ssh.js
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
/**
|
|
3
|
+
* █████╗ ██████╗ █████╗ ███████╗
|
|
4
|
+
* ██╔══██╗██╔══██╗██╔══██╗██╔════╝
|
|
5
|
+
* ███████║██████╔╝███████║███████╗
|
|
6
|
+
* ██╔══██║██╔══██╗██╔══██║╚════██║
|
|
7
|
+
* ██║ ██║██║ ██║██║ ██║███████║
|
|
8
|
+
* ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝
|
|
9
|
+
*
|
|
10
|
+
* Copyright (c) 2025 Rıza Emre ARAS <r.emrearas@proton.me>
|
|
11
|
+
*
|
|
12
|
+
* This file is part of Claude KVM.
|
|
13
|
+
* Released under the MIT License — see LICENSE for details.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { Client } from 'ssh2';
|
|
17
|
+
import { readFileSync } from 'node:fs';
|
|
18
|
+
|
|
19
|
+
function dbg(label, ...args) {
|
|
20
|
+
const ts = new Date().toISOString().slice(11, 23);
|
|
21
|
+
console.error(`[SSH ${ts}] ${label}`, ...args);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class SSHClient {
|
|
25
|
+
/**
|
|
26
|
+
* @param {import('./types.js').SSHConnectionConfig} config
|
|
27
|
+
*/
|
|
28
|
+
constructor(config) {
|
|
29
|
+
this.host = config.host;
|
|
30
|
+
this.port = config.port || 22;
|
|
31
|
+
this.username = config.username;
|
|
32
|
+
this.password = config.password || null;
|
|
33
|
+
this.privateKeyPath = config.privateKeyPath || null;
|
|
34
|
+
|
|
35
|
+
/** @type {import('ssh2').Client | null} */
|
|
36
|
+
this._client = null;
|
|
37
|
+
/** @type {boolean} */
|
|
38
|
+
this.connected = false;
|
|
39
|
+
/** @type {boolean} */
|
|
40
|
+
this.connecting = false;
|
|
41
|
+
/** @type {number} */
|
|
42
|
+
this.commandCount = 0;
|
|
43
|
+
|
|
44
|
+
dbg('INIT', `target=${this.host}:${this.port} user=${this.username} auth=${this.password ? 'password' : this.privateKeyPath ? 'key' : 'none'}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Connect to the SSH server. Resolves when ready.
|
|
49
|
+
* @returns {Promise<void>}
|
|
50
|
+
*/
|
|
51
|
+
connect() {
|
|
52
|
+
if (this.connected) return Promise.resolve();
|
|
53
|
+
if (this.connecting) return this._connectPromise;
|
|
54
|
+
|
|
55
|
+
this.connecting = true;
|
|
56
|
+
this._connectPromise = new Promise((resolve, reject) => {
|
|
57
|
+
this._client = new Client();
|
|
58
|
+
|
|
59
|
+
const connectConfig = {
|
|
60
|
+
host: this.host,
|
|
61
|
+
port: this.port,
|
|
62
|
+
username: this.username,
|
|
63
|
+
readyTimeout: 10000,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
if (this.privateKeyPath) {
|
|
67
|
+
try {
|
|
68
|
+
connectConfig.privateKey = readFileSync(this.privateKeyPath);
|
|
69
|
+
dbg('AUTH', `using key: ${this.privateKeyPath}`);
|
|
70
|
+
} catch (err) {
|
|
71
|
+
this.connecting = false;
|
|
72
|
+
reject(new Error(`Failed to read SSH key: ${err.message}`));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
} else if (this.password) {
|
|
76
|
+
connectConfig.password = this.password;
|
|
77
|
+
dbg('AUTH', 'using password');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
this._client.on('ready', () => {
|
|
81
|
+
dbg('READY', `connected to ${this.host}:${this.port}`);
|
|
82
|
+
this.connected = true;
|
|
83
|
+
this.connecting = false;
|
|
84
|
+
resolve();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
this._client.on('error', (err) => {
|
|
88
|
+
dbg('ERROR', err.message);
|
|
89
|
+
if (this.connecting) {
|
|
90
|
+
this.connecting = false;
|
|
91
|
+
reject(new Error(`SSH connection failed: ${err.message}`));
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
this._client.on('close', () => {
|
|
96
|
+
dbg('CLOSE', 'connection closed');
|
|
97
|
+
this.connected = false;
|
|
98
|
+
this.connecting = false;
|
|
99
|
+
this._client = null;
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
dbg('CONNECT', `opening SSH to ${this.host}:${this.port}...`);
|
|
103
|
+
this._client.connect(connectConfig);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
return this._connectPromise;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Execute a command over SSH.
|
|
111
|
+
* @param {string} command
|
|
112
|
+
* @param {number} [timeoutMs=30000]
|
|
113
|
+
* @returns {Promise<{stdout: string, stderr: string, code: number}>}
|
|
114
|
+
*/
|
|
115
|
+
async exec(command, timeoutMs = 30000) {
|
|
116
|
+
if (!this.connected) {
|
|
117
|
+
await this.connect();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
this.commandCount++;
|
|
121
|
+
dbg('EXEC', `[#${this.commandCount}] ${command}`);
|
|
122
|
+
|
|
123
|
+
return new Promise((resolve, reject) => {
|
|
124
|
+
const timer = setTimeout(() => {
|
|
125
|
+
reject(new Error(`SSH command timeout after ${timeoutMs}ms`));
|
|
126
|
+
}, timeoutMs);
|
|
127
|
+
|
|
128
|
+
this._client.exec(command, (err, stream) => {
|
|
129
|
+
if (err) {
|
|
130
|
+
clearTimeout(timer);
|
|
131
|
+
reject(new Error(`SSH exec error: ${err.message}`));
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
let stdout = '';
|
|
136
|
+
let stderr = '';
|
|
137
|
+
|
|
138
|
+
stream.on('data', (data) => { stdout += data.toString(); });
|
|
139
|
+
stream.stderr.on('data', (data) => { stderr += data.toString(); });
|
|
140
|
+
|
|
141
|
+
stream.on('close', (code) => {
|
|
142
|
+
clearTimeout(timer);
|
|
143
|
+
dbg('EXEC', `[#${this.commandCount}] exit=${code} stdout=${stdout.length}B stderr=${stderr.length}B`);
|
|
144
|
+
resolve({ stdout, stderr, code: code ?? 0 });
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Disconnect from the SSH server.
|
|
152
|
+
*/
|
|
153
|
+
disconnect() {
|
|
154
|
+
dbg('DISCONNECT', 'closing connection');
|
|
155
|
+
if (this._client) {
|
|
156
|
+
this._client.end();
|
|
157
|
+
this._client = null;
|
|
158
|
+
}
|
|
159
|
+
this.connected = false;
|
|
160
|
+
this.connecting = false;
|
|
161
|
+
}
|
|
162
|
+
}
|
package/lib/types.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
/**
|
|
3
|
+
* █████╗ ██████╗ █████╗ ███████╗
|
|
4
|
+
* ██╔══██╗██╔══██╗██╔══██╗██╔════╝
|
|
5
|
+
* ███████║██████╔╝███████║███████╗
|
|
6
|
+
* ██╔══██║██╔══██╗██╔══██║╚════██║
|
|
7
|
+
* ██║ ██║██║ ██║██║ ██║███████║
|
|
8
|
+
* ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝
|
|
9
|
+
*
|
|
10
|
+
* Copyright (c) 2025 Rıza Emre ARAS <r.emrearas@proton.me>
|
|
11
|
+
*
|
|
12
|
+
* This file is part of Claude KVM.
|
|
13
|
+
* Released under the MIT License — see LICENSE for details.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {object} VNCConnectionConfig
|
|
18
|
+
* @property {string} host
|
|
19
|
+
* @property {number} port
|
|
20
|
+
* @property {'auto' | 'none'} auth
|
|
21
|
+
* @property {string} [username]
|
|
22
|
+
* @property {string} [password]
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @typedef {object} TypingDelayConfig
|
|
27
|
+
* @property {number} min
|
|
28
|
+
* @property {number} max
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @typedef {object} HIDConfig
|
|
33
|
+
* @property {number} click_hold_ms
|
|
34
|
+
* @property {number} key_hold_ms
|
|
35
|
+
* @property {TypingDelayConfig} typing_delay_ms
|
|
36
|
+
* @property {number} scroll_events_per_step
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @typedef {object} CaptureConfig
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @typedef {object} DiffConfig
|
|
45
|
+
* @property {number} pixel_threshold
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @typedef {object} DisplayConfig
|
|
50
|
+
* @property {number} max_dimension
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @typedef {object} VNCTimeoutConfig
|
|
55
|
+
* @property {number} connect_timeout_ms
|
|
56
|
+
* @property {number} screenshot_timeout_ms
|
|
57
|
+
*/
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* @typedef {object} ClaudeKVMConfig
|
|
61
|
+
* @property {DisplayConfig} [display]
|
|
62
|
+
* @property {HIDConfig} hid
|
|
63
|
+
* @property {CaptureConfig} capture
|
|
64
|
+
* @property {DiffConfig} diff
|
|
65
|
+
* @property {VNCTimeoutConfig} [vnc_timeouts]
|
|
66
|
+
*/
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @typedef {object} PixelFormat
|
|
70
|
+
* @property {number} bitsPerPixel
|
|
71
|
+
* @property {number} depth
|
|
72
|
+
* @property {number} bigEndian
|
|
73
|
+
* @property {number} trueColour
|
|
74
|
+
* @property {number} redMax
|
|
75
|
+
* @property {number} greenMax
|
|
76
|
+
* @property {number} blueMax
|
|
77
|
+
* @property {number} redShift
|
|
78
|
+
* @property {number} greenShift
|
|
79
|
+
* @property {number} blueShift
|
|
80
|
+
*/
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @typedef {object} VNCServerInfo
|
|
84
|
+
* @property {number} width
|
|
85
|
+
* @property {number} height
|
|
86
|
+
* @property {string} name
|
|
87
|
+
*/
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @typedef {object} ScreenshotResult
|
|
91
|
+
* @property {Buffer} buffer
|
|
92
|
+
* @property {string} base64
|
|
93
|
+
* @property {number} width
|
|
94
|
+
* @property {number} height
|
|
95
|
+
*/
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* @typedef {object} QuickDiffResult
|
|
100
|
+
* @property {boolean} changeDetected
|
|
101
|
+
*/
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* @typedef {object} ScaledDisplay
|
|
105
|
+
* @property {number} width
|
|
106
|
+
* @property {number} height
|
|
107
|
+
*/
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* @typedef {object} ToolExecResult
|
|
111
|
+
* @property {string} text
|
|
112
|
+
* @property {string} [imageBase64]
|
|
113
|
+
* @property {boolean} [done]
|
|
114
|
+
* @property {'success' | 'failed'} [status]
|
|
115
|
+
*/
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* @typedef {object} KeysymMapping
|
|
119
|
+
* @property {number} keysym
|
|
120
|
+
* @property {boolean} shift
|
|
121
|
+
*/
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* @typedef {object} CursorPosition
|
|
125
|
+
* @property {number} x
|
|
126
|
+
* @property {number} y
|
|
127
|
+
*/
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* @typedef {object} SSHConnectionConfig
|
|
131
|
+
* @property {string} host
|
|
132
|
+
* @property {number} [port]
|
|
133
|
+
* @property {string} username
|
|
134
|
+
* @property {string} [password]
|
|
135
|
+
* @property {string} [privateKeyPath]
|
|
136
|
+
*/
|
|
137
|
+
|
|
138
|
+
export {};
|