usecomputer 0.0.1 → 0.0.3

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 (50) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +51 -0
  3. package/bin.js +3 -1
  4. package/build.zig +53 -0
  5. package/build.zig.zon +21 -0
  6. package/dist/bridge-contract.test.d.ts +2 -0
  7. package/dist/bridge-contract.test.d.ts.map +1 -0
  8. package/dist/bridge-contract.test.js +74 -0
  9. package/dist/bridge.d.ts +7 -0
  10. package/dist/bridge.d.ts.map +1 -0
  11. package/dist/bridge.js +130 -0
  12. package/dist/cli-parsing.test.d.ts +2 -0
  13. package/dist/cli-parsing.test.d.ts.map +1 -0
  14. package/dist/cli-parsing.test.js +30 -0
  15. package/dist/cli.d.ts +5 -1
  16. package/dist/cli.d.ts.map +1 -1
  17. package/dist/cli.js +297 -335
  18. package/dist/command-parsers.d.ts +6 -0
  19. package/dist/command-parsers.d.ts.map +1 -0
  20. package/dist/command-parsers.js +54 -0
  21. package/dist/command-parsers.test.d.ts +2 -0
  22. package/dist/command-parsers.test.d.ts.map +1 -0
  23. package/dist/command-parsers.test.js +44 -0
  24. package/dist/darwin-arm64/usecomputer.node +0 -0
  25. package/dist/darwin-x64/usecomputer.node +0 -0
  26. package/dist/index.d.ts +4 -1
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +5 -4
  29. package/dist/native-click-smoke.test.d.ts +2 -0
  30. package/dist/native-click-smoke.test.d.ts.map +1 -0
  31. package/dist/native-click-smoke.test.js +93 -0
  32. package/dist/native-lib.cjs +33 -0
  33. package/dist/native-lib.d.cts +7 -0
  34. package/dist/native-lib.d.ts +5 -0
  35. package/dist/native-lib.d.ts.map +1 -0
  36. package/dist/native-lib.js +27 -0
  37. package/dist/types.d.ts +80 -0
  38. package/dist/types.d.ts.map +1 -0
  39. package/dist/types.js +2 -0
  40. package/package.json +23 -12
  41. package/src/bridge-contract.test.ts +85 -0
  42. package/src/bridge.ts +159 -0
  43. package/src/cli.ts +344 -473
  44. package/src/command-parsers.test.ts +50 -0
  45. package/src/command-parsers.ts +60 -0
  46. package/src/index.ts +5 -4
  47. package/src/native-click-smoke.test.ts +131 -0
  48. package/src/native-lib.ts +38 -0
  49. package/src/types.ts +87 -0
  50. package/zig/src/lib.zig +493 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,31 @@
