usecomputer 0.0.2 → 0.0.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 (59) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +338 -0
  3. package/build.zig +1 -0
  4. package/dist/bridge-contract.test.js +124 -63
  5. package/dist/bridge.d.ts.map +1 -1
  6. package/dist/bridge.js +241 -46
  7. package/dist/cli-parsing.test.js +34 -11
  8. package/dist/cli.d.ts.map +1 -1
  9. package/dist/cli.js +328 -22
  10. package/dist/coord-map.d.ts +14 -0
  11. package/dist/coord-map.d.ts.map +1 -0
  12. package/dist/coord-map.js +75 -0
  13. package/dist/coord-map.test.d.ts +2 -0
  14. package/dist/coord-map.test.d.ts.map +1 -0
  15. package/dist/coord-map.test.js +157 -0
  16. package/dist/darwin-arm64/usecomputer.node +0 -0
  17. package/dist/darwin-x64/usecomputer.node +0 -0
  18. package/dist/debug-point-image.d.ts +8 -0
  19. package/dist/debug-point-image.d.ts.map +1 -0
  20. package/dist/debug-point-image.js +43 -0
  21. package/dist/debug-point-image.test.d.ts +2 -0
  22. package/dist/debug-point-image.test.d.ts.map +1 -0
  23. package/dist/debug-point-image.test.js +44 -0
  24. package/dist/index.d.ts +2 -0
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +3 -1
  27. package/dist/lib.d.ts +26 -0
  28. package/dist/lib.d.ts.map +1 -0
  29. package/dist/lib.js +88 -0
  30. package/dist/native-click-smoke.test.js +69 -29
  31. package/dist/native-lib.d.ts +59 -1
  32. package/dist/native-lib.d.ts.map +1 -1
  33. package/dist/terminal-table.d.ts +10 -0
  34. package/dist/terminal-table.d.ts.map +1 -0
  35. package/dist/terminal-table.js +55 -0
  36. package/dist/terminal-table.test.d.ts +2 -0
  37. package/dist/terminal-table.test.d.ts.map +1 -0
  38. package/dist/terminal-table.test.js +41 -0
  39. package/dist/types.d.ts +45 -0
  40. package/dist/types.d.ts.map +1 -1
  41. package/package.json +16 -4
  42. package/src/bridge-contract.test.ts +140 -69
  43. package/src/bridge.ts +293 -53
  44. package/src/cli-parsing.test.ts +61 -0
  45. package/src/cli.ts +401 -25
  46. package/src/coord-map.test.ts +178 -0
  47. package/src/coord-map.ts +105 -0
  48. package/src/debug-point-image.test.ts +50 -0
  49. package/src/debug-point-image.ts +69 -0
  50. package/src/index.ts +3 -1
  51. package/src/lib.ts +125 -0
  52. package/src/native-click-smoke.test.ts +81 -63
  53. package/src/native-lib.ts +39 -1
  54. package/src/terminal-table.test.ts +44 -0
  55. package/src/terminal-table.ts +88 -0
  56. package/src/types.ts +50 -0
  57. package/zig/src/lib.zig +1280 -163
  58. package/zig/src/scroll.zig +213 -0
  59. package/zig/src/window.zig +123 -0
