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 +67 -0
- package/build.zig +95 -11
- package/build.zig.zon +5 -0
- package/dist/bridge-contract.test.js +42 -109
- package/dist/darwin-arm64/usecomputer.node +0 -0
- package/dist/darwin-x64/usecomputer.node +0 -0
- package/package.json +4 -2
- package/src/bridge-contract.test.ts +44 -120
- package/zig/src/lib.zig +799 -94
- package/zig/src/main.zig +382 -0
package/zig/src/main.zig
ADDED
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
/// Standalone CLI for usecomputer — no Node.js required.
|
|
2
|
+
/// Calls the same native functions as the N-API module via lib.zig.
|
|
3
|
+
const std = @import("std");
|
|
4
|
+
const zeke = @import("zeke");
|
|
5
|
+
const lib = @import("usecomputer_lib");
|
|
6
|
+
|
|
7
|
+
const File = std.fs.File;
|
|
8
|
+
const Writer = File.DeprecatedWriter;
|
|
9
|
+
|
|
10
|
+
fn getStdout() Writer {
|
|
11
|
+
return File.stdout().deprecatedWriter();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
fn getStderr() Writer {
|
|
15
|
+
return File.stderr().deprecatedWriter();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// ─── Helpers ───
|
|
19
|
+
|
|
20
|
+
fn parseF64(s: []const u8) ?f64 {
|
|
21
|
+
return std.fmt.parseFloat(f64, s) catch null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
fn parseRegion(s: []const u8) ?lib.ScreenshotRegion {
|
|
25
|
+
// Parse "x,y,w,h" format
|
|
26
|
+
var iter = std.mem.splitScalar(u8, s, ',');
|
|
27
|
+
const x_str = iter.next() orelse return null;
|
|
28
|
+
const y_str = iter.next() orelse return null;
|
|
29
|
+
const w_str = iter.next() orelse return null;
|
|
30
|
+
const h_str = iter.next() orelse return null;
|
|
31
|
+
return .{
|
|
32
|
+
.x = std.fmt.parseFloat(f64, x_str) catch return null,
|
|
33
|
+
.y = std.fmt.parseFloat(f64, y_str) catch return null,
|
|
34
|
+
.width = std.fmt.parseFloat(f64, w_str) catch return null,
|
|
35
|
+
.height = std.fmt.parseFloat(f64, h_str) catch return null,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
fn printError(result: anytype) void {
|
|
40
|
+
const stderr = getStderr();
|
|
41
|
+
if (result.@"error") |err| {
|
|
42
|
+
stderr.print("error: {s} ({s})\n", .{ err.message, err.code }) catch {};
|
|
43
|
+
} else {
|
|
44
|
+
stderr.print("error: command failed\n", .{}) catch {};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
fn printScreenshotJson(data: lib.ScreenshotOutput) void {
|
|
49
|
+
const stdout = getStdout();
|
|
50
|
+
stdout.print(
|
|
51
|
+
"{{\"path\":\"{s}\",\"desktopIndex\":{d:.0},\"captureX\":{d:.0},\"captureY\":{d:.0},\"captureWidth\":{d:.0},\"captureHeight\":{d:.0},\"imageWidth\":{d:.0},\"imageHeight\":{d:.0}}}\n",
|
|
52
|
+
.{ data.path, data.desktopIndex, data.captureX, data.captureY, data.captureWidth, data.captureHeight, data.imageWidth, data.imageHeight },
|
|
53
|
+
) catch {};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ─── Command definitions ───
|
|
57
|
+
|
|
58
|
+
const Screenshot = zeke.cmd("screenshot [path]", "Take a screenshot")
|
|
59
|
+
.option("--region [region]", "Capture specific region (x,y,w,h)")
|
|
60
|
+
.option("--display [id]", "Target display")
|
|
61
|
+
.option("--window [id]", "Target window")
|
|
62
|
+
.option("--annotate", "Annotate with grid overlay")
|
|
63
|
+
.option("--json", "Output as JSON");
|
|
64
|
+
|
|
65
|
+
const Click = zeke.cmd("click [target]", "Click at coordinates or target")
|
|
66
|
+
.option("-x <x>", "X coordinate")
|
|
67
|
+
.option("-y <y>", "Y coordinate")
|
|
68
|
+
.option("--button [button]", "Mouse button: left, right, middle")
|
|
69
|
+
.option("--count [count]", "Click count");
|
|
70
|
+
|
|
71
|
+
const DebugPoint = zeke.cmd("debug-point [target]", "Validate click coordinates visually")
|
|
72
|
+
.option("-x [x]", "X coordinate")
|
|
73
|
+
.option("-y [y]", "Y coordinate")
|
|
74
|
+
.option("--output [path]", "Save annotated screenshot")
|
|
75
|
+
.option("--json", "Output as JSON");
|
|
76
|
+
|
|
77
|
+
const TypeText = zeke.cmd("type [text]", "Type text using keyboard")
|
|
78
|
+
.option("--delay [ms]", "Delay between keystrokes in ms");
|
|
79
|
+
|
|
80
|
+
const Press = zeke.cmd("press <key>", "Press a key or key combination")
|
|
81
|
+
.option("--count [n]", "Number of times to press")
|
|
82
|
+
.option("--delay [ms]", "Delay between presses in ms");
|
|
83
|
+
|
|
84
|
+
const Scroll = zeke.cmd("scroll <direction> [amount]", "Scroll in a direction")
|
|
85
|
+
.option("--at [coords]", "Scroll at specific coordinates (x,y)");
|
|
86
|
+
|
|
87
|
+
const Drag = zeke.cmd("drag <from> <to>", "Drag from one point to another")
|
|
88
|
+
.option("--duration [ms]", "Drag duration in ms")
|
|
89
|
+
.option("--button [button]", "Mouse button");
|
|
90
|
+
|
|
91
|
+
const Hover = zeke.cmd("hover", "Move mouse without clicking")
|
|
92
|
+
.option("-x <x>", "X coordinate")
|
|
93
|
+
.option("-y <y>", "Y coordinate");
|
|
94
|
+
|
|
95
|
+
const MouseMove = zeke.cmd("mouse move", "Move to absolute coordinates")
|
|
96
|
+
.option("-x <x>", "X coordinate")
|
|
97
|
+
.option("-y <y>", "Y coordinate");
|
|
98
|
+
|
|
99
|
+
const MouseDown = zeke.cmd("mouse down", "Press and hold mouse button")
|
|
100
|
+
.option("--button [button]", "Mouse button");
|
|
101
|
+
|
|
102
|
+
const MouseUp = zeke.cmd("mouse up", "Release mouse button")
|
|
103
|
+
.option("--button [button]", "Mouse button");
|
|
104
|
+
|
|
105
|
+
const MousePosition = zeke.cmd("mouse position", "Print current mouse position")
|
|
106
|
+
.option("--json", "Output as JSON");
|
|
107
|
+
|
|
108
|
+
const DisplayList = zeke.cmd("display list", "List connected displays")
|
|
109
|
+
.option("--json", "Output as JSON");
|
|
110
|
+
|
|
111
|
+
const WindowList = zeke.cmd("window list", "List open windows")
|
|
112
|
+
.option("--json", "Output as JSON");
|
|
113
|
+
|
|
114
|
+
const ClipboardGet = zeke.cmd("clipboard get", "Print clipboard text");
|
|
115
|
+
|
|
116
|
+
const ClipboardSet = zeke.cmd("clipboard set <text>", "Set clipboard text");
|
|
117
|
+
|
|
118
|
+
// ─── Action functions ───
|
|
119
|
+
|
|
120
|
+
fn screenshotAction(args: Screenshot.Args, opts: Screenshot.Options) !void {
|
|
121
|
+
const result = lib.screenshot(.{
|
|
122
|
+
.path = args.path,
|
|
123
|
+
.display = if (opts.display) |d| parseF64(d) else null,
|
|
124
|
+
.window = if (opts.window) |w| parseF64(w) else null,
|
|
125
|
+
.region = if (opts.region) |r| parseRegion(r) else null,
|
|
126
|
+
.annotate = opts.annotate,
|
|
127
|
+
});
|
|
128
|
+
if (!result.ok) {
|
|
129
|
+
printError(result);
|
|
130
|
+
return error.CommandFailed;
|
|
131
|
+
}
|
|
132
|
+
if (opts.json) {
|
|
133
|
+
if (result.data) |data| {
|
|
134
|
+
printScreenshotJson(data);
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
const stdout = getStdout();
|
|
138
|
+
if (result.data) |data| {
|
|
139
|
+
try stdout.print("Screenshot saved to {s} ({d:.0}x{d:.0})\n", .{
|
|
140
|
+
data.path, data.imageWidth, data.imageHeight,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
fn clickAction(_: Click.Args, opts: Click.Options) !void {
|
|
147
|
+
const x = parseF64(opts.x) orelse return error.InvalidCoordinate;
|
|
148
|
+
const y = parseF64(opts.y) orelse return error.InvalidCoordinate;
|
|
149
|
+
const result = lib.click(.{
|
|
150
|
+
.point = .{ .x = x, .y = y },
|
|
151
|
+
.button = opts.button,
|
|
152
|
+
.count = if (opts.count) |c| parseF64(c) else null,
|
|
153
|
+
});
|
|
154
|
+
if (!result.ok) {
|
|
155
|
+
printError(result);
|
|
156
|
+
return error.CommandFailed;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
fn debugPointAction(_: DebugPoint.Args, _: DebugPoint.Options) !void {
|
|
161
|
+
const stderr = getStderr();
|
|
162
|
+
try stderr.print("debug-point: TODO\n", .{});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
fn typeTextAction(args: TypeText.Args, opts: TypeText.Options) !void {
|
|
166
|
+
const text = args.text orelse {
|
|
167
|
+
const stderr = getStderr();
|
|
168
|
+
try stderr.print("error: text argument required\n", .{});
|
|
169
|
+
return error.MissingArgument;
|
|
170
|
+
};
|
|
171
|
+
const result = lib.typeText(.{
|
|
172
|
+
.text = text,
|
|
173
|
+
.delayMs = if (opts.delay) |d| parseF64(d) else null,
|
|
174
|
+
});
|
|
175
|
+
if (!result.ok) {
|
|
176
|
+
printError(result);
|
|
177
|
+
return error.CommandFailed;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
fn pressAction(args: Press.Args, opts: Press.Options) !void {
|
|
182
|
+
const result = lib.press(.{
|
|
183
|
+
.key = args.key,
|
|
184
|
+
.count = if (opts.count) |c| parseF64(c) else null,
|
|
185
|
+
.delayMs = if (opts.delay) |d| parseF64(d) else null,
|
|
186
|
+
});
|
|
187
|
+
if (!result.ok) {
|
|
188
|
+
printError(result);
|
|
189
|
+
return error.CommandFailed;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
fn scrollAction(args: Scroll.Args, opts: Scroll.Options) !void {
|
|
194
|
+
const amount: f64 = if (args.amount) |a| (parseF64(a) orelse 3.0) else 3.0;
|
|
195
|
+
var at: ?lib.Point = null;
|
|
196
|
+
if (opts.at) |at_str| {
|
|
197
|
+
var iter = std.mem.splitScalar(u8, at_str, ',');
|
|
198
|
+
const x_str = iter.next() orelse return error.InvalidCoordinate;
|
|
199
|
+
const y_str = iter.next() orelse return error.InvalidCoordinate;
|
|
200
|
+
at = .{
|
|
201
|
+
.x = std.fmt.parseFloat(f64, x_str) catch return error.InvalidCoordinate,
|
|
202
|
+
.y = std.fmt.parseFloat(f64, y_str) catch return error.InvalidCoordinate,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
const result = lib.scroll(.{
|
|
206
|
+
.direction = args.direction,
|
|
207
|
+
.amount = amount,
|
|
208
|
+
.at = at,
|
|
209
|
+
});
|
|
210
|
+
if (!result.ok) {
|
|
211
|
+
printError(result);
|
|
212
|
+
return error.CommandFailed;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
fn dragAction(args: Drag.Args, opts: Drag.Options) !void {
|
|
217
|
+
// Parse "x,y" format for from and to
|
|
218
|
+
const from = parsePointArg(args.from) orelse return error.InvalidCoordinate;
|
|
219
|
+
const to = parsePointArg(args.to) orelse return error.InvalidCoordinate;
|
|
220
|
+
const result = lib.drag(.{
|
|
221
|
+
.from = from,
|
|
222
|
+
.to = to,
|
|
223
|
+
.durationMs = if (opts.duration) |d| parseF64(d) else null,
|
|
224
|
+
.button = opts.button,
|
|
225
|
+
});
|
|
226
|
+
if (!result.ok) {
|
|
227
|
+
printError(result);
|
|
228
|
+
return error.CommandFailed;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
fn parsePointArg(s: []const u8) ?lib.Point {
|
|
233
|
+
var iter = std.mem.splitScalar(u8, s, ',');
|
|
234
|
+
const x_str = iter.next() orelse return null;
|
|
235
|
+
const y_str = iter.next() orelse return null;
|
|
236
|
+
return .{
|
|
237
|
+
.x = std.fmt.parseFloat(f64, x_str) catch return null,
|
|
238
|
+
.y = std.fmt.parseFloat(f64, y_str) catch return null,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
fn hoverAction(_: Hover.Args, opts: Hover.Options) !void {
|
|
243
|
+
const x = parseF64(opts.x) orelse return error.InvalidCoordinate;
|
|
244
|
+
const y = parseF64(opts.y) orelse return error.InvalidCoordinate;
|
|
245
|
+
const result = lib.hover(.{ .x = x, .y = y });
|
|
246
|
+
if (!result.ok) {
|
|
247
|
+
printError(result);
|
|
248
|
+
return error.CommandFailed;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
fn mouseMoveAction(_: MouseMove.Args, opts: MouseMove.Options) !void {
|
|
253
|
+
const x = parseF64(opts.x) orelse return error.InvalidCoordinate;
|
|
254
|
+
const y = parseF64(opts.y) orelse return error.InvalidCoordinate;
|
|
255
|
+
const result = lib.mouseMove(.{ .x = x, .y = y });
|
|
256
|
+
if (!result.ok) {
|
|
257
|
+
printError(result);
|
|
258
|
+
return error.CommandFailed;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
fn mouseDownAction(_: MouseDown.Args, opts: MouseDown.Options) !void {
|
|
263
|
+
const result = lib.mouseDown(.{ .button = opts.button });
|
|
264
|
+
if (!result.ok) {
|
|
265
|
+
printError(result);
|
|
266
|
+
return error.CommandFailed;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
fn mouseUpAction(_: MouseUp.Args, opts: MouseUp.Options) !void {
|
|
271
|
+
const result = lib.mouseUp(.{ .button = opts.button });
|
|
272
|
+
if (!result.ok) {
|
|
273
|
+
printError(result);
|
|
274
|
+
return error.CommandFailed;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
fn mousePositionAction(_: MousePosition.Args, opts: MousePosition.Options) !void {
|
|
279
|
+
const result = lib.mousePosition();
|
|
280
|
+
if (!result.ok) {
|
|
281
|
+
printError(result);
|
|
282
|
+
return error.CommandFailed;
|
|
283
|
+
}
|
|
284
|
+
if (result.data) |pos| {
|
|
285
|
+
const stdout = getStdout();
|
|
286
|
+
if (opts.json) {
|
|
287
|
+
try stdout.print("{{\"x\":{d:.0},\"y\":{d:.0}}}\n", .{ pos.x, pos.y });
|
|
288
|
+
} else {
|
|
289
|
+
try stdout.print("{d:.0}, {d:.0}\n", .{ pos.x, pos.y });
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
fn displayListAction(_: DisplayList.Args, opts: DisplayList.Options) !void {
|
|
295
|
+
const result = lib.displayList();
|
|
296
|
+
if (!result.ok) {
|
|
297
|
+
printError(result);
|
|
298
|
+
return error.CommandFailed;
|
|
299
|
+
}
|
|
300
|
+
if (result.data) |data| {
|
|
301
|
+
const stdout = getStdout();
|
|
302
|
+
if (opts.json) {
|
|
303
|
+
try stdout.print("{s}\n", .{data});
|
|
304
|
+
} else {
|
|
305
|
+
try stdout.print("{s}\n", .{data});
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
fn windowListAction(_: WindowList.Args, opts: WindowList.Options) !void {
|
|
311
|
+
const result = lib.windowList();
|
|
312
|
+
if (!result.ok) {
|
|
313
|
+
printError(result);
|
|
314
|
+
return error.CommandFailed;
|
|
315
|
+
}
|
|
316
|
+
if (result.data) |data| {
|
|
317
|
+
const stdout = getStdout();
|
|
318
|
+
if (opts.json) {
|
|
319
|
+
try stdout.print("{s}\n", .{data});
|
|
320
|
+
} else {
|
|
321
|
+
try stdout.print("{s}\n", .{data});
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
fn clipboardGetAction(_: ClipboardGet.Args, _: ClipboardGet.Options) !void {
|
|
327
|
+
const result = lib.clipboardGet();
|
|
328
|
+
if (!result.ok) {
|
|
329
|
+
printError(result);
|
|
330
|
+
return error.CommandFailed;
|
|
331
|
+
}
|
|
332
|
+
if (result.data) |data| {
|
|
333
|
+
const stdout = getStdout();
|
|
334
|
+
try stdout.print("{s}\n", .{data});
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
fn clipboardSetAction(args: ClipboardSet.Args, _: ClipboardSet.Options) !void {
|
|
339
|
+
const result = lib.clipboardSet(.{ .text = args.text });
|
|
340
|
+
if (!result.ok) {
|
|
341
|
+
printError(result);
|
|
342
|
+
return error.CommandFailed;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// ─── Main ───
|
|
347
|
+
|
|
348
|
+
pub fn main() !void {
|
|
349
|
+
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
|
350
|
+
defer _ = gpa.deinit();
|
|
351
|
+
|
|
352
|
+
var app = zeke.App(.{
|
|
353
|
+
Screenshot.bind(screenshotAction),
|
|
354
|
+
Click.bind(clickAction),
|
|
355
|
+
DebugPoint.bind(debugPointAction),
|
|
356
|
+
TypeText.bind(typeTextAction),
|
|
357
|
+
Press.bind(pressAction),
|
|
358
|
+
Scroll.bind(scrollAction),
|
|
359
|
+
Drag.bind(dragAction),
|
|
360
|
+
Hover.bind(hoverAction),
|
|
361
|
+
MouseMove.bind(mouseMoveAction),
|
|
362
|
+
MouseDown.bind(mouseDownAction),
|
|
363
|
+
MouseUp.bind(mouseUpAction),
|
|
364
|
+
MousePosition.bind(mousePositionAction),
|
|
365
|
+
DisplayList.bind(displayListAction),
|
|
366
|
+
WindowList.bind(windowListAction),
|
|
367
|
+
ClipboardGet.bind(clipboardGetAction),
|
|
368
|
+
ClipboardSet.bind(clipboardSetAction),
|
|
369
|
+
}).init(gpa.allocator(), "usecomputer");
|
|
370
|
+
|
|
371
|
+
app.setVersion("0.0.4");
|
|
372
|
+
app.run() catch |err| {
|
|
373
|
+
switch (err) {
|
|
374
|
+
error.CommandFailed, error.InvalidCoordinate, error.MissingArgument => {},
|
|
375
|
+
else => {
|
|
376
|
+
const stderr = getStderr();
|
|
377
|
+
stderr.print("error: {s}\n", .{@errorName(err)}) catch {};
|
|
378
|
+
},
|
|
379
|
+
}
|
|
380
|
+
std.process.exit(1);
|
|
381
|
+
};
|
|
382
|
+
}
|