usecomputer 0.1.2 → 0.1.4

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 (52) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +33 -13
  3. package/bin.sh +49 -0
  4. package/build.zig +12 -0
  5. package/dist/darwin-arm64/usecomputer +0 -0
  6. package/dist/darwin-arm64/usecomputer.node +0 -0
  7. package/dist/darwin-x64/usecomputer +0 -0
  8. package/dist/darwin-x64/usecomputer.node +0 -0
  9. package/dist/index.d.ts +0 -2
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +1 -3
  12. package/dist/linux-x64/usecomputer +0 -0
  13. package/dist/linux-x64/usecomputer.node +0 -0
  14. package/package.json +7 -14
  15. package/src/index.ts +1 -3
  16. package/zig/src/kitty-graphics.zig +151 -0
  17. package/zig/src/lib.zig +121 -0
  18. package/zig/src/main.zig +667 -47
  19. package/zig/src/table.zig +170 -0
  20. package/bin.js +0 -4
  21. package/dist/cli-parsing.test.d.ts +0 -2
  22. package/dist/cli-parsing.test.d.ts.map +0 -1
  23. package/dist/cli-parsing.test.js +0 -53
  24. package/dist/cli.d.ts +0 -6
  25. package/dist/cli.d.ts.map +0 -1
  26. package/dist/cli.js +0 -536
  27. package/dist/command-parsers.d.ts +0 -6
  28. package/dist/command-parsers.d.ts.map +0 -1
  29. package/dist/command-parsers.js +0 -54
  30. package/dist/command-parsers.test.d.ts +0 -2
  31. package/dist/command-parsers.test.d.ts.map +0 -1
  32. package/dist/command-parsers.test.js +0 -44
  33. package/dist/debug-point-image.d.ts +0 -8
  34. package/dist/debug-point-image.d.ts.map +0 -1
  35. package/dist/debug-point-image.js +0 -43
  36. package/dist/debug-point-image.test.d.ts +0 -2
  37. package/dist/debug-point-image.test.d.ts.map +0 -1
  38. package/dist/debug-point-image.test.js +0 -44
  39. package/dist/terminal-table.d.ts +0 -10
  40. package/dist/terminal-table.d.ts.map +0 -1
  41. package/dist/terminal-table.js +0 -55
  42. package/dist/terminal-table.test.d.ts +0 -2
  43. package/dist/terminal-table.test.d.ts.map +0 -1
  44. package/dist/terminal-table.test.js +0 -41
  45. package/src/cli-parsing.test.ts +0 -61
  46. package/src/cli.ts +0 -648
  47. package/src/command-parsers.test.ts +0 -50
  48. package/src/command-parsers.ts +0 -60
  49. package/src/debug-point-image.test.ts +0 -50
  50. package/src/debug-point-image.ts +0 -69
  51. package/src/terminal-table.test.ts +0 -44
  52. package/src/terminal-table.ts +0 -88