@@ -0,0 +1,55 @@
1
+ // Generic aligned terminal table renderer for CLI command output.
2
+ export function renderAlignedTable({ rows, columns, }) {
3
+ if (columns.length === 0) {
4
+ return [];
5
+ }
6
+ const widthByColumn = columns.map((column) => {
7
+ const rowWidth = rows.reduce((maxWidth, row) => {
8
+ const width = printableWidth(column.value(row));
9
+ return Math.max(maxWidth, width);
10
+ }, 0);
11
+ return Math.max(printableWidth(column.header), rowWidth);
12
+ });
13
+ const formatCell = ({ value, width, align, }) => {
14
+ const currentWidth = printableWidth(value);
15
+ const padSize = Math.max(0, width - currentWidth);
16
+ const padding = ' '.repeat(padSize);
17
+ if (align === 'right') {
18
+ return `${padding}${value}`;
19
+ }
20
+ return `${value}${padding}`;
21
+ };
22
+ const renderRow = ({ values, }) => {
23
+ return values.map((value, index) => {
24
+ const column = columns[index];
25
+ if (!column) {
26
+ return value;
27
+ }
28
+ return formatCell({
29
+ value,
30
+ width: widthByColumn[index] ?? value.length,
31
+ align: column.align ?? 'left',
32
+ });
33
+ }).join(' ');
34
+ };
35
+ const header = renderRow({
36
+ values: columns.map((column) => {
37
+ return column.header;
38
+ }),
39
+ });
40
+ const divider = widthByColumn.map((width) => {
41
+ return '-'.repeat(width);
42
+ }).join(' ');
43
+ const lines = rows.map((row) => {
44
+ return renderRow({
45
+ values: columns.map((column) => {
46
+ return column.value(row);
47
+ }),
48
+ });
49
+ });
50
+ return [header, divider, ...lines];
51
+ }
52
+ function printableWidth(value) {
53
+ const ansiStripped = value.replace(/\u001b\[[0-9;]*m/g, '');
54
+ return ansiStripped.length;
55
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=terminal-table.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminal-table.test.d.ts","sourceRoot":"","sources":["../src/terminal-table.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,41 @@
1
+ // Tests aligned terminal table formatting for deterministic CLI rendering.
2
+ import { describe, expect, test } from 'vitest';
3
+ import { renderAlignedTable } from './terminal-table.js';
4
+ describe('terminal table', () => {
5
+ test('renders aligned columns for mixed widths', () => {
6
+ const lines = renderAlignedTable({
7
+ rows: [
8
+ { id: 2, app: 'Zed', size: '1720x1440' },
9
+ { id: 102, app: 'Google Chrome', size: '3440x1440' },
10
+ ],
11
+ columns: [
12
+ {
13
+ header: 'id',
14
+ align: 'right',
15
+ value: (row) => {
16
+ return String(row.id);
17
+ },
18
+ },
19
+ {
20
+ header: 'app',
21
+ value: (row) => {
22
+ return row.app;
23
+ },
24
+ },
25
+ {
26
+ header: 'size',
27
+ align: 'right',
28
+ value: (row) => {
29
+ return row.size;
30
+ },
31
+ },
32
+ ],
33
+ });
34
+ expect(lines.join('\n')).toMatchInlineSnapshot(`
35
+ " id app size
36
+ --- ------------- ---------
37
+ 2 Zed 1720x1440
38
+ 102 Google Chrome 3440x1440"
39
+ `);
40
+ });
41
+ });
package/dist/types.d.ts CHANGED
@@ -10,8 +10,17 @@ export type Region = {
10
10
  width: number;
11
11
  height: number;
12
12
  };
13
+ export type CoordMap = {
14
+ captureX: number;
15
+ captureY: number;
16
+ captureWidth: number;
17
+ captureHeight: number;
18
+ imageWidth: number;
19
+ imageHeight: number;
20
+ };
13
21
  export type DisplayInfo = {
14
22
  id: number;
23
+ index: number;
15
24
  name: string;
16
25
  x: number;
17
26
  y: number;
@@ -20,14 +29,35 @@ export type DisplayInfo = {
20
29
  scale: number;
21
30
  isPrimary: boolean;
22
31
  };
32
+ export type WindowInfo = {
33
+ id: number;
34
+ ownerPid: number;
35
+ ownerName: string;
36
+ title: string;
37
+ x: number;
38
+ y: number;
39
+ width: number;
40
+ height: number;
41
+ desktopIndex: number;
42
+ };
23
43
  export type ScreenshotInput = {
24
44
  path?: string;
25
45
  display?: number;
46
+ window?: number;
26
47
  region?: Region;
27
48
  annotate?: boolean;
28
49
  };
29
50
  export type ScreenshotResult = {
30
51
  path: string;
52
+ desktopIndex: number;
53
+ captureX: number;
54
+ captureY: number;
55
+ captureWidth: number;
56
+ captureHeight: number;
57
+ imageWidth: number;
58
+ imageHeight: number;
59
+ coordMap: string;
60
+ hint: string;
31
61
  };
32
62
  export type ClickInput = {
33
63
  point: Point;
@@ -55,6 +85,20 @@ export type DragInput = {
55
85
  durationMs?: number;
56
86
  button: MouseButton;
57
87
  };
88
+ export type NativeErrorObject = {
89
+ code: string;
90
+ message: string;
91
+ command: string;
92
+ };
93
+ export type NativeCommandResult = {
94
+ ok: boolean;
95
+ error?: NativeErrorObject;
96
+ };
97
+ export type NativeDataResult<T> = {
98
+ ok: boolean;
99
+ data?: T;
100
+ error?: NativeErrorObject;
101
+ };
58
102
  export interface UseComputerBridge {
59
103
  screenshot(input: ScreenshotInput): Promise<ScreenshotResult>;
60
104
  click(input: ClickInput): Promise<void>;
@@ -72,6 +116,7 @@ export interface UseComputerBridge {
72
116
  }): Promise<void>;
73
117
  mousePosition(): Promise<Point>;
74
118
  displayList(): Promise<DisplayInfo[]>;
119
+ windowList(): Promise<WindowInfo[]>;
75
120
  clipboardGet(): Promise<string>;
76
121
  clipboardSet(input: {
77
122
  text: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAA;AAErD,MAAM,MAAM,eAAe,GAAG,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;AAE9D,MAAM,MAAM,KAAK,GAAG;IAClB,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;CACV,CAAA;AAED,MAAM,MAAM,MAAM,GAAG;IACnB,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACf,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,OAAO,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,EAAE,KAAK,CAAA;IACZ,MAAM,EAAE,WAAW,CAAA;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,EAAE,CAAA;CACpB,CAAA;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,SAAS,EAAE,eAAe,CAAA;IAC1B,MAAM,EAAE,MAAM,CAAA;IACd,EAAE,CAAC,EAAE,KAAK,CAAA;CACX,CAAA;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,KAAK,CAAA;IACX,EAAE,EAAE,KAAK,CAAA;IACT,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,WAAW,CAAA;CACpB,CAAA;AAED,MAAM,WAAW,iBAAiB;IAChC,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAA;IAC7D,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACvC,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACzC,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACvC,MAAM,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACzC,IAAI,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACrC,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAClC,SAAS,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACtC,SAAS,CAAC,KAAK,EAAE;QAAE,MAAM,EAAE,WAAW,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACxD,OAAO,CAAC,KAAK,EAAE;QAAE,MAAM,EAAE,WAAW,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACtD,aAAa,IAAI,OAAO,CAAC,KAAK,CAAC,CAAA;IAC/B,WAAW,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC,CAAA;IACrC,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC,CAAA;IAC/B,YAAY,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACrD"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usecomputer",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "type": "module",
5
5
  "description": "Fast computer automation CLI for AI agents. Control any desktop with accessibility snapshots, clicks, typing, scrolling, and more.",
6
6
  "bin": "./bin.js",
@@ -12,6 +12,14 @@
12
12
  "types": "./dist/index.d.ts",
13
13
  "default": "./dist/index.js"
14
14
  },
15
+ "./lib": {
16
+ "types": "./dist/lib.d.ts",
17
+ "default": "./dist/lib.js"
18
+ },
19
+ "./coord-map": {
20
+ "types": "./dist/coord-map.d.ts",
21
+ "default": "./dist/coord-map.js"
22
+ },
15
23
  "./src": {
16
24
  "types": "./src/index.ts",
17
25
  "default": "./src/index.ts"
@@ -47,18 +55,19 @@
47
55
  "license": "MIT",
48
56
  "repository": {
49
57
  "type": "git",
50
- "url": "git+https://github.com/remorses/kimakivoice.git",
58
+ "url": "git+https://github.com/remorses/kimaki.git",
51
59
  "directory": "usecomputer"
52
60
  },
53
- "homepage": "https://github.com/remorses/kimakivoice/tree/main/usecomputer",
61
+ "homepage": "https://github.com/remorses/kimaki/tree/main/usecomputer",
54
62
  "bugs": {
55
- "url": "https://github.com/remorses/kimakivoice/issues"
63
+ "url": "https://github.com/remorses/kimaki/issues"
56
64
  },
57
65
  "os": [
58
66
  "darwin"
59
67
  ],
60
68
  "dependencies": {
61
69
  "goke": "^6.3.0",
70
+ "picocolors": "^1.1.1",
62
71
  "string-dedent": "^3.0.1",
63
72
  "zod": "^4.3.6"
64
73
  },
@@ -68,6 +77,9 @@
68
77
  "typescript": "^5.8.3",
69
78
  "vitest": "^4.0.18"
70
79
  },
80
+ "optionalDependencies": {
81
+ "sharp": "^0.34.5"
82
+ },
71
83
  "scripts": {
72
84
  "build": "tsc && chmod +x bin.js",
73
85
  "build:zig": "zig build",
@@ -1,85 +1,156 @@
1
- // Contract tests for command names and payloads emitted to the native bridge.
1
+ // Contract tests for direct native method calls emitted by the TS bridge.
2
+ // These tests intentionally call the real Zig native module.
2
3
 
3
4
  import { describe, expect, test } from 'vitest'
4
5
  import { createBridgeFromNative } from './bridge.js'
5
- import type { NativeModule } from './native-lib.js'
6
+ import { native } from './native-lib.js'
6
7
 
7
- type Call = {
8
- command: string
9
- payload: unknown
10
- }
8
+ describe('native bridge contract', () => {
9
+ test('bridge calls hit real Zig module', async () => {
10
+ expect(native).toBeTruthy()
11
+ if (!native) {
12
+ return
13
+ }
11
14
 
12
- function createFakeNative({ calls }: { calls: Call[] }): NativeModule {
13
- return {
14
- execute(command: string, payloadJson: string): string {
15
- const payload = JSON.parse(payloadJson) as unknown
16
- calls.push({ command, payload })
17
- if (command === 'mouse-position') {
18
- return JSON.stringify({ ok: true, data: { x: 10, y: 20 } })
19
- }
20
- if (command === 'display-list') {
21
- return JSON.stringify({
22
- ok: true,
23
- data: [
24
- { id: 1, name: 'Built-in', x: 0, y: 0, width: 1512, height: 982, scale: 2, isPrimary: true },
25
- ],
26
- })
27
- }
28
- if (command === 'clipboard-get') {
29
- return JSON.stringify({ ok: true, data: { text: 'hello' } })
30
- }
31
- if (command === 'screenshot') {
32
- return JSON.stringify({ ok: true, data: { path: '/tmp/test.png' } })
33
- }
34
- return JSON.stringify({ ok: true, data: null })
35
- },
36
- }
37
- }
15
+ const bridge = createBridgeFromNative({ nativeModule: native })
38
16
 
39
- describe('native bridge contract', () => {
40
- test('maps high-level calls to native commands', async () => {
41
- const calls: Call[] = []
42
- const bridge = createBridgeFromNative({ nativeModule: createFakeNative({ calls }) })
17
+ const safeTarget = {
18
+ x: 0,
19
+ y: 0,
20
+ }
43
21
 
44
22
  await bridge.click({
45
- point: { x: 100, y: 200 },
23
+ point: safeTarget,
46
24
  button: 'left',
47
- count: 2,
48
- modifiers: ['cmd'],
25
+ count: 1,
26
+ modifiers: [],
49
27
  })
50
- await bridge.typeText({ text: 'hello', delayMs: 30 })
51
- await bridge.press({ key: 'enter', count: 1 })
52
- await bridge.scroll({ direction: 'down', amount: 300 })
53
- await bridge.drag({ from: { x: 10, y: 10 }, to: { x: 200, y: 120 }, button: 'left' })
54
- await bridge.hover({ x: 33, y: 44 })
55
- await bridge.mouseMove({ x: 60, y: 70 })
28
+ await bridge.hover(safeTarget)
29
+ await bridge.mouseMove(safeTarget)
56
30
  await bridge.mouseDown({ button: 'left' })
57
31
  await bridge.mouseUp({ button: 'left' })
58
- await bridge.mousePosition()
59
- await bridge.displayList()
60
- await bridge.clipboardGet()
61
- await bridge.clipboardSet({ text: 'copied' })
62
- await bridge.screenshot({ path: './out.png' })
32
+ await bridge.drag({
33
+ from: safeTarget,
34
+ to: { x: safeTarget.x + 6, y: safeTarget.y + 6 },
35
+ button: 'left',
36
+ durationMs: 10,
37
+ })
38
+
39
+ const screenshot = await bridge.screenshot({ path: `${process.cwd()}/tmp/bridge-contract-shot.png` })
40
+
41
+ await bridge.typeText({ text: 'h', delayMs: 30 })
42
+ await bridge.press({ key: 'backspace', count: 1 })
43
+ const scrollResult = await bridge.scroll({ direction: 'down', amount: 1 }).then(
44
+ () => {
45
+ return 'ok'
46
+ },
47
+ (error: unknown) => {
48
+ return error instanceof Error ? error.message : String(error)
49
+ },
50
+ )
51
+ const scrollAtResult = await bridge.scroll({ direction: 'right', amount: 1, at: safeTarget }).then(
52
+ () => {
53
+ return 'ok'
54
+ },
55
+ (error: unknown) => {
56
+ return error instanceof Error ? error.message : String(error)
57
+ },
58
+ )
59
+ const displays = await bridge.displayList()
60
+ const windows = await bridge.windowList()
61
+ const clipboardGetResult = await bridge.clipboardGet().then(
62
+ () => {
63
+ return 'ok'
64
+ },
65
+ (error: unknown) => {
66
+ return error instanceof Error ? error.message : String(error)
67
+ },
68
+ )
69
+ const clipboardSetResult = await bridge.clipboardSet({ text: 'copied' }).then(
70
+ () => {
71
+ return 'ok'
72
+ },
73
+ (error: unknown) => {
74
+ return error instanceof Error ? error.message : String(error)
75
+ },
76
+ )
77
+ const isOkOrTodo = ({ value }: { value: string }): boolean => {
78
+ return value === 'ok' || value.includes('TODO not implemented')
79
+ }
63
80
 
64
- expect(calls.map((call) => {
65
- return call.command
66
- })).toMatchInlineSnapshot(`
67
- [
68
- "click",
69
- "type-text",
70
- "press",
71
- "scroll",
72
- "drag",
73
- "hover",
74
- "mouse-move",
75
- "mouse-down",
76
- "mouse-up",
77
- "mouse-position",
78
- "display-list",
79
- "clipboard-get",
80
- "clipboard-set",
81
- "screenshot",
82
- ]
81
+ expect({
82
+ screenshotShape: {
83
+ path: screenshot.path,
84
+ desktopIndex: typeof screenshot.desktopIndex,
85
+ captureX: typeof screenshot.captureX,
86
+ captureY: typeof screenshot.captureY,
87
+ captureWidth: screenshot.captureWidth > 0,
88
+ captureHeight: screenshot.captureHeight > 0,
89
+ imageWidth: screenshot.imageWidth > 0,
90
+ imageHeight: screenshot.imageHeight > 0,
91
+ coordMapHasSixValues: screenshot.coordMap.split(',').length === 6,
92
+ hint: screenshot.hint,
93
+ },
94
+ firstDisplayShape: displays[0]
95
+ ? {
96
+ id: typeof displays[0].id,
97
+ index: typeof displays[0].index,
98
+ width: displays[0].width > 0,
99
+ height: displays[0].height > 0,
100
+ }
101
+ : null,
102
+ firstWindowShape: windows[0]
103
+ ? {
104
+ id: typeof windows[0].id,
105
+ ownerName: typeof windows[0].ownerName,
106
+ desktopIndex: typeof windows[0].desktopIndex,
107
+ }
108
+ : null,
109
+ optionalCommandOutcomes: {
110
+ scrollResult: isOkOrTodo({ value: scrollResult }),
111
+ scrollAtResult: isOkOrTodo({ value: scrollAtResult }),
112
+ clipboardGetResult: isOkOrTodo({ value: clipboardGetResult }),
113
+ clipboardSetResult: isOkOrTodo({ value: clipboardSetResult }),
114
+ },
115
+ }).toMatchInlineSnapshot(`
116
+ {
117
+ "firstDisplayShape": {
118
+ "height": true,
119
+ "id": "number",
120
+ "index": "number",
121
+ "width": true,
122
+ },
123
+ "firstWindowShape": {
124
+ "desktopIndex": "number",
125
+ "id": "number",
126
+ "ownerName": "string",
127
+ },
128
+ "optionalCommandOutcomes": {
129
+ "clipboardGetResult": true,
130
+ "clipboardSetResult": true,
131
+ "scrollAtResult": true,
132
+ "scrollResult": true,
133
+ },
134
+ "screenshotShape": {
135
+ "captureHeight": true,
136
+ "captureWidth": true,
137
+ "captureX": "number",
138
+ "captureY": "number",
139
+ "coordMapHasSixValues": true,
140
+ "desktopIndex": "number",
141
+ "hint": "ALWAYS pass this exact coord map to click, hover, drag, and mouse move when using coordinates from this screenshot:
142
+ --coord-map "0,0,3440,1440,1568,656"
143
+
144
+ Example:
145
+ usecomputer click -x 400 -y 220 --coord-map "0,0,3440,1440,1568,656"",
146
+ "imageHeight": true,
147
+ "imageWidth": true,
148
+ "path": "/Users/morse/Documents/GitHub/kimakivoice/usecomputer/tmp/bridge-contract-shot.png",
149
+ },
150
+ }
83
151
  `)
152
+
153
+ expect(displays.length).toBeGreaterThan(0)
154
+ expect(windows.length).toBeGreaterThan(0)
84
155
  })
85
156
  })