usecomputer 0.0.3 → 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 (57) hide show
  1. package/README.md +324 -0
  2. package/dist/bridge-contract.test.js +124 -63
  3. package/dist/bridge.d.ts.map +1 -1
  4. package/dist/bridge.js +241 -46
  5. package/dist/cli-parsing.test.js +34 -11
  6. package/dist/cli.d.ts.map +1 -1
  7. package/dist/cli.js +323 -28
  8. package/dist/coord-map.d.ts +14 -0
  9. package/dist/coord-map.d.ts.map +1 -0
  10. package/dist/coord-map.js +75 -0
  11. package/dist/coord-map.test.d.ts +2 -0
  12. package/dist/coord-map.test.d.ts.map +1 -0
  13. package/dist/coord-map.test.js +157 -0
  14. package/dist/darwin-arm64/usecomputer.node +0 -0
  15. package/dist/darwin-x64/usecomputer.node +0 -0
  16. package/dist/debug-point-image.d.ts +8 -0
  17. package/dist/debug-point-image.d.ts.map +1 -0
  18. package/dist/debug-point-image.js +43 -0
  19. package/dist/debug-point-image.test.d.ts +2 -0
  20. package/dist/debug-point-image.test.d.ts.map +1 -0
  21. package/dist/debug-point-image.test.js +44 -0
  22. package/dist/index.d.ts +2 -0
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +3 -1
  25. package/dist/lib.d.ts +26 -0
  26. package/dist/lib.d.ts.map +1 -0
  27. package/dist/lib.js +88 -0
  28. package/dist/native-click-smoke.test.js +69 -29
  29. package/dist/native-lib.d.ts +59 -1
  30. package/dist/native-lib.d.ts.map +1 -1
  31. package/dist/terminal-table.d.ts +10 -0
  32. package/dist/terminal-table.d.ts.map +1 -0
  33. package/dist/terminal-table.js +55 -0
  34. package/dist/terminal-table.test.d.ts +2 -0
  35. package/dist/terminal-table.test.d.ts.map +1 -0
  36. package/dist/terminal-table.test.js +41 -0
  37. package/dist/types.d.ts +45 -0
  38. package/dist/types.d.ts.map +1 -1
  39. package/package.json +16 -4
  40. package/src/bridge-contract.test.ts +140 -69
  41. package/src/bridge.ts +293 -53
  42. package/src/cli-parsing.test.ts +61 -0
  43. package/src/cli.ts +393 -32
  44. package/src/coord-map.test.ts +178 -0
  45. package/src/coord-map.ts +105 -0
  46. package/src/debug-point-image.test.ts +50 -0
  47. package/src/debug-point-image.ts +69 -0
  48. package/src/index.ts +3 -1
  49. package/src/lib.ts +125 -0
  50. package/src/native-click-smoke.test.ts +81 -63
  51. package/src/native-lib.ts +39 -1
  52. package/src/terminal-table.test.ts +44 -0
  53. package/src/terminal-table.ts +88 -0
  54. package/src/types.ts +50 -0
  55. package/zig/src/lib.zig +1258 -267
  56. package/zig/src/scroll.zig +213 -0
  57. package/zig/src/window.zig +123 -0
package/dist/bridge.js CHANGED
@@ -1,13 +1,67 @@
1
- // Native bridge that maps typed TS calls to the Zig N-API command dispatcher.
1
+ // Native bridge that maps typed TS calls to direct Zig N-API methods.
2
2
  import { native } from './native-lib.js';
3
+ import { z } from 'zod';
4
+ const displayInfoSchema = z.object({
5
+ id: z.number(),
6
+ index: z.number(),
7
+ name: z.string(),
8
+ x: z.number(),
9
+ y: z.number(),
10
+ width: z.number(),
11
+ height: z.number(),
12
+ scale: z.number(),
13
+ isPrimary: z.boolean(),
14
+ });
15
+ const displayListSchema = z.array(displayInfoSchema);
16
+ const windowInfoSchema = z.object({
17
+ id: z.number(),
18
+ ownerPid: z.number(),
19
+ ownerName: z.string(),
20
+ title: z.string(),
21
+ x: z.number(),
22
+ y: z.number(),
23
+ width: z.number(),
24
+ height: z.number(),
25
+ desktopIndex: z.number(),
26
+ });
27
+ const windowListSchema = z.array(windowInfoSchema);
3
28
  const unavailableError = 'Native backend is unavailable. Build it with `pnpm build:native` or `zig build` in usecomputer/.';
