@midscene/computer 1.3.12-beta-20260212063915.0 → 1.4.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.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ require('../dist/lib/cli.js');
@@ -0,0 +1,626 @@
1
+ import { CLIError, runToolsCLI } from "@midscene/shared/cli";
2
+ import { getMidsceneLocationSchema, z } from "@midscene/core";
3
+ import { getDebug } from "@midscene/shared/logger";
4
+ import { BaseMidsceneTools } from "@midscene/shared/mcp";
5
+ import { Agent } from "@midscene/core/agent";
6
+ import node_assert from "node:assert";
7
+ import { execSync } from "node:child_process";
8
+ import { createRequire } from "node:module";
9
+ import { actionHoverParamSchema, defineAction, defineActionClearInput, defineActionDoubleClick, defineActionDragAndDrop, defineActionKeyboardPress, defineActionRightClick, defineActionScroll, defineActionTap } from "@midscene/core/device";
10
+ import { sleep } from "@midscene/core/utils";
11
+ import { createImgBase64ByFormat } from "@midscene/shared/img";
12
+ import screenshot_desktop from "screenshot-desktop";
13
+ function _define_property(obj, key, value) {
14
+ if (key in obj) Object.defineProperty(obj, key, {
15
+ value: value,
16
+ enumerable: true,
17
+ configurable: true,
18
+ writable: true
19
+ });
20
+ else obj[key] = value;
21
+ return obj;
22
+ }
23
+ const SMOOTH_MOVE_STEPS_TAP = 8;
24
+ const SMOOTH_MOVE_STEPS_MOUSE_MOVE = 10;
25
+ const SMOOTH_MOVE_DELAY_TAP = 8;
26
+ const SMOOTH_MOVE_DELAY_MOUSE_MOVE = 10;
27
+ const MOUSE_MOVE_EFFECT_WAIT = 300;
28
+ const CLICK_HOLD_DURATION = 50;
29
+ const INPUT_FOCUS_DELAY = 300;
30
+ const INPUT_CLEAR_DELAY = 150;
31
+ const SCROLL_REPEAT_COUNT = 10;
32
+ const SCROLL_STEP_DELAY = 100;
33
+ const SCROLL_COMPLETE_DELAY = 500;
34
+ const INPUT_STRATEGY_ALWAYS_CLIPBOARD = 'always-clipboard';
35
+ const INPUT_STRATEGY_CLIPBOARD_FOR_NON_ASCII = 'clipboard-for-non-ascii';
36
+ const APPLESCRIPT_KEY_CODE_MAP = {
37
+ return: 36,
38
+ enter: 36,
39
+ tab: 48,
40
+ space: 49,
41
+ backspace: 51,
42
+ delete: 51,
43
+ escape: 53,
44
+ forwarddelete: 117,
45
+ left: 123,
46
+ right: 124,
47
+ down: 125,
48
+ up: 126,
49
+ home: 115,
50
+ end: 119,
51
+ pageup: 116,
52
+ pagedown: 121,
53
+ f1: 122,
54
+ f2: 120,
55
+ f3: 99,
56
+ f4: 118,
57
+ f5: 96,
58
+ f6: 97,
59
+ f7: 98,
60
+ f8: 100,
61
+ f9: 101,
62
+ f10: 109,
63
+ f11: 103,
64
+ f12: 111
65
+ };
66
+ const APPLESCRIPT_MODIFIER_MAP = {
67
+ command: 'command down',
68
+ cmd: 'command down',
69
+ control: 'control down',
70
+ ctrl: 'control down',
71
+ shift: 'shift down',
72
+ alt: 'option down',
73
+ option: 'option down',
74
+ meta: 'command down'
75
+ };
76
+ function typeStringViaAppleScript(text) {
77
+ const escaped = text.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
78
+ const script = `tell application "System Events" to keystroke "${escaped}"`;
79
+ debugDevice('typeStringViaAppleScript', {
80
+ text
81
+ });
82
+ execSync(`osascript -e '${script}'`);
83
+ }
84
+ function sendKeyViaAppleScript(key, modifiers = []) {
85
+ const lowerKey = key.toLowerCase();
86
+ const keyCode = APPLESCRIPT_KEY_CODE_MAP[lowerKey];
87
+ const modifierParts = modifiers.map((m)=>APPLESCRIPT_MODIFIER_MAP[m.toLowerCase()]).filter(Boolean);
88
+ const modifierStr = modifierParts.length > 0 ? ` using {${modifierParts.join(', ')}}` : '';
89
+ let script;
90
+ if (void 0 !== keyCode) script = `tell application "System Events" to key code ${keyCode}${modifierStr}`;
91
+ else lowerKey.length, script = `tell application "System Events" to keystroke "${key}"${modifierStr}`;
92
+ debugDevice('sendKeyViaAppleScript', {
93
+ key,
94
+ modifiers,
95
+ script
96
+ });
97
+ execSync(`osascript -e '${script}'`);
98
+ }
99
+ let libnut = null;
100
+ let libnutLoadError = null;
101
+ async function getLibnut() {
102
+ if (libnut) return libnut;
103
+ if (libnutLoadError) throw libnutLoadError;
104
+ try {
105
+ const require = createRequire(import.meta.url);
106
+ const libnutModule = require('@computer-use/libnut/dist/import_libnut');
107
+ libnut = libnutModule.libnut;
108
+ if (!libnut) throw new Error('libnut module loaded but libnut object is undefined');
109
+ return libnut;
110
+ } catch (error) {
111
+ libnutLoadError = error;
112
+ throw new Error(`Failed to load @computer-use/libnut. Make sure it is properly installed and compiled for your platform. Error: ${error}`);
113
+ }
114
+ }
115
+ const debugDevice = getDebug('computer:device');
116
+ async function smoothMoveMouse(targetX, targetY, steps, stepDelay) {
117
+ node_assert(libnut, 'libnut not initialized');
118
+ const currentPos = libnut.getMousePos();
119
+ for(let i = 1; i <= steps; i++){
120
+ const stepX = Math.round(currentPos.x + (targetX - currentPos.x) * i / steps);
121
+ const stepY = Math.round(currentPos.y + (targetY - currentPos.y) * i / steps);
122
+ libnut.moveMouse(stepX, stepY);
123
+ await sleep(stepDelay);
124
+ }
125
+ }
126
+ const KEY_NAME_MAP = {
127
+ windows: 'win',
128
+ win: 'win',
129
+ ctrl: 'control',
130
+ esc: 'escape',
131
+ del: 'delete',
132
+ ins: 'insert',
133
+ pgup: 'pageup',
134
+ pgdn: 'pagedown',
135
+ arrowup: 'up',
136
+ arrowdown: 'down',
137
+ arrowleft: 'left',
138
+ arrowright: 'right',
139
+ volumedown: 'audio_vol_down',
140
+ volumeup: 'audio_vol_up',
141
+ mediavolumedown: 'audio_vol_down',
142
+ mediavolumeup: 'audio_vol_up',
143
+ mute: 'audio_mute',
144
+ mediamute: 'audio_mute',
145
+ mediaplay: 'audio_play',
146
+ mediapause: 'audio_pause',
147
+ mediaplaypause: 'audio_play',
148
+ mediastop: 'audio_stop',
149
+ medianexttrack: 'audio_next',
150
+ mediaprevioustrack: 'audio_prev',
151
+ medianext: 'audio_next',
152
+ mediaprev: 'audio_prev'
153
+ };
154
+ const PRIMARY_KEY_MAP = {
155
+ command: 'cmd',
156
+ cmd: 'cmd',
157
+ meta: 'meta',
158
+ control: 'control',
159
+ ctrl: 'control',
160
+ shift: 'shift',
161
+ alt: 'alt',
162
+ option: 'alt'
163
+ };
164
+ function normalizeKeyName(key) {
165
+ const lowerKey = key.toLowerCase();
166
+ return KEY_NAME_MAP[lowerKey] || lowerKey;
167
+ }
168
+ function normalizePrimaryKey(key) {
169
+ const lowerKey = key.toLowerCase();
170
+ if (PRIMARY_KEY_MAP[lowerKey]) return PRIMARY_KEY_MAP[lowerKey];
171
+ return KEY_NAME_MAP[lowerKey] || lowerKey;
172
+ }
173
+ class ComputerDevice {
174
+ describe() {
175
+ return this.description || 'Computer Device';
176
+ }
177
+ static async listDisplays() {
178
+ try {
179
+ const displays = await screenshot_desktop.listDisplays();
180
+ return displays.map((d)=>({
181
+ id: String(d.id),
182
+ name: d.name || `Display ${d.id}`,
183
+ primary: d.primary || false
184
+ }));
185
+ } catch (error) {
186
+ debugDevice(`Failed to list displays: ${error}`);
187
+ return [];
188
+ }
189
+ }
190
+ async connect() {
191
+ debugDevice('Connecting to computer device');
192
+ try {
193
+ libnut = await getLibnut();
194
+ const size = await this.size();
195
+ const displays = await ComputerDevice.listDisplays();
196
+ this.description = `
197
+ Type: Computer
198
+ Platform: ${process.platform}
199
+ Display: ${this.displayId || 'Primary'}
200
+ Screen Size: ${size.width}x${size.height}
201
+ Available Displays: ${displays.length > 0 ? displays.map((d)=>d.name).join(', ') : 'Unknown'}
202
+ `;
203
+ debugDevice('Computer device connected', this.description);
204
+ } catch (error) {
205
+ debugDevice(`Failed to connect: ${error}`);
206
+ throw new Error(`Unable to connect to computer device: ${error}`);
207
+ }
208
+ }
209
+ async screenshotBase64() {
210
+ debugDevice('Taking screenshot', {
211
+ displayId: this.displayId
212
+ });
213
+ try {
214
+ const options = {
215
+ format: 'png'
216
+ };
217
+ if (void 0 !== this.displayId) if ('darwin' === process.platform) {
218
+ const screenIndex = Number(this.displayId);
219
+ if (!Number.isNaN(screenIndex)) options.screen = screenIndex;
220
+ } else options.screen = this.displayId;
221
+ debugDevice('Screenshot options', options);
222
+ const buffer = await screenshot_desktop(options);
223
+ return createImgBase64ByFormat('png', buffer.toString('base64'));
224
+ } catch (error) {
225
+ debugDevice(`Screenshot failed: ${error}`);
226
+ throw new Error(`Failed to take screenshot: ${error}`);
227
+ }
228
+ }
229
+ async size() {
230
+ node_assert(libnut, 'libnut not initialized');
231
+ try {
232
+ const screenSize = libnut.getScreenSize();
233
+ return {
234
+ width: screenSize.width,
235
+ height: screenSize.height,
236
+ dpr: 1
237
+ };
238
+ } catch (error) {
239
+ debugDevice(`Failed to get screen size: ${error}`);
240
+ throw new Error(`Failed to get screen size: ${error}`);
241
+ }
242
+ }
243
+ shouldUseClipboardForText(text) {
244
+ const hasNonAscii = /[\x80-\uFFFF]/.test(text);
245
+ return hasNonAscii;
246
+ }
247
+ async typeViaClipboard(text) {
248
+ node_assert(libnut, 'libnut not initialized');
249
+ debugDevice('Using clipboard to input text', {
250
+ textLength: text.length,
251
+ preview: text.substring(0, 20)
252
+ });
253
+ const clipboardy = await import("clipboardy");
254
+ const oldClipboard = await clipboardy.default.read().catch(()=>'');
255
+ try {
256
+ await clipboardy.default.write(text);
257
+ await sleep(50);
258
+ if (this.useAppleScript) sendKeyViaAppleScript('v', [
259
+ 'command'
260
+ ]);
261
+ else {
262
+ const modifier = 'darwin' === process.platform ? 'command' : 'control';
263
+ libnut.keyTap('v', [
264
+ modifier
265
+ ]);
266
+ }
267
+ await sleep(100);
268
+ } finally{
269
+ if (oldClipboard) await clipboardy.default.write(oldClipboard).catch(()=>{
270
+ debugDevice('Failed to restore clipboard content');
271
+ });
272
+ }
273
+ }
274
+ async smartTypeString(text) {
275
+ node_assert(libnut, 'libnut not initialized');
276
+ if ('darwin' === process.platform) {
277
+ if (this.useAppleScript) if (this.shouldUseClipboardForText(text)) await this.typeViaClipboard(text);
278
+ else typeStringViaAppleScript(text);
279
+ else libnut.typeString(text);
280
+ return;
281
+ }
282
+ const inputStrategy = this.options?.inputStrategy ?? INPUT_STRATEGY_CLIPBOARD_FOR_NON_ASCII;
283
+ if (inputStrategy === INPUT_STRATEGY_ALWAYS_CLIPBOARD) return void await this.typeViaClipboard(text);
284
+ const shouldUseClipboard = this.shouldUseClipboardForText(text);
285
+ if (shouldUseClipboard) await this.typeViaClipboard(text);
286
+ else libnut.typeString(text);
287
+ }
288
+ actionSpace() {
289
+ const defaultActions = [
290
+ defineActionTap(async (param)=>{
291
+ node_assert(libnut, 'libnut not initialized');
292
+ const element = param.locate;
293
+ node_assert(element, 'Element not found, cannot tap');
294
+ const [x, y] = element.center;
295
+ const targetX = Math.round(x);
296
+ const targetY = Math.round(y);
297
+ await smoothMoveMouse(targetX, targetY, SMOOTH_MOVE_STEPS_TAP, SMOOTH_MOVE_DELAY_TAP);
298
+ libnut.mouseToggle('down', 'left');
299
+ await sleep(CLICK_HOLD_DURATION);
300
+ libnut.mouseToggle('up', 'left');
301
+ }),
302
+ defineActionDoubleClick(async (param)=>{
303
+ node_assert(libnut, 'libnut not initialized');
304
+ const element = param.locate;
305
+ node_assert(element, 'Element not found, cannot double click');
306
+ const [x, y] = element.center;
307
+ libnut.moveMouse(Math.round(x), Math.round(y));
308
+ libnut.mouseClick('left', true);
309
+ }),
310
+ defineActionRightClick(async (param)=>{
311
+ node_assert(libnut, 'libnut not initialized');
312
+ const element = param.locate;
313
+ node_assert(element, 'Element not found, cannot right click');
314
+ const [x, y] = element.center;
315
+ libnut.moveMouse(Math.round(x), Math.round(y));
316
+ libnut.mouseClick('right');
317
+ }),
318
+ defineAction({
319
+ name: 'MouseMove',
320
+ description: 'Move the mouse to the element',
321
+ interfaceAlias: 'aiHover',
322
+ paramSchema: actionHoverParamSchema,
323
+ call: async (param)=>{
324
+ node_assert(libnut, 'libnut not initialized');
325
+ const element = param.locate;
326
+ node_assert(element, 'Element not found, cannot move mouse');
327
+ const [x, y] = element.center;
328
+ const targetX = Math.round(x);
329
+ const targetY = Math.round(y);
330
+ await smoothMoveMouse(targetX, targetY, SMOOTH_MOVE_STEPS_MOUSE_MOVE, SMOOTH_MOVE_DELAY_MOUSE_MOVE);
331
+ await sleep(MOUSE_MOVE_EFFECT_WAIT);
332
+ }
333
+ }),
334
+ defineAction({
335
+ name: 'Input',
336
+ description: 'Input text into the input field',
337
+ interfaceAlias: 'aiInput',
338
+ paramSchema: z.object({
339
+ value: z.string().describe('The text to input'),
340
+ mode: z["enum"]([
341
+ 'replace',
342
+ 'clear',
343
+ 'append'
344
+ ]).default('replace').optional().describe('Input mode: replace, clear, or append'),
345
+ locate: getMidsceneLocationSchema().describe('The input field to be filled').optional()
346
+ }),
347
+ call: async (param)=>{
348
+ node_assert(libnut, 'libnut not initialized');
349
+ const element = param.locate;
350
+ if (element) {
351
+ const [x, y] = element.center;
352
+ libnut.moveMouse(Math.round(x), Math.round(y));
353
+ libnut.mouseClick('left');
354
+ await sleep(INPUT_FOCUS_DELAY);
355
+ if ('append' !== param.mode) {
356
+ if (this.useAppleScript) {
357
+ sendKeyViaAppleScript('a', [
358
+ 'command'
359
+ ]);
360
+ await sleep(50);
361
+ sendKeyViaAppleScript('backspace', []);
362
+ } else {
363
+ const modifier = 'darwin' === process.platform ? 'command' : 'control';
364
+ libnut.keyTap('a', [
365
+ modifier
366
+ ]);
367
+ await sleep(50);
368
+ libnut.keyTap('backspace');
369
+ }
370
+ await sleep(INPUT_CLEAR_DELAY);
371
+ }
372
+ }
373
+ if ('clear' === param.mode) return;
374
+ if (!param.value) return;
375
+ await this.smartTypeString(param.value);
376
+ }
377
+ }),
378
+ defineActionScroll(async (param)=>{
379
+ node_assert(libnut, 'libnut not initialized');
380
+ if (param.locate) {
381
+ const element = param.locate;
382
+ const [x, y] = element.center;
383
+ libnut.moveMouse(Math.round(x), Math.round(y));
384
+ }
385
+ const scrollType = param?.scrollType;
386
+ const scrollToEdgeActions = {
387
+ scrollToTop: [
388
+ 0,
389
+ 10
390
+ ],
391
+ scrollToBottom: [
392
+ 0,
393
+ -10
394
+ ],
395
+ scrollToLeft: [
396
+ -10,
397
+ 0
398
+ ],
399
+ scrollToRight: [
400
+ 10,
401
+ 0
402
+ ]
403
+ };
404
+ const edgeAction = scrollToEdgeActions[scrollType || ''];
405
+ if (edgeAction) {
406
+ const [dx, dy] = edgeAction;
407
+ for(let i = 0; i < SCROLL_REPEAT_COUNT; i++){
408
+ libnut.scrollMouse(dx, dy);
409
+ await sleep(SCROLL_STEP_DELAY);
410
+ }
411
+ return;
412
+ }
413
+ if ('singleAction' === scrollType || !scrollType) {
414
+ const distance = param?.distance || 500;
415
+ const ticks = Math.ceil(distance / 100);
416
+ const direction = param?.direction || 'down';
417
+ const directionMap = {
418
+ up: [
419
+ 0,
420
+ ticks
421
+ ],
422
+ down: [
423
+ 0,
424
+ -ticks
425
+ ],
426
+ left: [
427
+ -ticks,
428
+ 0
429
+ ],
430
+ right: [
431
+ ticks,
432
+ 0
433
+ ]
434
+ };
435
+ const [dx, dy] = directionMap[direction] || [
436
+ 0,
437
+ -ticks
438
+ ];
439
+ libnut.scrollMouse(dx, dy);
440
+ await sleep(SCROLL_COMPLETE_DELAY);
441
+ return;
442
+ }
443
+ throw new Error(`Unknown scroll type: ${scrollType}, param: ${JSON.stringify(param)}`);
444
+ }),
445
+ defineActionKeyboardPress(async (param)=>{
446
+ node_assert(libnut, 'libnut not initialized');
447
+ if (param.locate) {
448
+ const [x, y] = param.locate.center;
449
+ libnut.moveMouse(Math.round(x), Math.round(y));
450
+ libnut.mouseClick('left');
451
+ await sleep(50);
452
+ }
453
+ const keys = param.keyName.split('+');
454
+ const modifiers = keys.slice(0, -1).map(normalizeKeyName);
455
+ const key = normalizePrimaryKey(keys[keys.length - 1]);
456
+ debugDevice('KeyboardPress', {
457
+ original: param.keyName,
458
+ key,
459
+ modifiers,
460
+ driver: this.useAppleScript ? "applescript" : 'libnut'
461
+ });
462
+ if (this.useAppleScript) sendKeyViaAppleScript(key, modifiers);
463
+ else if (modifiers.length > 0) libnut.keyTap(key, modifiers);
464
+ else libnut.keyTap(key);
465
+ }),
466
+ defineActionDragAndDrop(async (param)=>{
467
+ node_assert(libnut, 'libnut not initialized');
468
+ const from = param.from;
469
+ const to = param.to;
470
+ node_assert(from, 'missing "from" param for drag and drop');
471
+ node_assert(to, 'missing "to" param for drag and drop');
472
+ const [fromX, fromY] = from.center;
473
+ const [toX, toY] = to.center;
474
+ libnut.moveMouse(Math.round(fromX), Math.round(fromY));
475
+ libnut.mouseToggle('down', 'left');
476
+ await sleep(100);
477
+ libnut.moveMouse(Math.round(toX), Math.round(toY));
478
+ await sleep(100);
479
+ libnut.mouseToggle('up', 'left');
480
+ }),
481
+ defineActionClearInput(async (param)=>{
482
+ node_assert(libnut, 'libnut not initialized');
483
+ const element = param.locate;
484
+ node_assert(element, 'Element not found, cannot clear input');
485
+ const [x, y] = element.center;
486
+ libnut.moveMouse(Math.round(x), Math.round(y));
487
+ libnut.mouseClick('left');
488
+ await sleep(100);
489
+ if (this.useAppleScript) {
490
+ sendKeyViaAppleScript('a', [
491
+ 'command'
492
+ ]);
493
+ await sleep(50);
494
+ sendKeyViaAppleScript('backspace', []);
495
+ } else {
496
+ const modifier = 'darwin' === process.platform ? 'command' : 'control';
497
+ libnut.keyTap('a', [
498
+ modifier
499
+ ]);
500
+ libnut.keyTap('backspace');
501
+ }
502
+ await sleep(50);
503
+ })
504
+ ];
505
+ const platformActions = Object.values(createPlatformActions());
506
+ const customActions = this.options?.customActions || [];
507
+ return [
508
+ ...defaultActions,
509
+ ...platformActions,
510
+ ...customActions
511
+ ];
512
+ }
513
+ async destroy() {
514
+ if (this.destroyed) return;
515
+ this.destroyed = true;
516
+ debugDevice('Computer device destroyed');
517
+ }
518
+ async url() {
519
+ return '';
520
+ }
521
+ constructor(options){
522
+ _define_property(this, "interfaceType", 'computer');
523
+ _define_property(this, "options", void 0);
524
+ _define_property(this, "displayId", void 0);
525
+ _define_property(this, "description", void 0);
526
+ _define_property(this, "destroyed", false);
527
+ _define_property(this, "useAppleScript", void 0);
528
+ _define_property(this, "uri", void 0);
529
+ this.options = options;
530
+ this.displayId = options?.displayId;
531
+ this.useAppleScript = 'darwin' === process.platform && options?.keyboardDriver !== 'libnut';
532
+ }
533
+ }
534
+ function createPlatformActions() {
535
+ return {
536
+ ListDisplays: defineAction({
537
+ name: 'ListDisplays',
538
+ description: 'List all available displays/monitors',
539
+ call: async ()=>await ComputerDevice.listDisplays()
540
+ })
541
+ };
542
+ }
543
+ class ComputerAgent extends Agent {
544
+ }
545
+ async function agentFromComputer(opts) {
546
+ const device = new ComputerDevice(opts || {});
547
+ await device.connect();
548
+ return new ComputerAgent(device, opts);
549
+ }
550
+ const debug = getDebug('mcp:computer-tools');
551
+ class ComputerMidsceneTools extends BaseMidsceneTools {
552
+ createTemporaryDevice() {
553
+ return new ComputerDevice({});
554
+ }
555
+ async ensureAgent(displayId) {
556
+ if (this.agent && displayId) {
557
+ try {
558
+ await this.agent.destroy?.();
559
+ } catch (error) {
560
+ debug('Failed to destroy agent during cleanup:', error);
561
+ }
562
+ this.agent = void 0;
563
+ }
564
+ if (this.agent) return this.agent;
565
+ debug('Creating Computer agent with displayId:', displayId || 'primary');
566
+ const opts = displayId ? {
567
+ displayId
568
+ } : void 0;
569
+ const agent = await agentFromComputer(opts);
570
+ this.agent = agent;
571
+ return agent;
572
+ }
573
+ preparePlatformTools() {
574
+ return [
575
+ {
576
+ name: 'computer_connect',
577
+ description: 'Connect to computer desktop. If displayId not provided, uses the primary display.',
578
+ schema: {
579
+ displayId: z.string().optional().describe('Display ID (from list_displays)')
580
+ },
581
+ handler: async ({ displayId })=>{
582
+ const agent = await this.ensureAgent(displayId);
583
+ const screenshot = await agent.interface.screenshotBase64();
584
+ return {
585
+ content: [
586
+ {
587
+ type: 'text',
588
+ text: `Connected to computer${displayId ? ` (Display: ${displayId})` : ' (Primary display)'}`
589
+ },
590
+ ...this.buildScreenshotContent(screenshot)
591
+ ]
592
+ };
593
+ }
594
+ },
595
+ {
596
+ name: 'computer_disconnect',
597
+ description: 'Disconnect from computer and release resources',
598
+ schema: {},
599
+ handler: this.createDisconnectHandler('computer')
600
+ },
601
+ {
602
+ name: 'computer_list_displays',
603
+ description: 'List all available displays/monitors',
604
+ schema: {},
605
+ handler: async ()=>{
606
+ const displays = await ComputerDevice.listDisplays();
607
+ return {
608
+ content: [
609
+ {
610
+ type: 'text',
611
+ text: `Available displays:\n${displays.map((d)=>`- ${d.name} (ID: ${d.id})${d.primary ? ' [PRIMARY]' : ''}`).join('\n')}`
612
+ }
613
+ ]
614
+ };
615
+ }
616
+ }
617
+ ];
618
+ }
619
+ }
620
+ const tools = new ComputerMidsceneTools();
621
+ runToolsCLI(tools, 'midscene-computer', {
622
+ stripPrefix: 'computer_'
623
+ }).catch((e)=>{
624
+ if (!(e instanceof CLIError)) console.error(e);
625
+ process.exit(e instanceof CLIError ? e.exitCode : 1);
626
+ });