usecomputer 0.0.4 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,73 @@
4
4
 
5
5
  All notable changes to `usecomputer` will be documented in this file.
6
6
 
7
+ ## 0.1.0
8
+
9
+ 1. **Standalone executable** — `usecomputer` now ships as a self-contained binary.
10
+ Install once and run anywhere without needing Node.js at runtime:
11
+
12
+ ```bash
13
+ npm install -g usecomputer
14
+ usecomputer screenshot ./shot.png --json
15
+ ```
16
+
17
+ 2. **Linux X11 screenshot support** — capture screens on Linux desktops via XShm
18
+ (with automatic fallback to XGetImage on XWayland). Returns the same JSON
19
+ output shape as macOS:
20
+
21
+ ```bash
22
+ usecomputer screenshot ./shot.png --json
23
+ ```
24
+
25
+ 3. **Screenshot coord-map and scaling** — screenshots are scaled so the longest edge
26
+ is at most 1568 px (model-friendly size). Output includes a `coordMap` field
27
+ for accurate pointer remapping:
28
+
29
+ ```bash
30
+ usecomputer screenshot ./shot.png --json
31
+ # use the emitted coord-map for all subsequent pointer commands
32
+ usecomputer click -x 400 -y 220 --coord-map "0,0,1600,900,1568,882"
33
+ ```
34
+
35
+ 4. **New `debug-point` command** — validate a click target before clicking. Captures
36
+ a screenshot and draws a red marker at the mapped coordinate:
37
+
38
+ ```bash
39
+ usecomputer debug-point -x 400 -y 220 --coord-map "0,0,1600,900,1568,882"
40
+ ```
41
+
42
+ 5. **Keyboard synthesis** — new `type` and `press` commands for text input and key
43
+ chords:
44
+
45
+ ```bash
46
+ usecomputer type "hello from usecomputer"
47
+ usecomputer press "cmd+s"
48
+ usecomputer press "down" --count 10 --delay 30
49
+ cat ./notes.txt | usecomputer type --stdin --chunk-size 4000
50
+ ```
51
+
52
+ 6. **Native scroll support** — scroll in any direction at any position:
53
+
54
+ ```bash
55
+ usecomputer scroll --direction down --amount 5
56
+ usecomputer scroll --direction up --amount 3 -x 800 -y 400
57
+ ```
58
+
59
+ 7. **Library exports** — import `usecomputer` as a Node.js library to reuse all
60
+ commands in your own agent harness:
61
+
62
+ ```ts
63
+ import * as usecomputer from 'usecomputer'
64
+
65
+ const shot = await usecomputer.screenshot({ path: './shot.png', display: null, window: null, region: null, annotate: null })
66
+ const coordMap = usecomputer.parseCoordMapOrThrow(shot.coordMap)
67
+ await usecomputer.click({ point: usecomputer.mapPointFromCoordMap({ point: { x: 400, y: 220 }, coordMap }), button: 'left', count: 1 })
68
+ ```
69
+
70
+ 8. **OpenAI and Anthropic computer-use examples** — README now includes full
71
+ agentic loop examples for both providers showing screenshot → action → result
72
+ cycles.
73
+
7
74
  ## 0.0.3
8
75
 
9
76
  - Implement real screenshot capture + PNG file writing on macOS.
package/build.zig CHANGED
@@ -1,36 +1,64 @@
1
- // Build script for usecomputer Zig N-API native module artifacts.
1
+ // Build script for usecomputer produces both:
2
+ // 1. Dynamic library (.node) for N-API consumption from Node.js
3
+ // 2. Standalone executable CLI (no Node.js required, uses zeke)
2
4
 
3
5
  const std = @import("std");
4
6
  const napigen = @import("napigen");
5
7
 
6
8
  const LIB_NAME = "usecomputer";
7
9
 