package/dist/cli.js DELETED
@@ -1,536 +0,0 @@
1
- // usecomputer CLI entrypoint and command wiring for desktop automation actions.
2
- import { goke } from 'goke';
3
- import pc from 'picocolors';
4
- import { z } from 'zod';
5
- import dedent from 'string-dedent';
6
- import { createRequire } from 'node:module';
7
- import fs from 'node:fs';
8
- import pathModule from 'node:path';
9
- import url from 'node:url';
10
- import { createBridge } from './bridge.js';
11
- import { getRegionFromCoordMap, mapPointFromCoordMap, mapPointToCoordMap, parseCoordMapOrThrow, } from './coord-map.js';
12
- import { parseDirection, parseModifiers, parsePoint, parseRegion } from './command-parsers.js';
13
- import { drawDebugPointOnImage } from './debug-point-image.js';
14
- import { renderAlignedTable } from './terminal-table.js';
15
- const require = createRequire(import.meta.url);
16
- const packageJson = require('../package.json');
17
- function printJson(value) {
18
- process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
19
- }
20
- function printLine(value) {
21
- process.stdout.write(`${value}\n`);
22
- }
23
- function readTextFromStdin() {
24
- return fs.readFileSync(0, 'utf8');
25
- }
26
- function parsePositiveInteger({ value, option, }) {
27
- if (typeof value !== 'number') {
28
- return undefined;
29
- }
30
- if (!Number.isFinite(value) || value <= 0) {
31
- throw new Error(`Option ${option} must be a positive number`);
32
- }
33
- return Math.round(value);
34
- }
35
- function splitIntoChunks({ text, chunkSize, }) {
36
- if (!chunkSize || text.length <= chunkSize) {
37
- return [text];
38
- }
39
- const chunkCount = Math.ceil(text.length / chunkSize);
40
- return Array.from({ length: chunkCount }, (_, index) => {
41
- const start = index * chunkSize;
42
- const end = start + chunkSize;
43
- return text.slice(start, end);
44
- }).filter((chunk) => {
45
- return chunk.length > 0;
46
- });
47
- }
48
- function sleep({ ms, }) {
49
- return new Promise((resolve) => {
50
- setTimeout(() => {
51
- resolve();
52
- }, ms);
53
- });
54
- }
55
- function parsePointOrThrow(input) {
56
- const parsed = parsePoint(input);
57
- if (parsed instanceof Error) {
58
- throw parsed;
59
- }
60
- return parsed;
61
- }
62
- function resolveOutputPath({ path }) {
63
- if (!path) {
64
- return undefined;
65
- }
66
- return path.startsWith('/')
67
- ? path
68
- : `${process.cwd()}/${path}`;
69
- }
70
- function ensureParentDirectory({ filePath }) {
71
- if (!filePath) {
72
- return;
73
- }
74
- const parentDirectory = pathModule.dirname(filePath);
75
- fs.mkdirSync(parentDirectory, { recursive: true });
76
- }
77
- function resolvePointInput({ x, y, target, command, }) {
78
- if (typeof x === 'number' || typeof y === 'number') {
79
- if (typeof x !== 'number' || typeof y !== 'number') {
80
- throw new Error(`Command \"${command}\" requires both -x and -y when using coordinate flags`);
81
- }
82
- return { x, y };
83
- }
84
- if (target) {
85
- return parsePointOrThrow(target);
86
- }
87
- throw new Error(`Command \"${command}\" requires coordinates. Use -x <n> -y <n>`);
88
- }
89
- function parseButton(input) {
90
- if (input === 'right' || input === 'middle') {
91
- return input;
92
- }
93
- return 'left';
94
- }
95
- function printDesktopList({ displays }) {
96
- const rows = displays.map((display) => {
97
- return {
98
- desktop: `#${display.index}`,
99
- primary: display.isPrimary ? pc.green('yes') : 'no',
100
- size: `${display.width}x${display.height}`,
101
- position: `${display.x},${display.y}`,
102
- id: String(display.id),
103
- scale: String(display.scale),
104
- name: display.name,
105
- };
106
- });
107
- const lines = renderAlignedTable({
108
- rows,
109
- columns: [
110
- { header: pc.bold('desktop'), value: (row) => { return row.desktop; } },
111
- { header: pc.bold('primary'), value: (row) => { return row.primary; } },
112
- { header: pc.bold('size'), value: (row) => { return row.size; }, align: 'right' },
113
- { header: pc.bold('position'), value: (row) => { return row.position; }, align: 'right' },
114
- { header: pc.bold('id'), value: (row) => { return row.id; }, align: 'right' },
115
- { header: pc.bold('scale'), value: (row) => { return row.scale; }, align: 'right' },
116
- { header: pc.bold('name'), value: (row) => { return row.name; } },
117
- ],
118
- });
119
- lines.forEach((line) => {
120
- printLine(line);
121
- });
122
- }
123
- function mapWindowsByDesktopIndex({ windows, }) {
124
- return windows.reduce((acc, window) => {
125
- const list = acc.get(window.desktopIndex) ?? [];
126
- list.push(window);
127
- acc.set(window.desktopIndex, list);
128
- return acc;
129
- }, new Map());
130
- }
131
- function printDesktopListWithWindows({ displays, windows, }) {
132
- const windowsByDesktop = mapWindowsByDesktopIndex({ windows });
133
- printDesktopList({ displays });
134
- displays.forEach((display) => {
135
- printLine('');
136
- printLine(pc.bold(pc.cyan(`desktop #${display.index} windows`)));
137
- const desktopWindows = windowsByDesktop.get(display.index) ?? [];
138
- if (desktopWindows.length === 0) {
139
- printLine(pc.dim('none'));
140
- return;
141
- }
142
- const lines = renderAlignedTable({
143
- rows: desktopWindows,
144
- columns: [
145
- { header: pc.bold('id'), value: (row) => { return String(row.id); }, align: 'right' },
146
- { header: pc.bold('app'), value: (row) => { return row.ownerName; } },
147
- { header: pc.bold('pid'), value: (row) => { return String(row.ownerPid); }, align: 'right' },
148
- { header: pc.bold('size'), value: (row) => { return `${row.width}x${row.height}`; }, align: 'right' },
149
- { header: pc.bold('position'), value: (row) => { return `${row.x},${row.y}`; }, align: 'right' },
150
- { header: pc.bold('title'), value: (row) => { return row.title; } },
151
- ],
152
- });
153
- lines.forEach((line) => {
154
- printLine(line);
155
- });
156
- });
157
- }
158
- function printWindowList({ windows }) {
159
- const lines = renderAlignedTable({
160
- rows: windows,
161
- columns: [
162
- { header: pc.bold('id'), value: (row) => { return String(row.id); }, align: 'right' },
163
- { header: pc.bold('desktop'), value: (row) => { return `#${row.desktopIndex}`; }, align: 'right' },
164
- { header: pc.bold('app'), value: (row) => { return row.ownerName; } },
165
- { header: pc.bold('pid'), value: (row) => { return String(row.ownerPid); }, align: 'right' },
166
- { header: pc.bold('size'), value: (row) => { return `${row.width}x${row.height}`; }, align: 'right' },
167
- { header: pc.bold('position'), value: (row) => { return `${row.x},${row.y}`; }, align: 'right' },
168
- { header: pc.bold('title'), value: (row) => { return row.title; } },
169
- ],
170
- });
171
- lines.forEach((line) => {
172
- printLine(line);
173
- });
174
- }
175
- export function createCli({ bridge = createBridge() } = {}) {
176
- const cli = goke('usecomputer');
177
- cli
178
- .command('screenshot [path]', dedent `
179
- Take a screenshot of the entire screen or a region.
180
-
181
- This command uses a native Zig backend over macOS APIs.
182
- `)
183
- .option('-r, --region [region]', z.string().describe('Capture region as x,y,width,height'))
184
- .option('--display [display]', z.number().describe('Display index for multi-monitor setups (0-based: first display is index 0)'))
185
- .option('--window [window]', z.number().describe('Capture a specific window by window id'))
186
- .option('--annotate', 'Annotate screenshot with labels')
187
- .option('--json', 'Output as JSON')
188
- .action(async (path, options) => {
189
- const outputPath = resolveOutputPath({ path });
190
- ensureParentDirectory({ filePath: outputPath });
191
- const region = options.region ? parseRegion(options.region) : undefined;
192
- if (region instanceof Error) {
193
- throw region;
194
- }
195
- if (typeof options.window === 'number' && region) {
196
- throw new Error('Cannot use --window and --region together');
197
- }
198
- if (typeof options.window === 'number' && typeof options.display === 'number') {
199
- throw new Error('Cannot use --window and --display together');
200
- }
201
- const result = await bridge.screenshot({
202
- path: outputPath,
203
- region,
204
- display: options.display,
205
- window: options.window,
206
- annotate: options.annotate,
207
- });
208
- if (options.json) {
209
- printJson(result);
210
- return;
211
- }
212
- printLine(result.path);
213
- printLine(result.hint);
214
- printLine(`desktop-index=${String(result.desktopIndex)}`);
215
- });
216
- cli
217
- .command('click [target]', dedent `
218
- Click at coordinates.
219
-
220
- When you are clicking from a screenshot, use the exact pixel coordinates
221
- of the target in that screenshot image and always pass the exact
222
- --coord-map value printed by usecomputer screenshot. The coord map
223
- scales screenshot-space pixels back into the real captured desktop or
224
- window rectangle before sending the native click.
225
- `)
226
- .option('-x [x]', z.number().describe('X coordinate. When using --coord-map, this must be the exact pixel from the screenshot image'))
227
- .option('-y [y]', z.number().describe('Y coordinate. When using --coord-map, this must be the exact pixel from the screenshot image'))
228
- .option('--button [button]', z.enum(['left', 'right', 'middle']).default('left').describe('Mouse button'))
229
- .option('--count [count]', z.number().default(1).describe('Number of clicks'))
230
- .option('--modifiers [modifiers]', z.string().describe('Modifiers as ctrl,shift,alt,meta'))
231
- .option('--coord-map [coordMap]', z.string().describe('Map exact screenshot-space pixels back into the real captured desktop or window rectangle'))
232
- .example('# Click the exact pixel you saw in a screenshot')
233
- .example('usecomputer click -x 155 -y 446 --coord-map "0,0,1720,1440,1568,1313"')
234
- .action(async (target, options) => {
235
- const point = resolvePointInput({
236
- x: options.x,
237
- y: options.y,
238
- target,
239
- command: 'click',
240
- });
241
- const coordMap = parseCoordMapOrThrow(options.coordMap);
242
- await bridge.click({
243
- point: mapPointFromCoordMap({ point, coordMap }),
244
- button: options.button,
245
- count: options.count,
246
- modifiers: parseModifiers(options.modifiers),
247
- });
248
- });
249
- cli
250
- .command('debug-point [target]', dedent `
251
- Capture a screenshot and draw a red marker where a click would land.
252
-
253
- Pass the same --coord-map you plan to use for click. This validates
254
- screenshot-space coordinates before you send a real click. When
255
- --coord-map is present, debug-point captures that same region so the
256
- overlay matches the screenshot you are targeting.
257
- `)
258
- .option('-x [x]', z.number().describe('X coordinate'))
259
- .option('-y [y]', z.number().describe('Y coordinate'))
260
- .option('--coord-map [coordMap]', z.string().describe('Map input coordinates from screenshot space'))
261
- .option('--output [path]', z.string().describe('Write the annotated screenshot to this path'))
262
- .option('--json', 'Output as JSON')
263
- .example('# Validate the same coordinates you plan to click')
264
- .example('usecomputer debug-point -x 210 -y 560 --coord-map "0,0,1720,1440,1568,1313"')
265
- .action(async (target, options) => {
266
- const point = resolvePointInput({
267
- x: options.x,
268
- y: options.y,
269
- target,
270
- command: 'debug-point',
271
- });
272
- const inputCoordMap = parseCoordMapOrThrow(options.coordMap);
273
- const desktopPoint = mapPointFromCoordMap({ point, coordMap: inputCoordMap });
274
- const outputPath = resolveOutputPath({ path: options.output ?? './tmp/debug-point.png' });
275
- ensureParentDirectory({ filePath: outputPath });
276
- const screenshotRegion = getRegionFromCoordMap({ coordMap: inputCoordMap });
277
- const screenshot = await bridge.screenshot({
278
- path: outputPath,
279
- region: screenshotRegion,
280
- });
281
- const screenshotCoordMap = parseCoordMapOrThrow(screenshot.coordMap);
282
- const screenshotPoint = mapPointToCoordMap({ point: desktopPoint, coordMap: screenshotCoordMap });
283
- await drawDebugPointOnImage({
284
- imagePath: screenshot.path,
285
- point: screenshotPoint,
286
- imageWidth: screenshot.imageWidth,
287
- imageHeight: screenshot.imageHeight,
288
- });
289
- if (options.json) {
290
- printJson({
291
- path: screenshot.path,
292
- inputPoint: point,
293
- desktopPoint,
294
- screenshotPoint,
295
- inputCoordMap: options.coordMap ?? null,
296
- screenshotCoordMap: screenshot.coordMap,
297
- hint: screenshot.hint,
298
- });
299
- return;
300
- }
301
- printLine(screenshot.path);
302
- printLine(`input-point=${point.x},${point.y}`);
303
- printLine(`desktop-point=${desktopPoint.x},${desktopPoint.y}`);
304
- printLine(`screenshot-point=${screenshotPoint.x},${screenshotPoint.y}`);
305
- printLine(screenshot.hint);
306
- });
307
- cli
308
- .command('type [text]', dedent `
309
- Type text in the currently focused input.
310
-
311
- Supports direct text arguments or --stdin for long/multiline content.
312
- For very long text, use --chunk-size to split input into multiple native
313
- type calls so shells and apps are less likely to drop input.
314
- `)
315
- .option('--stdin', 'Read text from stdin instead of [text] argument')
316
- .option('--delay [delay]', z.number().describe('Delay in milliseconds between typed characters'))
317
- .option('--chunk-size [size]', z.number().describe('Split text into fixed-size chunks before typing'))
318
- .option('--chunk-delay [delay]', z.number().describe('Delay in milliseconds between chunks'))
319
- .option('--max-length [length]', z.number().describe('Fail when input text exceeds this maximum length'))
320
- .example('# Type a short string')
321
- .example('usecomputer type "hello"')
322
- .example('# Type multiline text from a file')
323
- .example('cat ./notes.txt | usecomputer type --stdin --chunk-size 4000 --chunk-delay 15')
324
- .action(async (text, options) => {
325
- const fromStdin = Boolean(options.stdin);
326
- if (fromStdin && text) {
327
- throw new Error('Use either [text] or --stdin, not both');
328
- }
329
- if (!fromStdin && !text) {
330
- throw new Error('Command "type" requires [text] or --stdin');
331
- }
332
- const sourceText = fromStdin ? readTextFromStdin() : text ?? '';
333
- const chunkSize = parsePositiveInteger({
334
- value: options.chunkSize,
335
- option: '--chunk-size',
336
- });
337
- const maxLength = parsePositiveInteger({
338
- value: options.maxLength,
339
- option: '--max-length',
340
- });
341
- const chunkDelay = parsePositiveInteger({
342
- value: options.chunkDelay,
343
- option: '--chunk-delay',
344
- });
345
- if (typeof maxLength === 'number' && sourceText.length > maxLength) {
346
- throw new Error(`Input text length ${String(sourceText.length)} exceeds --max-length ${String(maxLength)}`);
347
- }
348
- const chunks = splitIntoChunks({
349
- text: sourceText,
350
- chunkSize,
351
- });
352
- await chunks.reduce(async (previousChunk, chunk, index) => {
353
- await previousChunk;
354
- await bridge.typeText({
355
- text: chunk,
356
- delayMs: options.delay,
357
- });
358
- if (typeof chunkDelay === 'number' && index < chunks.length - 1) {
359
- await sleep({ ms: chunkDelay });
360
- }
361
- }, Promise.resolve());
362
- });
363
- cli
364
- .command('press <key>', dedent `
365
- Press a key or key combo in the focused app.
366
-
367
- Key combos use plus syntax such as cmd+s or ctrl+shift+p.
368
- Platform behavior: cmd maps to Command on macOS, Win/Super on
369
- Windows/Linux. For cross-platform app shortcuts, prefer ctrl+... .
370
- `)
371
- .option('--count [count]', z.number().default(1).describe('How many times to press'))
372
- .option('--delay [delay]', z.number().describe('Delay between presses in milliseconds'))
373
- .example('# Save in the current app on macOS')
374
- .example('usecomputer press "cmd+s"')
375
- .example('# Portable save shortcut across most apps')
376
- .example('usecomputer press "ctrl+s"')
377
- .example('# Open command palette in many editors')
378
- .example('usecomputer press "cmd+shift+p"')
379
- .action(async (key, options) => {
380
- await bridge.press({ key, count: options.count, delayMs: options.delay });
381
- });
382
- cli
383
- .command('scroll <direction> [amount]', 'Scroll in a direction')
384
- .option('--at [at]', z.string().describe('Coordinates x,y where scroll happens'))
385
- .action(async (direction, amount, options) => {
386
- const parsedDirection = parseDirection(direction);
387
- if (parsedDirection instanceof Error) {
388
- throw parsedDirection;
389
- }
390
- const at = options.at ? parsePointOrThrow(options.at) : undefined;
391
- const scrollAmount = amount ? Number(amount) : 300;
392
- if (!Number.isFinite(scrollAmount)) {
393
- throw new Error(`Invalid amount \"${amount}\"`);
394
- }
395
- await bridge.scroll({
396
- direction: parsedDirection,
397
- amount: scrollAmount,
398
- at,
399
- });
400
- });
401
- cli
402
- .command('drag <from> <to>', 'Drag from one coordinate to another')
403
- .option('--duration [duration]', z.number().describe('Duration in milliseconds'))
404
- .option('--button [button]', z.enum(['left', 'right', 'middle']).default('left').describe('Mouse button'))
405
- .option('--coord-map [coordMap]', z.string().describe('Map input coordinates from screenshot space'))
406
- .action(async (from, to, options) => {
407
- const coordMap = parseCoordMapOrThrow(options.coordMap);
408
- await bridge.drag({
409
- from: mapPointFromCoordMap({ point: parsePointOrThrow(from), coordMap }),
410
- to: mapPointFromCoordMap({ point: parsePointOrThrow(to), coordMap }),
411
- durationMs: options.duration,
412
- button: options.button,
413
- });
414
- });
415
- cli
416
- .command('hover [target]', 'Move mouse cursor to coordinates without clicking')
417
- .option('-x [x]', z.number().describe('X coordinate'))
418
- .option('-y [y]', z.number().describe('Y coordinate'))
419
- .option('--coord-map [coordMap]', z.string().describe('Map input coordinates from screenshot space'))
420
- .action(async (target, options) => {
421
- const point = resolvePointInput({
422
- x: options.x,
423
- y: options.y,
424
- target,
425
- command: 'hover',
426
- });
427
- const coordMap = parseCoordMapOrThrow(options.coordMap);
428
- await bridge.hover(mapPointFromCoordMap({ point, coordMap }));
429
- });
430
- cli
431
- .command('mouse move [x] [y]', 'Move mouse cursor to absolute coordinates (optional before click; click can target coordinates directly)')
432
- .option('-x [x]', z.number().describe('X coordinate'))
433
- .option('-y [y]', z.number().describe('Y coordinate'))
434
- .option('--coord-map [coordMap]', z.string().describe('Map input coordinates from screenshot space'))
435
- .action(async (x, y, options) => {
436
- const point = resolvePointInput({
437
- x: options.x,
438
- y: options.y,
439
- target: x && y ? `${x},${y}` : undefined,
440
- command: 'mouse move',
441
- });
442
- const coordMap = parseCoordMapOrThrow(options.coordMap);
443
- await bridge.mouseMove(mapPointFromCoordMap({ point, coordMap }));
444
- });
445
- cli
446
- .command('mouse down', 'Press and hold mouse button')
447
- .option('--button [button]', z.enum(['left', 'right', 'middle']).default('left').describe('Mouse button'))
448
- .action(async (options) => {
449
- await bridge.mouseDown({ button: parseButton(options.button) });
450
- });
451
- cli
452
- .command('mouse up', 'Release mouse button')
453
- .option('--button [button]', z.enum(['left', 'right', 'middle']).default('left').describe('Mouse button'))
454
- .action(async (options) => {
455
- await bridge.mouseUp({ button: parseButton(options.button) });
456
- });
457
- cli
458
- .command('mouse position', 'Print current mouse position as x,y')
459
- .option('--json', 'Output as JSON')
460
- .action(async (options) => {
461
- const position = await bridge.mousePosition();
462
- if (options.json) {
463
- printJson(position);
464
- return;
465
- }
466
- printLine(`${position.x},${position.y}`);
467
- });
468
- cli
469
- .command('display list', 'List connected displays')
470
- .option('--json', 'Output as JSON')
471
- .action(async (options) => {
472
- const displays = await bridge.displayList();
473
- if (options.json) {
474
- printJson(displays);
475
- return;
476
- }
477
- printDesktopList({ displays });
478
- });
479
- cli
480
- .command('desktop list', 'List desktops as display indexes and sizes (#0 is the primary display)')
481
- .option('--windows', 'Include available windows grouped by desktop index')
482
- .option('--json', 'Output as JSON')
483
- .action(async (options) => {
484
- const displays = await bridge.displayList();
485
- const windows = options.windows ? await bridge.windowList() : [];
486
- if (options.json) {
487
- if (options.windows) {
488
- printJson({ displays, windows });
489
- return;
490
- }
491
- printJson(displays);
492
- return;
493
- }
494
- if (options.windows) {
495
- printDesktopListWithWindows({ displays, windows });
496
- return;
497
- }
498
- printDesktopList({ displays });
499
- });
500
- cli
501
- .command('clipboard get', 'Print clipboard text')
502
- .action(async () => {
503
- const text = await bridge.clipboardGet();
504
- printLine(text);
505
- });
506
- cli
507
- .command('clipboard set <text>', 'Set clipboard text')
508
- .action(async (text) => {
509
- await bridge.clipboardSet({ text });
510
- });
511
- cli.command('window list').option('--json', 'Output as JSON').action(async (options) => {
512
- const windows = await bridge.windowList();
513
- if (options.json) {
514
- printJson(windows);
515
- return;
516
- }
517
- printWindowList({ windows });
518
- });
519
- cli.help();
520
- cli.version(packageJson.version);
521
- return cli;
522
- }
523
- export function runCli() {
524
- const cli = createCli();
525
- cli.parse();
526
- }
527
- const isDirectEntrypoint = (() => {
528
- const argvPath = process.argv[1];
529
- if (!argvPath) {
530
- return false;
531
- }
532
- return import.meta.url === url.pathToFileURL(argvPath).href;
533
- })();
534
- if (isDirectEntrypoint) {
535
- runCli();
536
- }
@@ -1,6 +0,0 @@
1
- import type { Point, Region, ScrollDirection } from './types.js';
2
- export declare function parsePoint(input: string): Error | Point;
3
- export declare function parseRegion(input: string): Error | Region;
4
- export declare function parseModifiers(input?: string): string[];
5
- export declare function parseDirection(input: string): Error | ScrollDirection;
6
- //# sourceMappingURL=command-parsers.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"command-parsers.d.ts","sourceRoot":"","sources":["../src/command-parsers.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAEhE,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,GAAG,KAAK,CAavD;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,CAkBzD;AAED,wBAAgB,cAAc,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAYvD;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,GAAG,eAAe,CAMrE"}
@@ -1,54 +0,0 @@
1
- // Parser helpers for CLI values such as coordinates, regions, and key modifiers.
2
- export function parsePoint(input) {
3
- const parts = input.split(',').map((value) => {
4
- return value.trim();
5
- });
6
- if (parts.length !== 2) {
7
- return new Error(`Invalid point \"${input}\". Expected x,y`);
8
- }
9
- const x = Number(parts[0]);
10
- const y = Number(parts[1]);
11
- if (!Number.isFinite(x) || !Number.isFinite(y)) {
12
- return new Error(`Invalid point \"${input}\". Coordinates must be numbers`);
13
- }
14
- return { x, y };
15
- }
16
- export function parseRegion(input) {
17
- const parts = input.split(',').map((value) => {
18
- return value.trim();
19
- });
20
- if (parts.length !== 4) {
21
- return new Error(`Invalid region \"${input}\". Expected x,y,width,height`);
22
- }
23
- const x = Number(parts[0]);
24
- const y = Number(parts[1]);
25
- const width = Number(parts[2]);
26
- const height = Number(parts[3]);
27
- if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(width) || !Number.isFinite(height)) {
28
- return new Error(`Invalid region \"${input}\". Values must be numbers`);
29
- }
30
- if (width <= 0 || height <= 0) {
31
- return new Error(`Invalid region \"${input}\". Width and height must be greater than 0`);
32
- }
33
- return { x, y, width, height };
34
- }
35
- export function parseModifiers(input) {
36
- if (!input) {
37
- return [];
38
- }
39
- return input
40
- .split(',')
41
- .map((value) => {
42
- return value.trim().toLowerCase();
43
- })
44
- .filter((value) => {
45
- return value.length > 0;
46
- });
47
- }
48
- export function parseDirection(input) {
49
- const normalized = input.trim().toLowerCase();
50
- if (normalized === 'up' || normalized === 'down' || normalized === 'left' || normalized === 'right') {
51
- return normalized;
52
- }
53
- return new Error(`Invalid direction \"${input}\". Expected up, down, left, or right`);
54
- }
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=command-parsers.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"command-parsers.test.d.ts","sourceRoot":"","sources":["../src/command-parsers.test.ts"],"names":[],"mappings":""}
@@ -1,44 +0,0 @@
1
- // Tests for parsing coordinates, regions, directions, and keyboard modifiers.
2
- import { describe, expect, test } from 'vitest';
3
- import { parseDirection, parseModifiers, parsePoint, parseRegion } from './command-parsers.js';
4
- describe('command parsers', () => {
5
- test('parses x,y points', () => {
6
- const result = parsePoint('100,200');
7
- expect(result).toMatchInlineSnapshot(`
8
- {
9
- "x": 100,
10
- "y": 200,
11
- }
12
- `);
13
- });
14
- test('rejects invalid points', () => {
15
- const result = parsePoint('100');
16
- expect(result instanceof Error).toBe(true);
17
- expect(result instanceof Error ? result.message : '').toMatchInlineSnapshot(`"Invalid point "100". Expected x,y"`);
18
- });
19
- test('parses x,y,width,height regions', () => {
20
- const result = parseRegion('10,20,300,400');
21
- expect(result).toMatchInlineSnapshot(`
22
- {
23
- "height": 400,
24
- "width": 300,
25
- "x": 10,
26
- "y": 20,
27
- }
28
- `);
29
- });
30
- test('parses modifiers with normalization', () => {
31
- expect(parseModifiers(' CMD,shift, alt ')).toMatchInlineSnapshot(`
32
- [
33
- "cmd",
34
- "shift",
35
- "alt",
36
- ]
37
- `);
38
- });
39
- test('validates scroll direction', () => {
40
- expect(parseDirection('down')).toBe('down');
41
- const invalid = parseDirection('diagonal');
42
- expect(invalid instanceof Error).toBe(true);
43
- });
44
- });
@@ -1,8 +0,0 @@
1
- import type { Point } from './types.js';
2
- export declare function drawDebugPointOnImage({ imagePath, point, imageWidth, imageHeight, }: {
3
- imagePath: string;
4
- point: Point;
5
- imageWidth: number;
6
- imageHeight: number;
7
- }): Promise<void>;
8
- //# sourceMappingURL=debug-point-image.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"debug-point-image.d.ts","sourceRoot":"","sources":["../src/debug-point-image.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AA2CvC,wBAAsB,qBAAqB,CAAC,EAC1C,SAAS,EACT,KAAK,EACL,UAAU,EACV,WAAW,GACZ,EAAE;IACD,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,KAAK,CAAA;IACZ,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,EAAE,MAAM,CAAA;CACpB,GAAG,OAAO,CAAC,IAAI,CAAC,CAUhB"}