1
+ <!-- Purpose: track notable user-facing changes for npm releases. -->
2
+
3
+ # Changelog
4
+
5
+ All notable changes to `usecomputer` will be documented in this file.
6
+
7
+ ## 0.0.3
8
+
9
+ - Implement real screenshot capture + PNG file writing on macOS.
10
+ - Screenshot path handling now uses the requested output path reliably.
11
+ - Unimplemented commands now return explicit `TODO not implemented: ...` errors.
12
+ - Clarify `--display` index behavior as 0-based in help/docs.
13
+
14
+ ## 0.0.2
15
+
16
+ - Publish macOS native binaries for both `darwin-arm64` and `darwin-x64`.
17
+ - Add package metadata/docs for npm distribution.
18
+ - Improve CLI coordinate input with `-x` / `-y` flags.
19
+
20
+ ## 0.0.1
21
+
22
+ - Initial npm package release for macOS.
23
+ - Native Zig + Quartz mouse actions:
24
+ - `click`
25
+ - `mouse move`
26
+ - `mouse down`
27
+ - `mouse up`
28
+ - `mouse position`
29
+ - `hover`
30
+ - `drag`
31
+ - CLI coordinates improved with `-x` and `-y` flags.
package/README.md ADDED
@@ -0,0 +1,51 @@
1
+ <!-- Purpose: npm package usage and install guide for usecomputer CLI. -->
2
+
3
+ # usecomputer
4
+
5
+ `usecomputer` is a macOS desktop automation CLI for AI agents.
6
+
7
+ It can move the mouse, click, drag, and query cursor position using native
8
+ Quartz events through a Zig N-API module.
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ npm install -g usecomputer
14
+ ```
15
+
16
+ ## Requirements
17
+
18
+ - macOS (Darwin)
19
+ - Accessibility permission enabled for your terminal app
20
+
21
+ ## Quick start
22
+
23
+ ```bash
24
+ usecomputer mouse position --json
25
+ usecomputer mouse move -x 500 -y 500
26
+ usecomputer click -x 500 -y 500 --button left --count 1
27
+ ```
28
+
29
+ ## Coordinate options
30
+
31
+ Commands that target coordinates accept `-x` and `-y` flags:
32
+
33
+ - `usecomputer click -x <n> -y <n>`
34
+ - `usecomputer hover -x <n> -y <n>`
35
+ - `usecomputer mouse move -x <n> -y <n>`
36
+
37
+ Legacy coordinate forms are also accepted where available.
38
+
39
+ ## Display index options
40
+
41
+ For commands that accept `--display`, the index is 0-based:
42
+
43
+ - `0` = first display
44
+ - `1` = second display
45
+ - `2` = third display
46
+
47
+ Example:
48
+
49
+ ```bash
50
+ usecomputer screenshot ./shot.png --display 0 --json
51
+ ```
package/bin.js CHANGED
@@ -1,2 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import './dist/cli.js'
2
+ import { runCli } from './dist/cli.js'
3
+
4
+ runCli()
package/build.zig ADDED
@@ -0,0 +1,53 @@
1
+ // Build script for usecomputer Zig N-API native module artifacts.
2
+
3
+ const std = @import("std");
4
+ const napigen = @import("napigen");
5
+
6
+ const LIB_NAME = "usecomputer";
7
+
8
+ pub fn build(b: *std.Build) void {
9
+ const target = b.standardTargetOptions(.{});
10
+ const optimize = b.standardOptimizeOption(.{});
11
+
12
+ const lib_mod = b.createModule(.{
13
+ .root_source_file = b.path("zig/src/lib.zig"),
14
+ .target = target,
15
+ .optimize = optimize,
16
+ });
17
+ 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"));
22
+
23
+ const lib = b.addLibrary(.{
24
+ .name = LIB_NAME,
25
+ .root_module = lib_mod,
26
+ .linkage = .dynamic,
27
+ });
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
+ }
34
+
35
+ napigen.setup(lib);
36
+ b.installArtifact(lib);
37
+
38
+ const copy_node_step = b.addInstallLibFile(lib.getEmittedBin(), LIB_NAME ++ ".node");
39
+ b.getInstallStep().dependOn(&copy_node_step.step);
40
+
41
+ const test_mod = b.createModule(.{
42
+ .root_source_file = b.path("zig/src/lib.zig"),
43
+ .target = target,
44
+ .optimize = optimize,
45
+ });
46
+
47
+ const test_step = b.step("test", "Run Zig unit tests");
48
+ const test_exe = b.addTest(.{
49
+ .root_module = test_mod,
50
+ });
51
+ const run_test = b.addRunArtifact(test_exe);
52
+ test_step.dependOn(&run_test.step);
53
+ }
package/build.zig.zon ADDED
@@ -0,0 +1,21 @@
1
+ // Zig package manifest for the usecomputer native addon dependencies.
2
+ .{
3
+ .name = .usecomputer,
4
+ .version = "0.1.0",
5
+ .fingerprint = 0x28c2cde2d2b298eb,
6
+ .dependencies = .{
7
+ .napigen = .{
8
+ .url = "git+https://github.com/cztomsik/napigen?ref=main#bc2c8259d95be774847e60fce9bfc203ab623b30",
9
+ .hash = "napigen-0.1.0-YpiIumJ9AAA4yVKISHKfN_2H0u7-su18jRHSSq_UUTNN",
10
+ },
11
+ .zig_objc = .{
12
+ .url = "git+https://github.com/mitchellh/zig-objc?ref=main#27d0e03242e7ee6842bf8a86d2e0bb1f586a9847",
13
+ .hash = "zig_objc-0.0.0-Ir_Sp7oUAQC3JpeR9EGUFGcHRSx_33IehitnjBCy-CwD",
14
+ },
15
+ },
16
+ .paths = .{
17
+ "build.zig",
18
+ "build.zig.zon",
19
+ "zig",
20
+ },
21
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=bridge-contract.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge-contract.test.d.ts","sourceRoot":"","sources":["../src/bridge-contract.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,74 @@
1
+ // Contract tests for command names and payloads emitted to the native bridge.
2
+ import { describe, expect, test } from 'vitest';
3
+ import { createBridgeFromNative } from './bridge.js';
4
+ function createFakeNative({ calls }) {
5
+ return {
6
+ execute(command, payloadJson) {
7
+ const payload = JSON.parse(payloadJson);
8
+ calls.push({ command, payload });
9
+ if (command === 'mouse-position') {
10
+ return JSON.stringify({ ok: true, data: { x: 10, y: 20 } });
11
+ }
12
+ if (command === 'display-list') {
13
+ return JSON.stringify({
14
+ ok: true,
15
+ data: [
16
+ { id: 1, name: 'Built-in', x: 0, y: 0, width: 1512, height: 982, scale: 2, isPrimary: true },
17
+ ],
18
+ });
19
+ }
20
+ if (command === 'clipboard-get') {
21
+ return JSON.stringify({ ok: true, data: { text: 'hello' } });
22
+ }
23
+ if (command === 'screenshot') {
24
+ return JSON.stringify({ ok: true, data: { path: '/tmp/test.png' } });
25
+ }
26
+ return JSON.stringify({ ok: true, data: null });
27
+ },
28
+ };
29
+ }
30
+ describe('native bridge contract', () => {
31
+ test('maps high-level calls to native commands', async () => {
32
+ const calls = [];
33
+ const bridge = createBridgeFromNative({ nativeModule: createFakeNative({ calls }) });
34
+ await bridge.click({
35
+ point: { x: 100, y: 200 },
36
+ button: 'left',
37
+ count: 2,
38
+ modifiers: ['cmd'],
39
+ });
40
+ await bridge.typeText({ text: 'hello', delayMs: 30 });
41
+ await bridge.press({ key: 'enter', count: 1 });
42
+ await bridge.scroll({ direction: 'down', amount: 300 });
43
+ await bridge.drag({ from: { x: 10, y: 10 }, to: { x: 200, y: 120 }, button: 'left' });
44
+ await bridge.hover({ x: 33, y: 44 });
45
+ await bridge.mouseMove({ x: 60, y: 70 });
46
+ await bridge.mouseDown({ button: 'left' });
47
+ await bridge.mouseUp({ button: 'left' });
48
+ await bridge.mousePosition();
49
+ await bridge.displayList();
50
+ await bridge.clipboardGet();
51
+ await bridge.clipboardSet({ text: 'copied' });
52
+ await bridge.screenshot({ path: './out.png' });
53
+ expect(calls.map((call) => {
54
+ return call.command;
55
+ })).toMatchInlineSnapshot(`
56
+ [
57
+ "click",
58
+ "type-text",
59
+ "press",
60
+ "scroll",
61
+ "drag",
62
+ "hover",
63
+ "mouse-move",
64
+ "mouse-down",
65
+ "mouse-up",
66
+ "mouse-position",
67
+ "display-list",
68
+ "clipboard-get",
69
+ "clipboard-set",
70
+ "screenshot",
71
+ ]
72
+ `);
73
+ });
74
+ });
@@ -0,0 +1,7 @@
1
+ import { type NativeModule } from './native-lib.js';
2
+ import type { UseComputerBridge } from './types.js';
3
+ export declare function createBridgeFromNative({ nativeModule }: {
4
+ nativeModule: NativeModule | null;
5
+ }): UseComputerBridge;
6
+ export declare function createBridge(): UseComputerBridge;
7
+ //# sourceMappingURL=bridge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AAEA,OAAO,EAAU,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAC3D,OAAO,KAAK,EASV,iBAAiB,EAElB,MAAM,YAAY,CAAA;AA6CnB,wBAAgB,sBAAsB,CAAC,EAAE,YAAY,EAAE,EAAE;IAAE,YAAY,EAAE,YAAY,GAAG,IAAI,CAAA;CAAE,GAAG,iBAAiB,CA+FjH;AAED,wBAAgB,YAAY,IAAI,iBAAiB,CAEhD"}
package/dist/bridge.js ADDED
@@ -0,0 +1,130 @@
1
+ // Native bridge that maps typed TS calls to the Zig N-API command dispatcher.
2
+ import { native } from './native-lib.js';
3
+ 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}`);
9
+ }
10
+ return parsed.data;
11
+ }
12
+ function unavailableBridge() {
13
+ const fail = async () => {
14
+ throw new Error(unavailableError);
15
+ };
16
+ return {
17
+ screenshot: fail,
18
+ click: fail,
19
+ typeText: fail,
20
+ press: fail,
21
+ scroll: fail,
22
+ drag: fail,
23
+ hover: fail,
24
+ mouseMove: fail,
25
+ mouseDown: fail,
26
+ mouseUp: fail,
27
+ mousePosition: fail,
28
+ displayList: fail,
29
+ clipboardGet: fail,
30
+ clipboardSet: fail,
31
+ };
32
+ }
33
+ export function createBridgeFromNative({ nativeModule }) {
34
+ if (!nativeModule) {
35
+ return unavailableBridge();
36
+ }
37
+ return {
38
+ async screenshot(input) {
39
+ const result = execute({ nativeModule, command: 'screenshot', payload: input });
40
+ if (result instanceof Error) {
41
+ throw result;
42
+ }
43
+ return result;
44
+ },
45
+ async click(input) {
46
+ const result = execute({ nativeModule, command: 'click', payload: input });
47
+ if (result instanceof Error) {
48
+ throw result;
49
+ }
50
+ },
51
+ async typeText(input) {
52
+ const result = execute({ nativeModule, command: 'type-text', payload: input });
53
+ if (result instanceof Error) {
54
+ throw result;
55
+ }
56
+ },
57
+ async press(input) {
58
+ const result = execute({ nativeModule, command: 'press', payload: input });
59
+ if (result instanceof Error) {
60
+ throw result;
61
+ }
62
+ },
63
+ async scroll(input) {
64
+ const result = execute({ nativeModule, command: 'scroll', payload: input });
65
+ if (result instanceof Error) {
66
+ throw result;
67
+ }
68
+ },
69
+ async drag(input) {
70
+ const result = execute({ nativeModule, command: 'drag', payload: input });
71
+ if (result instanceof Error) {
72
+ throw result;
73
+ }
74
+ },
75
+ async hover(input) {
76
+ const result = execute({ nativeModule, command: 'hover', payload: input });
77
+ if (result instanceof Error) {
78
+ throw result;
79
+ }
80
+ },
81
+ async mouseMove(input) {
82
+ const result = execute({ nativeModule, command: 'mouse-move', payload: input });
83
+ if (result instanceof Error) {
84
+ throw result;
85
+ }
86
+ },
87
+ async mouseDown(input) {
88
+ const result = execute({ nativeModule, command: 'mouse-down', payload: input });
89
+ if (result instanceof Error) {
90
+ throw result;
91
+ }
92
+ },
93
+ async mouseUp(input) {
94
+ const result = execute({ nativeModule, command: 'mouse-up', payload: input });
95
+ if (result instanceof Error) {
96
+ throw result;
97
+ }
98
+ },
99
+ async mousePosition() {
100
+ const result = execute({ nativeModule, command: 'mouse-position', payload: {} });
101
+ if (result instanceof Error) {
102
+ throw result;
103
+ }
104
+ return result;
105
+ },
106
+ async displayList() {
107
+ const result = execute({ nativeModule, command: 'display-list', payload: {} });
108
+ if (result instanceof Error) {
109
+ throw result;
110
+ }
111
+ return result;
112
+ },
113
+ async clipboardGet() {
114
+ const result = execute({ nativeModule, command: 'clipboard-get', payload: {} });
115
+ if (result instanceof Error) {
116
+ throw result;
117
+ }
118
+ return result.text;
119
+ },
120
+ async clipboardSet(input) {
121
+ const result = execute({ nativeModule, command: 'clipboard-set', payload: input });
122
+ if (result instanceof Error) {
123
+ throw result;
124
+ }
125
+ },
126
+ };
127
+ }
128
+ export function createBridge() {
129
+ return createBridgeFromNative({ nativeModule: native });
130
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=cli-parsing.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-parsing.test.d.ts","sourceRoot":"","sources":["../src/cli-parsing.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,30 @@
1
+ // Tests for goke CLI parsing on key automation commands.
2
+ import { describe, expect, test } from 'vitest';
3
+ import { createCli } from './cli.js';
4
+ describe('usecomputer cli parsing', () => {
5
+ test('parses click options with typed defaults', () => {
6
+ const cli = createCli();
7
+ const parsed = cli.parse(['node', 'usecomputer', 'click', '100,200', '--count', '2'], { run: false });
8
+ expect(parsed.args[0]).toBe('100,200');
9
+ expect(parsed.options.count).toBe(2);
10
+ expect(parsed.options.button).toBe('left');
11
+ });
12
+ test('parses screenshot options', () => {
13
+ const cli = createCli();
14
+ const parsed = cli.parse(['node', 'usecomputer', 'screenshot', './shot.png', '--display', '2', '--region', '0,0,120,80'], { run: false });
15
+ expect(parsed.args[0]).toBe('./shot.png');
16
+ expect(parsed.options.display).toBe(2);
17
+ expect(parsed.options.region).toBe('0,0,120,80');
18
+ });
19
+ test('parses scroll amount and coordinates', () => {
20
+ 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');
29
+ });
30
+ });
package/dist/cli.d.ts CHANGED
@@ -1,2 +1,6 @@
1
- export {};
1
+ import type { UseComputerBridge } from './types.js';
2
+ export declare function createCli({ bridge }?: {
3
+ bridge?: UseComputerBridge;
4
+ }): import("goke").Goke<{}>;
5
+ export declare function runCli(): void;
2
6
  //# sourceMappingURL=cli.d.ts.map
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":"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"}