10
+ /// Link platform-specific libraries needed by the native core.
11
+ fn linkPlatformDeps(mod: *std.Build.Module, target_os: std.Target.Os.Tag) void {
12
+ if (target_os == .macos) {
13
+ mod.linkFramework("CoreGraphics", .{});
14
+ mod.linkFramework("CoreFoundation", .{});
15
+ mod.linkFramework("ImageIO", .{});
16
+ }
17
+ if (target_os == .linux) {
18
+ mod.linkSystemLibrary("X11", .{});
19
+ mod.linkSystemLibrary("Xext", .{});
20
+ mod.linkSystemLibrary("Xtst", .{});
21
+ mod.linkSystemLibrary("png", .{});
22
+ }
23
+ if (target_os == .windows) {
24
+ mod.linkSystemLibrary("user32", .{});
25
+ }
26
+ }
27
+
8
28
  pub fn build(b: *std.Build) void {
9
29
  const target = b.standardTargetOptions(.{});
10
30
  const optimize = b.standardOptimizeOption(.{});
31
+ const target_os = target.result.os.tag;
32
+
33
+ // ── N-API dynamic library (.node) ──
34
+
35
+ // Build options for lib.zig: enable_napigen controls N-API glue
36
+ const lib_options = b.addOptions();
37
+ lib_options.addOption(bool, "enable_napigen", true);
38
+ const lib_options_mod = lib_options.createModule();
11
39
 
12
40
  const lib_mod = b.createModule(.{
13
41
  .root_source_file = b.path("zig/src/lib.zig"),
14
42
  .target = target,
15
43
  .optimize = optimize,
16
44
  });
45
+ lib_mod.addImport("build_options", lib_options_mod);
17
46
  lib_mod.addImport("napigen", b.dependency("napigen", .{}).module("napigen"));
18
- lib_mod.addImport("objc", b.dependency("zig_objc", .{
19
- .target = target,
20
- .optimize = optimize,
21
- }).module("objc"));
47
+ if (target_os == .macos) {
48
+ if (b.lazyDependency("zig_objc", .{
49
+ .target = target,
50
+ .optimize = optimize,
51
+ })) |dep| {
52
+ lib_mod.addImport("objc", dep.module("objc"));
53
+ }
54
+ }
22
55
 
23
56
  const lib = b.addLibrary(.{
24
57
  .name = LIB_NAME,
25
58
  .root_module = lib_mod,
26
59
  .linkage = .dynamic,
27
60
  });
28
-
29
- if (target.result.os.tag == .macos) {
30
- lib.root_module.linkFramework("CoreGraphics", .{});
31
- lib.root_module.linkFramework("CoreFoundation", .{});
32
- lib.root_module.linkFramework("ImageIO", .{});
33
- }
61
+ linkPlatformDeps(lib.root_module, target_os);
34
62
 
35
63
  napigen.setup(lib);
36
64
  b.installArtifact(lib);
@@ -38,16 +66,72 @@ pub fn build(b: *std.Build) void {
38
66
  const copy_node_step = b.addInstallLibFile(lib.getEmittedBin(), LIB_NAME ++ ".node");
39
67
  b.getInstallStep().dependOn(&copy_node_step.step);
40
68
 
69
+ // ── Standalone executable CLI ──
70
+ //
71
+ // Uses a separate copy of lib.zig WITHOUT napigen so the executable
72
+ // doesn't try to link N-API symbols (those only exist in Node.js).
73
+
74
+ const exe_options = b.addOptions();
75
+ exe_options.addOption(bool, "enable_napigen", false);
76
+ const exe_options_mod = exe_options.createModule();
77
+
78
+ const exe_lib_mod = b.createModule(.{
79
+ .root_source_file = b.path("zig/src/lib.zig"),
80
+ .target = target,
81
+ .optimize = optimize,
82
+ });
83
+ exe_lib_mod.addImport("build_options", exe_options_mod);
84
+ if (target_os == .macos) {
85
+ if (b.lazyDependency("zig_objc", .{
86
+ .target = target,
87
+ .optimize = optimize,
88
+ })) |dep| {
89
+ exe_lib_mod.addImport("objc", dep.module("objc"));
90
+ }
91
+ }
92
+
93
+ const exe_mod = b.createModule(.{
94
+ .root_source_file = b.path("zig/src/main.zig"),
95
+ .target = target,
96
+ .optimize = optimize,
97
+ });
98
+ exe_mod.addImport("usecomputer_lib", exe_lib_mod);
99
+ exe_mod.addImport("zeke", b.dependency("zeke", .{
100
+ .target = target,
101
+ .optimize = optimize,
102
+ }).module("zeke"));
103
+
104
+ const exe = b.addExecutable(.{
105
+ .name = LIB_NAME,
106
+ .root_module = exe_mod,
107
+ });
108
+ linkPlatformDeps(exe.root_module, target_os);
109
+ b.installArtifact(exe);
110
+
111
+ const run_exe = b.addRunArtifact(exe);
112
+ if (b.args) |args| {
113
+ run_exe.addArgs(args);
114
+ }
115
+ const run_step = b.step("run", "Run the CLI");
116
+ run_step.dependOn(&run_exe.step);
117
+
118
+ // ── Tests ──
119
+
120
+ const test_options = b.addOptions();
121
+ test_options.addOption(bool, "enable_napigen", false);
122
+
41
123
  const test_mod = b.createModule(.{
42
124
  .root_source_file = b.path("zig/src/lib.zig"),
43
125
  .target = target,
44
126
  .optimize = optimize,
45
127
  });
128
+ test_mod.addImport("build_options", test_options.createModule());
46
129
 
47
130
  const test_step = b.step("test", "Run Zig unit tests");
48
131
  const test_exe = b.addTest(.{
49
132
  .root_module = test_mod,
50
133
  });
134
+ linkPlatformDeps(test_exe.root_module, target_os);
51
135
  const run_test = b.addRunArtifact(test_exe);
52
136
  test_step.dependOn(&run_test.step);
53
137
  }
package/build.zig.zon CHANGED
@@ -11,6 +11,11 @@
11
11
  .zig_objc = .{
12
12
  .url = "git+https://github.com/mitchellh/zig-objc?ref=main#27d0e03242e7ee6842bf8a86d2e0bb1f586a9847",
13
13
  .hash = "zig_objc-0.0.0-Ir_Sp7oUAQC3JpeR9EGUFGcHRSx_33IehitnjBCy-CwD",
14
+ .lazy = true,
15
+ },
16
+ .zeke = .{
17
+ .url = "https://github.com/remorses/zeke/archive/refs/heads/main.tar.gz",
18
+ .hash = "zeke-0.1.0-fnPIzP2mAADBDhCqMNuyU5TV7PEG9rEb2GDDjwMXCZYN",
14
19
  },
15
20
  },
