usecomputer 0.0.1 → 0.0.2

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.
Files changed (50) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +37 -0
  3. package/bin.js +3 -1
  4. package/build.zig +52 -0
  5. package/build.zig.zon +21 -0
  6. package/dist/bridge-contract.test.d.ts +2 -0
  7. package/dist/bridge-contract.test.d.ts.map +1 -0
  8. package/dist/bridge-contract.test.js +74 -0
  9. package/dist/bridge.d.ts +7 -0
  10. package/dist/bridge.d.ts.map +1 -0
  11. package/dist/bridge.js +130 -0
  12. package/dist/cli-parsing.test.d.ts +2 -0
  13. package/dist/cli-parsing.test.d.ts.map +1 -0
  14. package/dist/cli-parsing.test.js +30 -0
  15. package/dist/cli.d.ts +5 -1
  16. package/dist/cli.d.ts.map +1 -1
  17. package/dist/cli.js +286 -335
  18. package/dist/command-parsers.d.ts +6 -0
  19. package/dist/command-parsers.d.ts.map +1 -0
  20. package/dist/command-parsers.js +54 -0
  21. package/dist/command-parsers.test.d.ts +2 -0
  22. package/dist/command-parsers.test.d.ts.map +1 -0
  23. package/dist/command-parsers.test.js +44 -0
  24. package/dist/darwin-arm64/usecomputer.node +0 -0
  25. package/dist/darwin-x64/usecomputer.node +0 -0
  26. package/dist/index.d.ts +4 -1
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +5 -4
  29. package/dist/native-click-smoke.test.d.ts +2 -0
  30. package/dist/native-click-smoke.test.d.ts.map +1 -0
  31. package/dist/native-click-smoke.test.js +93 -0
  32. package/dist/native-lib.cjs +33 -0
  33. package/dist/native-lib.d.cts +7 -0
  34. package/dist/native-lib.d.ts +5 -0
  35. package/dist/native-lib.d.ts.map +1 -0
  36. package/dist/native-lib.js +27 -0
  37. package/dist/types.d.ts +80 -0
  38. package/dist/types.d.ts.map +1 -0
  39. package/dist/types.js +2 -0
  40. package/package.json +23 -12
  41. package/src/bridge-contract.test.ts +85 -0
  42. package/src/bridge.ts +159 -0
  43. package/src/cli.ts +329 -473
  44. package/src/command-parsers.test.ts +50 -0
  45. package/src/command-parsers.ts +60 -0
  46. package/src/index.ts +5 -4
  47. package/src/native-click-smoke.test.ts +131 -0
  48. package/src/native-lib.ts +38 -0
  49. package/src/types.ts +87 -0
  50. package/zig/src/lib.zig +367 -0
package/dist/cli.js CHANGED
@@ -1,339 +1,290 @@
1
- // usecomputer CLI computer automation for AI agents.
2
- // Outline only. Commands print "not implemented" placeholders.
1
+ // usecomputer CLI entrypoint and command wiring for desktop automation actions.
3
2
  import { goke } from 'goke';
4
3
  import { z } from 'zod';
5
4
  import dedent from 'string-dedent';