4
- function execute({ nativeModule, command, payload, }) {
5
- const response = nativeModule.execute(command, JSON.stringify(payload));
6
- const parsed = JSON.parse(response);
7
- if (!parsed.ok) {
8
- return new Error(parsed.error || `Native command failed: ${command}`);
29
+ class NativeBridgeError extends Error {
30
+ code;
31
+ command;
32
+ constructor({ message, code, command, }) {
33
+ super(message);
34
+ this.name = 'NativeBridgeError';
35
+ this.code = code;
36
+ this.command = command;
9
37
  }
10
- return parsed.data;
38
+ }
39
+ function unwrapCommand({ result, fallbackCommand, }) {
40
+ if (result.ok) {
41
+ return null;
42
+ }
43
+ const message = result.error?.message || `Native command failed: ${fallbackCommand}`;
44
+ return new NativeBridgeError({
45
+ message,
46
+ code: result.error?.code,
47
+ command: result.error?.command || fallbackCommand,
48
+ });
49
+ }
50
+ function unwrapData({ result, fallbackCommand, }) {
51
+ if (result.ok) {
52
+ if (result.data === undefined) {
53
+ return new NativeBridgeError({
54
+ message: `Native command returned no data: ${fallbackCommand}`,
55
+ command: fallbackCommand,
56
+ });
57
+ }
58
+ return result.data;
59
+ }
60
+ return new NativeBridgeError({
61
+ message: result.error?.message || `Native command failed: ${fallbackCommand}`,
62
+ code: result.error?.code,
63
+ command: result.error?.command || fallbackCommand,
64
+ });
11
65
  }
12
66
  function unavailableBridge() {
13
67
  const fail = async () => {
@@ -26,6 +80,7 @@ function unavailableBridge() {
26
80
  mouseUp: fail,
27
81
  mousePosition: fail,
28
82
  displayList: fail,
83
+ windowList: fail,
29
84
  clipboardGet: fail,
30
85
  clipboardSet: fail,
31
86
  };
@@ -36,91 +91,231 @@ export function createBridgeFromNative({ nativeModule }) {
36
91
  }
37
92
  return {
38
93
  async screenshot(input) {
39
- const result = execute({ nativeModule, command: 'screenshot', payload: input });
94
+ const nativeInput = {
95
+ path: input.path ?? null,
96
+ display: input.display ?? null,
97
+ window: input.window ?? null,
98
+ region: input.region ?? null,
99
+ annotate: input.annotate ?? null,
100
+ };
101
+ const result = unwrapData({
102
+ result: nativeModule.screenshot(nativeInput),
103
+ fallbackCommand: 'screenshot',
104
+ });
40
105
  if (result instanceof Error) {
41
106
  throw result;
42
107
  }
43
- return result;
108
+ const coordMap = [
109
+ result.captureX,
110
+ result.captureY,
111
+ result.captureWidth,
112
+ result.captureHeight,
113
+ result.imageWidth,
114
+ result.imageHeight,
115
+ ].join(',');
116
+ const hint = [
117
+ 'ALWAYS pass this exact coord map to click, hover, drag, and mouse move when using coordinates from this screenshot:',
118
+ `--coord-map "${coordMap}"`,
119
+ '',
120
+ 'Example:',
121
+ `usecomputer click -x 400 -y 220 --coord-map "${coordMap}"`,
122
+ ].join('\n');
123
+ return {
124
+ path: result.path,
125
+ desktopIndex: result.desktopIndex,
126
+ captureX: result.captureX,
127
+ captureY: result.captureY,
128
+ captureWidth: result.captureWidth,
129
+ captureHeight: result.captureHeight,
130
+ imageWidth: result.imageWidth,
131
+ imageHeight: result.imageHeight,
132
+ coordMap,
133
+ hint,
134
+ };
44
135
  },
45
136
  async click(input) {
46
- const result = execute({ nativeModule, command: 'click', payload: input });
47
- if (result instanceof Error) {
48
- throw result;
137
+ const nativeInput = {
138
+ point: input.point,
139
+ button: input.button ?? null,
140
+ count: input.count ?? null,
141
+ };
142
+ const result = nativeModule.click(nativeInput);
143
+ const maybeError = unwrapCommand({ result, fallbackCommand: 'click' });
144
+ if (maybeError instanceof Error) {
145
+ throw maybeError;
49
146
  }
50
147
  },
51
148
  async typeText(input) {
52
- const result = execute({ nativeModule, command: 'type-text', payload: input });
53
- if (result instanceof Error) {
54
- throw result;
149
+ const nativeInput = {
150
+ text: input.text,
151
+ delayMs: input.delayMs ?? null,
152
+ };
153
+ const result = nativeModule.typeText(nativeInput);
154
+ const maybeError = unwrapCommand({ result, fallbackCommand: 'typeText' });
155
+ if (maybeError instanceof Error) {
156
+ throw maybeError;
55
157
  }
56
158
  },
57
159
  async press(input) {
58
- const result = execute({ nativeModule, command: 'press', payload: input });
59
- if (result instanceof Error) {
60
- throw result;
160
+ const nativeInput = {
161
+ key: input.key,
162
+ count: input.count ?? null,
163
+ delayMs: input.delayMs ?? null,
164
+ };
165
+ const result = nativeModule.press(nativeInput);
166
+ const maybeError = unwrapCommand({ result, fallbackCommand: 'press' });
167
+ if (maybeError instanceof Error) {
168
+ throw maybeError;
61
169
  }
62
170
  },
63
171
  async scroll(input) {
64
- const result = execute({ nativeModule, command: 'scroll', payload: input });
65
- if (result instanceof Error) {
66
- throw result;
172
+ const nativeInput = {
173
+ direction: input.direction,
174
+ amount: input.amount,
175
+ at: input.at ?? null,
176
+ };
177
+ const result = nativeModule.scroll(nativeInput);
178
+ const maybeError = unwrapCommand({ result, fallbackCommand: 'scroll' });
179
+ if (maybeError instanceof Error) {
180
+ throw maybeError;
67
181
  }
68
182
  },
69
183
  async drag(input) {
70
- const result = execute({ nativeModule, command: 'drag', payload: input });
71
- if (result instanceof Error) {
72
- throw result;
184
+ const nativeInput = {
185
+ from: input.from,
186
+ to: input.to,
187
+ durationMs: input.durationMs ?? null,
188
+ button: input.button ?? null,
189
+ };
190
+ const result = nativeModule.drag(nativeInput);
191
+ const maybeError = unwrapCommand({ result, fallbackCommand: 'drag' });
192
+ if (maybeError instanceof Error) {
193
+ throw maybeError;
73
194
  }
74
195
  },
75
196
  async hover(input) {
76
- const result = execute({ nativeModule, command: 'hover', payload: input });
77
- if (result instanceof Error) {
78
- throw result;
197
+ const result = nativeModule.hover(input);
198
+ const maybeError = unwrapCommand({ result, fallbackCommand: 'hover' });
199
+ if (maybeError instanceof Error) {
200
+ throw maybeError;
79
201
  }
80
202
  },
81
203
  async mouseMove(input) {
82
- const result = execute({ nativeModule, command: 'mouse-move', payload: input });
83
- if (result instanceof Error) {
84
- throw result;
204
+ const result = nativeModule.mouseMove(input);
205
+ const maybeError = unwrapCommand({ result, fallbackCommand: 'mouseMove' });
206
+ if (maybeError instanceof Error) {
207
+ throw maybeError;
85
208
  }
86
209
  },
87
210
  async mouseDown(input) {
88
- const result = execute({ nativeModule, command: 'mouse-down', payload: input });
89
- if (result instanceof Error) {
90
- throw result;
211
+ const result = nativeModule.mouseDown({ button: input.button ?? null });
212
+ const maybeError = unwrapCommand({ result, fallbackCommand: 'mouseDown' });
213
+ if (maybeError instanceof Error) {
214
+ throw maybeError;
91
215
  }
92
216
  },
93
217
  async mouseUp(input) {
94
- const result = execute({ nativeModule, command: 'mouse-up', payload: input });
95
- if (result instanceof Error) {
96
- throw result;
218
+ const result = nativeModule.mouseUp({ button: input.button ?? null });
219
+ const maybeError = unwrapCommand({ result, fallbackCommand: 'mouseUp' });
220
+ if (maybeError instanceof Error) {
221
+ throw maybeError;
97
222
  }
98
223
  },
99
224
  async mousePosition() {
100
- const result = execute({ nativeModule, command: 'mouse-position', payload: {} });
225
+ const result = unwrapData({
226
+ result: nativeModule.mousePosition(),
227
+ fallbackCommand: 'mousePosition',
228
+ });
101
229
  if (result instanceof Error) {
102
230
  throw result;
103
231
  }
104
232
  return result;
105
233
  },
106
234
  async displayList() {
107
- const result = execute({ nativeModule, command: 'display-list', payload: {} });
108
- if (result instanceof Error) {
109
- throw result;
235
+ const payload = unwrapData({
236
+ result: nativeModule.displayList(),
237
+ fallbackCommand: 'displayList',
238
+ });
239
+ if (payload instanceof Error) {
240
+ throw payload;
110
241
  }
111
- return result;
242
+ let parsedJson;
243
+ try {
244
+ parsedJson = JSON.parse(payload);
245
+ }
246
+ catch (e) {
247
+ throw new NativeBridgeError({
248
+ message: 'Native displayList returned invalid JSON',
249
+ command: 'displayList',
250
+ code: 'INVALID_NATIVE_JSON',
251
+ });
252
+ }
253
+ const parsed = displayListSchema.safeParse(parsedJson);
254
+ if (!parsed.success) {
255
+ throw new NativeBridgeError({
256
+ message: 'Native displayList returned invalid payload shape',
257
+ command: 'displayList',
258
+ code: 'INVALID_NATIVE_PAYLOAD',
259
+ });
260
+ }
261
+ return parsed.data.map((display) => {
262
+ return {
263
+ id: display.id,
264
+ index: display.index,
265
+ name: display.name,
266
+ x: display.x,
267
+ y: display.y,
268
+ width: display.width,
269
+ height: display.height,
270
+ scale: display.scale,
271
+ isPrimary: display.isPrimary,
272
+ };
273
+ });
274
+ },
275
+ async windowList() {
276
+ const payload = unwrapData({
277
+ result: nativeModule.windowList(),
278
+ fallbackCommand: 'windowList',
279
+ });
280
+ if (payload instanceof Error) {
281
+ throw payload;
282
+ }
283
+ let parsedJson;
284
+ try {
285
+ parsedJson = JSON.parse(payload);
286
+ }
287
+ catch {
288
+ throw new NativeBridgeError({
289
+ message: 'Native windowList returned invalid JSON',
290
+ command: 'windowList',
291
+ code: 'INVALID_NATIVE_JSON',
292
+ });
293
+ }
294
+ const parsed = windowListSchema.safeParse(parsedJson);
295
+ if (!parsed.success) {
296
+ throw new NativeBridgeError({
297
+ message: 'Native windowList returned invalid payload shape',
298
+ command: 'windowList',
299
+ code: 'INVALID_NATIVE_PAYLOAD',
300
+ });
301
+ }
302
+ return parsed.data;
112
303
  },
113
304
  async clipboardGet() {
114
- const result = execute({ nativeModule, command: 'clipboard-get', payload: {} });
305
+ const result = unwrapData({
306
+ result: nativeModule.clipboardGet(),
307
+ fallbackCommand: 'clipboardGet',
308
+ });
115
309
  if (result instanceof Error) {
116
310
  throw result;
117
311
  }
118
- return result.text;
312
+ return result;
119
313
  },
120
314
  async clipboardSet(input) {
121
- const result = execute({ nativeModule, command: 'clipboard-set', payload: input });
122
- if (result instanceof Error) {
123
- throw result;
315
+ const result = nativeModule.clipboardSet(input);
316
+ const maybeError = unwrapCommand({ result, fallbackCommand: 'clipboardSet' });
317
+ if (maybeError instanceof Error) {
318
+ throw maybeError;
124
319
  }
125
320
  },
126
321
  };
@@ -1,4 +1,4 @@
1
- // Tests for goke CLI parsing on key automation commands.
1
+ // Parser tests for goke CLI options and flags.
2
2
  import { describe, expect, test } from 'vitest';
3
3
  import { createCli } from './cli.js';
4
4
  describe('usecomputer cli parsing', () => {
@@ -11,20 +11,43 @@ describe('usecomputer cli parsing', () => {
11
11
  });
12
12
  test('parses screenshot options', () => {
13
13
  const cli = createCli();
14
- const parsed = cli.parse(['node', 'usecomputer', 'screenshot', './shot.png', '--display', '2', '--region', '0,0,120,80'], { run: false });
14
+ const parsed = cli.parse(['node', 'usecomputer', 'screenshot', './shot.png', '--display', '2', '--region', '0,0,120,80'], {
15
+ run: false,
16
+ });
15
17
  expect(parsed.args[0]).toBe('./shot.png');
16
18
  expect(parsed.options.display).toBe(2);
17
19
  expect(parsed.options.region).toBe('0,0,120,80');
18
20
  });
19
- test('parses scroll amount and coordinates', () => {
21
+ test('parses coord-map option for click and mouse move', () => {
22
+ const clickCli = createCli();
23
+ const clickParsed = clickCli.parse(['node', 'usecomputer', 'click', '-x', '100', '-y', '200', '--coord-map', '0,0,1600,900,1568,882'], {
24
+ run: false,
25
+ });
26
+ const moveCli = createCli();
27
+ const moveParsed = moveCli.parse(['node', 'usecomputer', 'mouse', 'move', '-x', '100', '-y', '200', '--coord-map', '0,0,1600,900,1568,882'], {
28
+ run: false,
29
+ });
30
+ expect(clickParsed.options.coordMap).toBe('0,0,1600,900,1568,882');
31
+ expect(moveParsed.options.coordMap).toBe('0,0,1600,900,1568,882');
32
+ });
33
+ test('parses debug-point options', () => {
20
34
  const cli = createCli();
21
- const parsed = cli.parse(['node', 'usecomputer', 'scroll', 'down', '500', '--at', '10,20'], { run: false });
22
- expect(parsed.args).toMatchInlineSnapshot(`
23
- [
24
- "down",
25
- "500",
26
- ]
27
- `);
28
- expect(parsed.options.at).toBe('10,20');
35
+ const parsed = cli.parse([
36
+ 'node',
37
+ 'usecomputer',
38
+ 'debug-point',
39
+ '-x',
40
+ '210',
41
+ '-y',
42
+ '560',
43
+ '--coord-map',
44
+ '0,0,1720,1440,1568,1313',
45
+ '--output',
46
+ './tmp/debug-point.png',
47
+ ], { run: false });
48
+ expect(parsed.options.coordMap).toBe('0,0,1720,1440,1568,1313');
49
+ expect(parsed.options.output).toBe('./tmp/debug-point.png');
50
+ expect(parsed.options.x).toBe(210);
51
+ expect(parsed.options.y).toBe(560);
29
52
  });
30
53
  });
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAsB,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAuDvE,wBAAgB,SAAS,CAAC,EAAE,MAAuB,EAAE,GAAE;IAAE,MAAM,CAAC,EAAE,iBAAiB,CAAA;CAAO,2BAyQzF;AAED,wBAAgB,MAAM,IAAI,IAAI,CAG7B"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAmC,iBAAiB,EAAc,MAAM,YAAY,CAAA;AAiOhG,wBAAgB,SAAS,CAAC,EAAE,MAAuB,EAAE,GAAE;IAAE,MAAM,CAAC,EAAE,iBAAiB,CAAA;CAAO,2BA+bzF;AAED,wBAAgB,MAAM,IAAI,IAAI,CAG7B"}