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.
- package/README.md +324 -0
- package/dist/bridge-contract.test.js +124 -63
- package/dist/bridge.d.ts.map +1 -1
- package/dist/bridge.js +241 -46
- package/dist/cli-parsing.test.js +34 -11
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +323 -28
- package/dist/coord-map.d.ts +14 -0
- package/dist/coord-map.d.ts.map +1 -0
- package/dist/coord-map.js +75 -0
- package/dist/coord-map.test.d.ts +2 -0
- package/dist/coord-map.test.d.ts.map +1 -0
- package/dist/coord-map.test.js +157 -0
- package/dist/darwin-arm64/usecomputer.node +0 -0
- package/dist/darwin-x64/usecomputer.node +0 -0
- package/dist/debug-point-image.d.ts +8 -0
- package/dist/debug-point-image.d.ts.map +1 -0
- package/dist/debug-point-image.js +43 -0
- package/dist/debug-point-image.test.d.ts +2 -0
- package/dist/debug-point-image.test.d.ts.map +1 -0
- package/dist/debug-point-image.test.js +44 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/lib.d.ts +26 -0
- package/dist/lib.d.ts.map +1 -0
- package/dist/lib.js +88 -0
- package/dist/native-click-smoke.test.js +69 -29
- package/dist/native-lib.d.ts +59 -1
- package/dist/native-lib.d.ts.map +1 -1
- package/dist/terminal-table.d.ts +10 -0
- package/dist/terminal-table.d.ts.map +1 -0
- package/dist/terminal-table.js +55 -0
- package/dist/terminal-table.test.d.ts +2 -0
- package/dist/terminal-table.test.d.ts.map +1 -0
- package/dist/terminal-table.test.js +41 -0
- package/dist/types.d.ts +45 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +16 -4
- package/src/bridge-contract.test.ts +140 -69
- package/src/bridge.ts +293 -53
- package/src/cli-parsing.test.ts +61 -0
- package/src/cli.ts +393 -32
- package/src/coord-map.test.ts +178 -0
- package/src/coord-map.ts +105 -0
- package/src/debug-point-image.test.ts +50 -0
- package/src/debug-point-image.ts +69 -0
- package/src/index.ts +3 -1
- package/src/lib.ts +125 -0
- package/src/native-click-smoke.test.ts +81 -63
- package/src/native-lib.ts +39 -1
- package/src/terminal-table.test.ts +44 -0
- package/src/terminal-table.ts +88 -0
- package/src/types.ts +50 -0
- package/zig/src/lib.zig +1258 -267
- package/zig/src/scroll.zig +213 -0
- 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
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
47
|
-
|
|
48
|
-
|
|
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
|
|
53
|
-
|
|
54
|
-
|
|
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
|
|
59
|
-
|
|
60
|
-
|
|
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
|
|
65
|
-
|
|
66
|
-
|
|
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
|
|
71
|
-
|
|
72
|
-
|
|
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 =
|
|
77
|
-
|
|
78
|
-
|
|
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 =
|
|
83
|
-
|
|
84
|
-
|
|
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 =
|
|
89
|
-
|
|
90
|
-
|
|
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 =
|
|
95
|
-
|
|
96
|
-
|
|
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 =
|
|
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
|
|
108
|
-
|
|
109
|
-
|
|
235
|
+
const payload = unwrapData({
|
|
236
|
+
result: nativeModule.displayList(),
|
|
237
|
+
fallbackCommand: 'displayList',
|
|
238
|
+
});
|
|
239
|
+
if (payload instanceof Error) {
|
|
240
|
+
throw payload;
|
|
110
241
|
}
|
|
111
|
-
|
|
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 =
|
|
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
|
|
312
|
+
return result;
|
|
119
313
|
},
|
|
120
314
|
async clipboardSet(input) {
|
|
121
|
-
const result =
|
|
122
|
-
|
|
123
|
-
|
|
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
|
};
|
package/dist/cli-parsing.test.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//
|
|
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'], {
|
|
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
|
|
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([
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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":"
|
|
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"}
|