6
- import pkg from '../package.json' with { type: 'json' };
7
- const cli = goke('usecomputer');
8
- // ─── Core Commands ──────────────────────────────────────────────────────
9
- cli
10
- .command('snapshot', dedent `
11
- Capture the accessibility tree of the desktop or a window.
5
+ import { createRequire } from 'node:module';
6
+ import url from 'node:url';
7
+ import { createBridge } from './bridge.js';
8
+ import { parseDirection, parseModifiers, parsePoint, parseRegion } from './command-parsers.js';
9
+ const require = createRequire(import.meta.url);
10
+ const packageJson = require('../package.json');
11
+ function printJson(value) {
12
+ process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
13
+ }
14
+ function printLine(value) {
15
+ process.stdout.write(`${value}\n`);
16
+ }
17
+ function parsePointOrThrow(input) {
18
+ const parsed = parsePoint(input);
19
+ if (parsed instanceof Error) {
20
+ throw parsed;
21
+ }
22
+ return parsed;
23
+ }
24
+ function resolvePointInput({ x, y, target, command, }) {
25
+ if (typeof x === 'number' || typeof y === 'number') {
26
+ if (typeof x !== 'number' || typeof y !== 'number') {
27
+ throw new Error(`Command \"${command}\" requires both -x and -y when using coordinate flags`);
28
+ }
29
+ return { x, y };
30
+ }
31
+ if (target) {
32
+ return parsePointOrThrow(target);
33
+ }
34
+ throw new Error(`Command \"${command}\" requires coordinates. Use -x <n> -y <n>`);
35
+ }
36
+ function parseButton(input) {
37
+ if (input === 'right' || input === 'middle') {
38
+ return input;
39
+ }
40
+ return 'left';
41
+ }
42
+ function notImplemented({ command }) {
43
+ throw new Error(`Command \"${command}\" is not implemented yet`);
44
+ }
45
+ export function createCli({ bridge = createBridge() } = {}) {
46
+ const cli = goke('usecomputer');
47
+ cli
48
+ .command('screenshot [path]', dedent `
49
+ Take a screenshot of the entire screen or a region.
12
50
 
13
- Uses native accessibility APIs (macOS AX, AT-SPI on Linux, UIA on Windows)
14
- to produce a structured tree of UI elements with roles, names, and ref IDs.
15
- Refs like @e1, @e2 can be used in click, type, and other commands.
16
- `)
17
- .option('-w, --window [window]', z.string().describe('Target a specific window by title or ID'))
18
- .option('-a, --app [app]', z.string().describe('Target a specific application by name or bundle ID'))
19
- .option('-i, --interactive', 'Only show interactive elements (buttons, inputs, links)')
20
- .option('-c, --compact', 'Remove empty structural elements')
21
- .option('-d, --depth [depth]', z.number().describe('Limit tree depth'))
22
- .example('# Full desktop accessibility snapshot')
23
- .example('usecomputer snapshot')
24
- .example('# Interactive elements in a specific app')
25
- .example('usecomputer snapshot --app "Visual Studio Code" -i')
26
- .action((options) => {
27
- console.log('not implemented');
28
- });
29
- cli
30
- .command('screenshot [path]', dedent `
31
- Take a screenshot of the entire screen, a window, or a region.
32
-
33
- Saves as PNG. If no path is given, prints the file path of a temp file.
34
- Use --window or --app to capture a specific window.
35
- Use --region to capture a rectangular area (x,y,width,height).
36
- `)
37
- .option('-w, --window [window]', z.string().describe('Capture a specific window by title or ID'))
38
- .option('-a, --app [app]', z.string().describe('Capture a specific application by name or bundle ID'))
39
- .option('-r, --region [region]', z.string().describe('Capture region as x,y,width,height'))
40
- .option('--display [display]', z.number().describe('Display/monitor index for multi-monitor setups'))
41
- .option('--annotate', 'Annotate screenshot with numbered labels on interactive elements')
42
- .example('# Screenshot entire screen')
43
- .example('usecomputer screenshot')
44
- .example('# Screenshot a specific app window')
45
- .example('usecomputer screenshot --app Finder ~/Desktop/finder.png')
46
- .example('# Screenshot a region')
47
- .example('usecomputer screenshot --region 100,200,800,600')
48
- .action((path, options) => {
49
- console.log('not implemented');
50
- });
51
- cli
52
- .command('click <target>', dedent `
53
- Click at a target. Target can be:
54
- - Coordinates: "500,300" (x,y pixels)
55
- - Accessibility ref: "@e2" (from a snapshot)
56
- - Text match: "Submit" (finds element by accessible name)
57
- `)
58
- .option('--button [button]', z.enum(['left', 'right', 'middle']).default('left').describe('Mouse button'))
59
- .option('--count [count]', z.number().default(1).describe('Number of clicks (2 for double-click)'))
60
- .option('--modifiers [modifiers]', z.string().describe('Modifier keys held during click (ctrl,shift,alt,meta)'))
61
- .example('# Click at coordinates')
62
- .example('usecomputer click 500,300')
63
- .example('# Click an element from snapshot')
64
- .example('usecomputer click @e2')
65
- .example('# Right-click')
66
- .example('usecomputer click 500,300 --button right')
67
- .example('# Double-click')
68
- .example('usecomputer click @e5 --count 2')
69
- .action((target, options) => {
70
- console.log('not implemented');
71
- });
72
- cli
73
- .command('type <text>', dedent `
74
- Type text using keyboard input, as if the user is typing.
75
-
76
- Types each character sequentially with realistic key events.
77
- Works with the currently focused element. Use "click" first to focus.
78
- `)
79
- .option('--delay [delay]', z.number().describe('Delay between keystrokes in milliseconds'))
80
- .example('# Type into the currently focused field')
81
- .example('usecomputer type "Hello, world!"')
82
- .action((text, options) => {
83
- console.log('not implemented');
84
- });
85
- cli
86
- .command('press <key>', dedent `
87
- Press a key or key combination.
88
-
89
- Supports modifier combos like "ctrl+c", "cmd+shift+s", "alt+tab".
90
- Key names: enter, tab, escape, space, backspace, delete, up, down,
91
- left, right, home, end, pageup, pagedown, f1-f12.
92
- `)
93
- .option('--count [count]', z.number().default(1).describe('Number of times to press'))
94
- .option('--delay [delay]', z.number().describe('Delay between repeated presses in milliseconds'))
95
- .example('# Press Enter')
96
- .example('usecomputer press enter')
97
- .example('# Copy to clipboard')
98
- .example('usecomputer press cmd+c')
99
- .example('# Switch apps on macOS')
100
- .example('usecomputer press cmd+tab')
101
- .example('# Press Escape 3 times')
102
- .example('usecomputer press escape --count 3')
103
- .action((key, options) => {
104
- console.log('not implemented');
105
- });
106
- cli
107
- .command('scroll <direction> [amount]', dedent `
108
- Scroll in a direction. Amount is in pixels (default: 300).
109
-
110
- Directions: up, down, left, right.
111
- Scrolls at the current mouse position unless --at is specified.
112
- `)
113
- .option('--at [at]', z.string().describe('Scroll at specific coordinates (x,y)'))
114
- .example('# Scroll down')
115
- .example('usecomputer scroll down')
116
- .example('# Scroll up 500px at a specific position')
117
- .example('usecomputer scroll up 500 --at 400,300')
118
- .action((direction, amount, options) => {
119
- console.log('not implemented');
120
- });
121
- cli
122
- .command('drag <from> <to>', dedent `
123
- Drag from one position to another.
124
-
125
- Positions are x,y coordinates or accessibility refs (@e1).
126
- Performs mouse-down at "from", moves to "to", then mouse-up.
127
- `)
128
- .option('--duration [duration]', z.number().describe('Duration of the drag in milliseconds'))
129
- .example('# Drag from one position to another')
130
- .example('usecomputer drag 100,200 500,200')
131
- .example('# Drag an accessibility element')
132
- .example('usecomputer drag @e3 400,600')
133
- .action((from, to, options) => {
134
- console.log('not implemented');
135
- });
136
- cli
137
- .command('hover <target>', dedent `
138
- Move the mouse to a target without clicking.
139
-
140
- Target can be coordinates (x,y) or an accessibility ref (@e1).
141
- `)
142
- .example('usecomputer hover 500,300')
143
- .example('usecomputer hover @e4')
144
- .action((target) => {
145
- console.log('not implemented');
146
- });
147
- // ─── Mouse Commands ─────────────────────────────────────────────────────
148
- cli
149
- .command('mouse move <x> <y>', 'Move mouse cursor to absolute screen coordinates.')
150
- .action((x, y) => {
151
- console.log('not implemented');
152
- });
153
- cli
154
- .command('mouse down', 'Press and hold mouse button.')
155
- .option('--button [button]', z.enum(['left', 'right', 'middle']).default('left').describe('Mouse button'))
156
- .action((options) => {
157
- console.log('not implemented');
158
- });
159
- cli
160
- .command('mouse up', 'Release mouse button.')
161
- .option('--button [button]', z.enum(['left', 'right', 'middle']).default('left').describe('Mouse button'))
162
- .action((options) => {
163
- console.log('not implemented');
164
- });
165
- cli
166
- .command('mouse position', 'Print the current mouse cursor position as x,y.')
167
- .action(() => {
168
- console.log('not implemented');
169
- });
170
- // ─── Get Info Commands ──────────────────────────────────────────────────
171
- cli
172
- .command('get text <target>', 'Get the accessible text content of an element. Target is an accessibility ref (@e1) or coordinates.')
173
- .action((target) => {
174
- console.log('not implemented');
175
- });
176
- cli
177
- .command('get title <target>', 'Get the title/name of a window or element. Target is a ref, coordinates, or window ID.')
178
- .action((target) => {
179
- console.log('not implemented');
180
- });
181
- cli
182
- .command('get value <target>', 'Get the current value of an input element (text fields, sliders, checkboxes).')
183
- .action((target) => {
184
- console.log('not implemented');
185
- });
186
- cli
187
- .command('get bounds <target>', 'Get the bounding rectangle (x, y, width, height) of an element or window.')
188
- .action((target) => {
189
- console.log('not implemented');
190
- });
191
- cli
192
- .command('get focused', 'Get the currently focused element and its accessibility info.')
193
- .action(() => {
194
- console.log('not implemented');
195
- });
196
- // ─── Window Management ─────────────────────────────────────────────────
197
- cli
198
- .command('window list', 'List all open windows with their titles, apps, positions, and sizes.')
199
- .option('--app [app]', z.string().describe('Filter by application name'))
200
- .option('--json', 'Output as JSON')
201
- .action((options) => {
202
- console.log('not implemented');
203
- });
204
- cli
205
- .command('window focus <target>', 'Bring a window to the foreground. Target is a window title, ID, or app name.')
206
- .action((target) => {
207
- console.log('not implemented');
208
- });
209
- cli
210
- .command('window resize <target> <width> <height>', 'Resize a window. Target is a window title, ID, or app name.')
211
- .action((target, width, height) => {
212
- console.log('not implemented');
213
- });
214
- cli
215
- .command('window move <target> <x> <y>', 'Move a window to absolute screen coordinates.')
216
- .action((target, x, y) => {
217
- console.log('not implemented');
218
- });
219
- cli
220
- .command('window minimize <target>', 'Minimize a window.')
221
- .action((target) => {
222
- console.log('not implemented');
223
- });
224
- cli
225
- .command('window maximize <target>', 'Maximize/fullscreen a window.')
226
- .action((target) => {
227
- console.log('not implemented');
228
- });
229
- cli
230
- .command('window close <target>', 'Close a window.')
231
- .action((target) => {
232
- console.log('not implemented');
233
- });
234
- // ─── App Management ────────────────────────────────────────────────────
235
- cli
236
- .command('app list', 'List all running applications with their process IDs and window counts.')
237
- .option('--json', 'Output as JSON')
238
- .action((options) => {
239
- console.log('not implemented');
240
- });
241
- cli
242
- .command('app launch <name>', dedent `
243
- Launch an application by name or path.
244
-
245
- On macOS: app name ("Safari"), bundle ID ("com.apple.Safari"), or path.
246
- On Linux: executable name or .desktop file.
247
- On Windows: executable name or Start Menu shortcut.
248
- `)
249
- .option('--wait', 'Wait for the application window to appear before returning')
250
- .action((name, options) => {
251
- console.log('not implemented');
252
- });
253
- cli
254
- .command('app quit <name>', 'Quit an application gracefully by name or process ID.')
255
- .option('--force', 'Force-kill the application if it does not quit gracefully')
256
- .action((name, options) => {
257
- console.log('not implemented');
258
- });
259
- // ─── Clipboard ──────────────────────────────────────────────────────────
260
- cli
261
- .command('clipboard get', 'Print the current clipboard text content.')
262
- .action(() => {
263
- console.log('not implemented');
264
- });
265
- cli
266
- .command('clipboard set <text>', 'Set the clipboard content to the given text.')
267
- .action((text) => {
268
- console.log('not implemented');
269
- });
270
- // ─── Wait ───────────────────────────────────────────────────────────────
271
- cli
272
- .command('wait <target>', dedent `
273
- Wait for a condition before continuing.
274
-
275
- Target can be:
276
- - Milliseconds: "2000" (wait 2 seconds)
277
- - Accessibility ref: "@e5" (wait for element to appear)
278
- - Window title: "--window Untitled" (wait for window to appear)
279
- `)
280
- .option('-w, --window [window]', z.string().describe('Wait for a window with this title to appear'))
281
- .option('--timeout [timeout]', z.number().default(30000).describe('Maximum wait time in milliseconds'))
282
- .example('# Wait 2 seconds')
283
- .example('usecomputer wait 2000')
284
- .example('# Wait for an element to appear')
285
- .example('usecomputer wait @e5')
286
- .example('# Wait for a window to appear')
287
- .example('usecomputer wait --window "Save As"')
288
- .action((target, options) => {
289
- console.log('not implemented');
290
- });
291
- // ─── Display ────────────────────────────────────────────────────────────
292
- cli
293
- .command('display list', 'List connected displays with their resolutions, positions, and scale factors.')
294
- .option('--json', 'Output as JSON')
295
- .action((options) => {
296
- console.log('not implemented');
297
- });
298
- // ─── Find Elements ──────────────────────────────────────────────────────
299
- cli
300
- .command('find <query>', dedent `
301
- Search for UI elements matching a text query across the accessibility tree.
302
-
303
- Returns matching elements with their refs, roles, and positions.
304
- Useful for locating elements before clicking or typing.
305
- `)
306
- .option('-w, --window [window]', z.string().describe('Scope search to a specific window'))
307
- .option('-a, --app [app]', z.string().describe('Scope search to a specific application'))
308
- .option('--role [role]', z.string().describe('Filter by accessibility role (button, textField, link, etc.)'))
309
- .option('--limit [limit]', z.number().default(20).describe('Maximum number of results'))
310
- .example('# Find all buttons with "Save" in the name')
311
- .example('usecomputer find "Save" --role button')
312
- .example('# Find elements in a specific app')
313
- .example('usecomputer find "File" --app "Visual Studio Code"')
314
- .action((query, options) => {
315
- console.log('not implemented');
316
- });
317
- // ─── Diff ───────────────────────────────────────────────────────────────
318
- cli
319
- .command('diff snapshot', 'Compare the current accessibility snapshot against the previous one. Shows added, removed, and changed elements.')
320
- .option('-w, --window [window]', z.string().describe('Scope to a specific window'))
321
- .option('-a, --app [app]', z.string().describe('Scope to a specific application'))
322
- .action((options) => {
323
- console.log('not implemented');
324
- });
325
- cli
326
- .command('diff screenshot', 'Compare the current screenshot against a baseline image. Highlights visual differences.')
327
- .option('--baseline <baseline>', z.string().describe('Path to the baseline screenshot'))
328
- .option('--threshold [threshold]', z.number().default(0.1).describe('Pixel difference threshold (0-1)'))
329
- .action((options) => {
330
- console.log('not implemented');
331
- });
332
- // ─── Global Options ─────────────────────────────────────────────────────
333
- cli.option('--json', 'Output as JSON');
334
- cli.option('--display [display]', z.number().describe('Target display/monitor index for multi-monitor setups'));
335
- cli.option('--timeout [timeout]', z.number().default(25000).describe('Default timeout for operations in milliseconds'));
336
- cli.option('--debug', 'Enable debug output');
337
- cli.help();
338
- cli.version(pkg.version);
339
- cli.parse();
51
+ This command uses a native Zig backend over macOS APIs.
52
+ `)
53
+ .option('-r, --region [region]', z.string().describe('Capture region as x,y,width,height'))
54
+ .option('--display [display]', z.number().describe('Display index for multi-monitor setups'))
55
+ .option('--annotate', 'Annotate screenshot with labels')
56
+ .option('--json', 'Output as JSON')
57
+ .action(async (path, options) => {
58
+ const region = options.region ? parseRegion(options.region) : undefined;
59
+ if (region instanceof Error) {
60
+ throw region;
61
+ }
62
+ const result = await bridge.screenshot({
63
+ path,
64
+ region,
65
+ display: options.display,
66
+ annotate: options.annotate,
67
+ });
68
+ if (options.json) {
69
+ printJson(result);
70
+ return;
71
+ }
72
+ printLine(result.path);
73
+ });
74
+ cli
75
+ .command('click [target]', 'Click at coordinates')
76
+ .option('-x [x]', z.number().describe('X coordinate'))
77
+ .option('-y [y]', z.number().describe('Y coordinate'))
78
+ .option('--button [button]', z.enum(['left', 'right', 'middle']).default('left').describe('Mouse button'))
79
+ .option('--count [count]', z.number().default(1).describe('Number of clicks'))
80
+ .option('--modifiers [modifiers]', z.string().describe('Modifiers as ctrl,shift,alt,meta'))
81
+ .action(async (target, options) => {
82
+ const point = resolvePointInput({
83
+ x: options.x,
84
+ y: options.y,
85
+ target,
86
+ command: 'click',
87
+ });
88
+ await bridge.click({
89
+ point,
90
+ button: options.button,
91
+ count: options.count,
92
+ modifiers: parseModifiers(options.modifiers),
93
+ });
94
+ });
95
+ cli
96
+ .command('type <text>', 'Type text in the focused element')
97
+ .option('--delay [delay]', z.number().describe('Delay in milliseconds between keystrokes'))
98
+ .action(async (text, options) => {
99
+ await bridge.typeText({ text, delayMs: options.delay });
100
+ });
101
+ cli
102
+ .command('press <key>', 'Press a key or key combo')
103
+ .option('--count [count]', z.number().default(1).describe('How many times to press'))
104
+ .option('--delay [delay]', z.number().describe('Delay between presses in milliseconds'))
105
+ .action(async (key, options) => {
106
+ await bridge.press({ key, count: options.count, delayMs: options.delay });
107
+ });
108
+ cli
109
+ .command('scroll <direction> [amount]', 'Scroll in a direction')
110
+ .option('--at [at]', z.string().describe('Coordinates x,y where scroll happens'))
111
+ .action(async (direction, amount, options) => {
112
+ const parsedDirection = parseDirection(direction);
113
+ if (parsedDirection instanceof Error) {
114
+ throw parsedDirection;
115
+ }
116
+ const at = options.at ? parsePointOrThrow(options.at) : undefined;
117
+ const scrollAmount = amount ? Number(amount) : 300;
118
+ if (!Number.isFinite(scrollAmount)) {
119
+ throw new Error(`Invalid amount \"${amount}\"`);
120
+ }
121
+ await bridge.scroll({
122
+ direction: parsedDirection,
123
+ amount: scrollAmount,
124
+ at,
125
+ });
126
+ });
127
+ cli
128
+ .command('drag <from> <to>', 'Drag from one coordinate to another')
129
+ .option('--duration [duration]', z.number().describe('Duration in milliseconds'))
130
+ .option('--button [button]', z.enum(['left', 'right', 'middle']).default('left').describe('Mouse button'))
131
+ .action(async (from, to, options) => {
132
+ await bridge.drag({
133
+ from: parsePointOrThrow(from),
134
+ to: parsePointOrThrow(to),
135
+ durationMs: options.duration,
136
+ button: options.button,
137
+ });
138
+ });
139
+ cli
140
+ .command('hover [target]', 'Move mouse cursor to coordinates without clicking')
141
+ .option('-x [x]', z.number().describe('X coordinate'))
142
+ .option('-y [y]', z.number().describe('Y coordinate'))
143
+ .action(async (target, options) => {
144
+ const point = resolvePointInput({
145
+ x: options.x,
146
+ y: options.y,
147
+ target,
148
+ command: 'hover',
149
+ });
150
+ await bridge.hover(point);
151
+ });
152
+ cli
153
+ .command('mouse move [x] [y]', 'Move mouse cursor to absolute coordinates')
154
+ .option('-x [x]', z.number().describe('X coordinate'))
155
+ .option('-y [y]', z.number().describe('Y coordinate'))
156
+ .action(async (x, y, options) => {
157
+ const point = resolvePointInput({
158
+ x: options.x,
159
+ y: options.y,
160
+ target: x && y ? `${x},${y}` : undefined,
161
+ command: 'mouse move',
162
+ });
163
+ await bridge.mouseMove(point);
164
+ });
165
+ cli
166
+ .command('mouse down', 'Press and hold mouse button')
167
+ .option('--button [button]', z.enum(['left', 'right', 'middle']).default('left').describe('Mouse button'))
168
+ .action(async (options) => {
169
+ await bridge.mouseDown({ button: parseButton(options.button) });
170
+ });
171
+ cli
172
+ .command('mouse up', 'Release mouse button')
173
+ .option('--button [button]', z.enum(['left', 'right', 'middle']).default('left').describe('Mouse button'))
174
+ .action(async (options) => {
175
+ await bridge.mouseUp({ button: parseButton(options.button) });
176
+ });
177
+ cli
178
+ .command('mouse position', 'Print current mouse position as x,y')
179
+ .option('--json', 'Output as JSON')
180
+ .action(async (options) => {
181
+ const position = await bridge.mousePosition();
182
+ if (options.json) {
183
+ printJson(position);
184
+ return;
185
+ }
186
+ printLine(`${position.x},${position.y}`);
187
+ });
188
+ cli
189
+ .command('display list', 'List connected displays')
190
+ .option('--json', 'Output as JSON')
191
+ .action(async (options) => {
192
+ const displays = await bridge.displayList();
193
+ if (options.json) {
194
+ printJson(displays);
195
+ return;
196
+ }
197
+ displays.forEach((display) => {
198
+ const primary = display.isPrimary ? ' (primary)' : '';
199
+ printLine(`#${display.id} ${display.name}${primary} ${display.width}x${display.height} @ (${display.x},${display.y}) scale=${display.scale}`);
200
+ });
201
+ });
202
+ cli
203
+ .command('clipboard get', 'Print clipboard text')
204
+ .action(async () => {
205
+ const text = await bridge.clipboardGet();
206
+ printLine(text);
207
+ });
208
+ cli
209
+ .command('clipboard set <text>', 'Set clipboard text')
210
+ .action(async (text) => {
211
+ await bridge.clipboardSet({ text });
212
+ });
213
+ cli.command('snapshot').action(() => {
214
+ notImplemented({ command: 'snapshot' });
215
+ });
216
+ cli.command('get text <target>').action(() => {
217
+ notImplemented({ command: 'get text' });
218
+ });
219
+ cli.command('get title <target>').action(() => {
220
+ notImplemented({ command: 'get title' });
221
+ });
222
+ cli.command('get value <target>').action(() => {
223
+ notImplemented({ command: 'get value' });
224
+ });
225
+ cli.command('get bounds <target>').action(() => {
226
+ notImplemented({ command: 'get bounds' });
227
+ });
228
+ cli.command('get focused').action(() => {
229
+ notImplemented({ command: 'get focused' });
230
+ });
231
+ cli.command('window list').action(() => {
232
+ notImplemented({ command: 'window list' });
233
+ });
234
+ cli.command('window focus <target>').action(() => {
235
+ notImplemented({ command: 'window focus' });
236
+ });
237
+ cli.command('window resize <target> <width> <height>').action(() => {
238
+ notImplemented({ command: 'window resize' });
239
+ });
240
+ cli.command('window move <target> <x> <y>').action(() => {
241
+ notImplemented({ command: 'window move' });
242
+ });
243
+ cli.command('window minimize <target>').action(() => {
244
+ notImplemented({ command: 'window minimize' });
245
+ });
246
+ cli.command('window maximize <target>').action(() => {
247
+ notImplemented({ command: 'window maximize' });
248
+ });
249
+ cli.command('window close <target>').action(() => {
250
+ notImplemented({ command: 'window close' });
251
+ });
252
+ cli.command('app list').action(() => {
253
+ notImplemented({ command: 'app list' });
254
+ });
255
+ cli.command('app launch <name>').action(() => {
256
+ notImplemented({ command: 'app launch' });
257
+ });
258
+ cli.command('app quit <name>').action(() => {
259
+ notImplemented({ command: 'app quit' });
260
+ });
261
+ cli.command('wait <target>').action(() => {
262
+ notImplemented({ command: 'wait' });
263
+ });
264
+ cli.command('find <query>').action(() => {
265
+ notImplemented({ command: 'find' });
266
+ });
267
+ cli.command('diff snapshot').action(() => {
268
+ notImplemented({ command: 'diff snapshot' });
269
+ });
270
+ cli.command('diff screenshot').action(() => {
271
+ notImplemented({ command: 'diff screenshot' });
272
+ });
273
+ cli.help();
274
+ cli.version(packageJson.version);
275
+ return cli;
276
+ }
277
+ export function runCli() {
278
+ const cli = createCli();
279
+ cli.parse();
280
+ }
281
+ const isDirectEntrypoint = (() => {
282
+ const argvPath = process.argv[1];
283
+ if (!argvPath) {
284
+ return false;
285
+ }
286
+ return import.meta.url === url.pathToFileURL(argvPath).href;
287
+ })();
288
+ if (isDirectEntrypoint) {
289
+ runCli();
290
+ }