16
21
  .paths = .{
@@ -1,8 +1,11 @@
1
1
  // Contract tests for direct native method calls emitted by the TS bridge.
2
2
  // These tests intentionally call the real Zig native module.
3
+ import fs from 'node:fs';
4
+ import os from 'node:os';
3
5
  import { describe, expect, test } from 'vitest';
4
6
  import { createBridgeFromNative } from './bridge.js';
5
7
  import { native } from './native-lib.js';
8
+ const isMacOS = os.platform() === 'darwin';
6
9
  describe('native bridge contract', () => {
7
10
  test('bridge calls hit real Zig module', async () => {
8
11
  expect(native).toBeTruthy();
@@ -10,16 +13,9 @@ describe('native bridge contract', () => {
10
13
  return;
11
14
  }
12
15
  const bridge = createBridgeFromNative({ nativeModule: native });
13
- const safeTarget = {
14
- x: 0,
15
- y: 0,
16
- };
17
- await bridge.click({
18
- point: safeTarget,
19
- button: 'left',
20
- count: 1,
21
- modifiers: [],
22
- });
16
+ const safeTarget = { x: 0, y: 0 };
17
+ // -- Mouse commands --
18
+ await bridge.click({ point: safeTarget, button: 'left', count: 1, modifiers: [] });
23
19
  await bridge.hover(safeTarget);
24
20
  await bridge.mouseMove(safeTarget);
25
21
  await bridge.mouseDown({ button: 'left' });
@@ -30,106 +26,43 @@ describe('native bridge contract', () => {
30
26
  button: 'left',
31
27
  durationMs: 10,
32
28
  });
33
- const screenshot = await bridge.screenshot({ path: `${process.cwd()}/tmp/bridge-contract-shot.png` });
29
+ // -- Screenshot --
30
+ const screenshotPath = `${process.cwd()}/tmp/bridge-contract-shot.png`;
31
+ const shot = await bridge.screenshot({ path: screenshotPath });
32
+ expect(shot.captureWidth).toBeGreaterThan(0);
33
+ expect(shot.captureHeight).toBeGreaterThan(0);
34
+ expect(shot.imageWidth).toBeGreaterThan(0);
35
+ expect(shot.imageHeight).toBeGreaterThan(0);
36
+ expect(shot.coordMap.split(',').length).toBe(6);
37
+ expect(shot.hint).toContain('--coord-map');
38
+ expect(fs.existsSync(screenshotPath)).toBe(true);
39
+ const stat = fs.statSync(screenshotPath);
40
+ expect(stat.size).toBeGreaterThan(100);
41
+ // -- Keyboard (works on both platforms) --
34
42
  await bridge.typeText({ text: 'h', delayMs: 30 });
35
43
  await bridge.press({ key: 'backspace', count: 1 });
36
- const scrollResult = await bridge.scroll({ direction: 'down', amount: 1 }).then(() => {
37
- return 'ok';
38
- }, (error) => {
39
- return error instanceof Error ? error.message : String(error);
40
- });
41
- const scrollAtResult = await bridge.scroll({ direction: 'right', amount: 1, at: safeTarget }).then(() => {
42
- return 'ok';
43
- }, (error) => {
44
- return error instanceof Error ? error.message : String(error);
45
- });
46
- const displays = await bridge.displayList();
47
- const windows = await bridge.windowList();
48
- const clipboardGetResult = await bridge.clipboardGet().then(() => {
49
- return 'ok';
50
- }, (error) => {
51
- return error instanceof Error ? error.message : String(error);
52
- });
53
- const clipboardSetResult = await bridge.clipboardSet({ text: 'copied' }).then(() => {
54
- return 'ok';
55
- }, (error) => {
56
- return error instanceof Error ? error.message : String(error);
57
- });
58
- const isOkOrTodo = ({ value }) => {
59
- return value === 'ok' || value.includes('TODO not implemented');
60
- };
61
- expect({
62
- screenshotShape: {
63
- path: screenshot.path,
64
- desktopIndex: typeof screenshot.desktopIndex,
65
- captureX: typeof screenshot.captureX,
66
- captureY: typeof screenshot.captureY,
67
- captureWidth: screenshot.captureWidth > 0,
68
- captureHeight: screenshot.captureHeight > 0,
69
- imageWidth: screenshot.imageWidth > 0,
70
- imageHeight: screenshot.imageHeight > 0,
71
- coordMapHasSixValues: screenshot.coordMap.split(',').length === 6,
72
- hint: screenshot.hint,
73
- },
74
- firstDisplayShape: displays[0]
75
- ? {
76
- id: typeof displays[0].id,
77
- index: typeof displays[0].index,
78
- width: displays[0].width > 0,
79
- height: displays[0].height > 0,
80
- }
81
- : null,
82
- firstWindowShape: windows[0]
83
- ? {
84
- id: typeof windows[0].id,
85
- ownerName: typeof windows[0].ownerName,
86
- desktopIndex: typeof windows[0].desktopIndex,
87
- }
88
- : null,
89
- optionalCommandOutcomes: {
90
- scrollResult: isOkOrTodo({ value: scrollResult }),
91
- scrollAtResult: isOkOrTodo({ value: scrollAtResult }),
92
- clipboardGetResult: isOkOrTodo({ value: clipboardGetResult }),
93
- clipboardSetResult: isOkOrTodo({ value: clipboardSetResult }),
94
- },
95
- }).toMatchInlineSnapshot(`
96
- {
97
- "firstDisplayShape": {
98
- "height": true,
99
- "id": "number",
100
- "index": "number",
101
- "width": true,
102
- },
103
- "firstWindowShape": {
104
- "desktopIndex": "number",
105
- "id": "number",
106
- "ownerName": "string",
107
- },
108
- "optionalCommandOutcomes": {
109
- "clipboardGetResult": true,
110
- "clipboardSetResult": true,
111
- "scrollAtResult": true,
112
- "scrollResult": true,
113
- },
114
- "screenshotShape": {
115
- "captureHeight": true,
116
- "captureWidth": true,
117
- "captureX": "number",
118
- "captureY": "number",
119
- "coordMapHasSixValues": true,
120
- "desktopIndex": "number",
121
- "hint": "ALWAYS pass this exact coord map to click, hover, drag, and mouse move when using coordinates from this screenshot:
122
- --coord-map "0,0,3440,1440,1568,656"
123
-
124
- Example:
125
- usecomputer click -x 400 -y 220 --coord-map "0,0,3440,1440,1568,656"",
126
- "imageHeight": true,
127
- "imageWidth": true,
128
- "path": "/Users/morse/Documents/GitHub/kimakivoice/usecomputer/tmp/bridge-contract-shot.png",
129
- },
130
- }
131
- `);
132
- expect(displays.length).toBeGreaterThan(0);
133
- expect(windows.length).toBeGreaterThan(0);
44
+ // -- Scroll --
45
+ await bridge.scroll({ direction: 'down', amount: 1 });
46
+ await bridge.scroll({ direction: 'right', amount: 1, at: safeTarget });
47
+ // -- Display list --
48
+ const displayList = await bridge.displayList();
49
+ expect(displayList.length).toBeGreaterThan(0);
50
+ const firstDisplay = displayList[0];
51
+ expect(firstDisplay.width).toBeGreaterThan(0);
52
+ expect(firstDisplay.height).toBeGreaterThan(0);
53
+ expect(typeof firstDisplay.id).toBe('number');
54
+ expect(typeof firstDisplay.index).toBe('number');
55
+ // -- Window list --
56
+ if (isMacOS) {
57
+ const windowList = await bridge.windowList();
58
+ expect(windowList.length).toBeGreaterThan(0);
59
+ const firstWindow = windowList[0];
60
+ expect(typeof firstWindow.id).toBe('number');
61
+ expect(typeof firstWindow.ownerName).toBe('string');
62
+ expect(typeof firstWindow.desktopIndex).toBe('number');
63
+ }
64
+ // -- Clipboard (TODO on all platforms — Zig returns "TODO not implemented") --
65
+ await expect(bridge.clipboardSet({ text: 'bridge-contract-test' })).rejects.toThrow('TODO not implemented');
66
+ await expect(bridge.clipboardGet()).rejects.toThrow('TODO not implemented');
134
67
  });
135
68
  });
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usecomputer",
3
- "version": "0.0.4",
3
+ "version": "0.1.0",
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",
@@ -63,7 +63,8 @@
63
63
  "url": "https://github.com/remorses/kimaki/issues"
64
64
  },
65
65
  "os": [
66
- "darwin"
66
+ "darwin",
67
+ "linux"
67
68
  ],
68
69
  "dependencies": {
69
70
  "goke": "^6.3.0",
@@ -85,6 +86,7 @@
85
86
  "build:zig": "zig build",
86
87
  "build:native": "tsx scripts/build.ts",
87
88
  "build:native:macos": "tsx scripts/build.ts darwin-arm64 darwin-x64",
89
+ "vm": "tsx scripts/vm.ts",
88
90
  "test": "vitest --run",
89
91
  "typecheck": "tsc --noEmit"
90
92
  }
@@ -1,10 +1,14 @@
1
1
  // Contract tests for direct native method calls emitted by the TS bridge.
2
2
  // These tests intentionally call the real Zig native module.
3
3
 
4
+ import fs from 'node:fs'
5
+ import os from 'node:os'
4
6
  import { describe, expect, test } from 'vitest'
5
7
  import { createBridgeFromNative } from './bridge.js'
6
8
  import { native } from './native-lib.js'
7
9
 
10
+ const isMacOS = os.platform() === 'darwin'
11
+
8
12
  describe('native bridge contract', () => {
9
13
  test('bridge calls hit real Zig module', async () => {
10
14
  expect(native).toBeTruthy()
@@ -14,17 +18,10 @@ describe('native bridge contract', () => {
14
18
 
15
19
  const bridge = createBridgeFromNative({ nativeModule: native })
16
20
 
17
- const safeTarget = {
18
- x: 0,
19
- y: 0,
20
- }
21
+ const safeTarget = { x: 0, y: 0 }
21
22
 
22
- await bridge.click({
23
- point: safeTarget,
24
- button: 'left',
25
- count: 1,
26
- modifiers: [],
27
- })
23
+ // -- Mouse commands --
24
+ await bridge.click({ point: safeTarget, button: 'left', count: 1, modifiers: [] })
28
25
  await bridge.hover(safeTarget)
29
26
  await bridge.mouseMove(safeTarget)
30
27
  await bridge.mouseDown({ button: 'left' })
@@ -36,121 +33,48 @@ describe('native bridge contract', () => {
36
33
  durationMs: 10,
37
34
  })
38
35
 
39
- const screenshot = await bridge.screenshot({ path: `${process.cwd()}/tmp/bridge-contract-shot.png` })
36
+ // -- Screenshot --
37
+ const screenshotPath = `${process.cwd()}/tmp/bridge-contract-shot.png`
38
+ const shot = await bridge.screenshot({ path: screenshotPath })
39
+ expect(shot.captureWidth).toBeGreaterThan(0)
40
+ expect(shot.captureHeight).toBeGreaterThan(0)
41
+ expect(shot.imageWidth).toBeGreaterThan(0)
42
+ expect(shot.imageHeight).toBeGreaterThan(0)
43
+ expect(shot.coordMap.split(',').length).toBe(6)
44
+ expect(shot.hint).toContain('--coord-map')
45
+ expect(fs.existsSync(screenshotPath)).toBe(true)
46
+ const stat = fs.statSync(screenshotPath)
47
+ expect(stat.size).toBeGreaterThan(100)
40
48
 
49
+ // -- Keyboard (works on both platforms) --
41
50
  await bridge.typeText({ text: 'h', delayMs: 30 })
42
51
  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
- }
80
52
 
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"
53
+ // -- Scroll --
54
+ await bridge.scroll({ direction: 'down', amount: 1 })
55
+ await bridge.scroll({ direction: 'right', amount: 1, at: safeTarget })
56
+
57
+ // -- Display list --
58
+ const displayList = await bridge.displayList()
59
+ expect(displayList.length).toBeGreaterThan(0)
60
+ const firstDisplay = displayList[0]!
61
+ expect(firstDisplay.width).toBeGreaterThan(0)
62
+ expect(firstDisplay.height).toBeGreaterThan(0)
63
+ expect(typeof firstDisplay.id).toBe('number')
64
+ expect(typeof firstDisplay.index).toBe('number')
143
65
 
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
- }
151
- `)
66
+ // -- Window list --
67
+ if (isMacOS) {
68
+ const windowList = await bridge.windowList()
69
+ expect(windowList.length).toBeGreaterThan(0)
70
+ const firstWindow = windowList[0]!
71
+ expect(typeof firstWindow.id).toBe('number')
72
+ expect(typeof firstWindow.ownerName).toBe('string')
73
+ expect(typeof firstWindow.desktopIndex).toBe('number')
74
+ }
152
75
 
153
- expect(displays.length).toBeGreaterThan(0)
154
- expect(windows.length).toBeGreaterThan(0)
76
+ // -- Clipboard (TODO on all platforms — Zig returns "TODO not implemented") --
77
+ await expect(bridge.clipboardSet({ text: 'bridge-contract-test' })).rejects.toThrow('TODO not implemented')
78
+ await expect(bridge.clipboardGet()).rejects.toThrow('TODO not implemented')
155
79
  })
156
80
  })