usecomputer 0.0.1 → 0.0.2

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 +18 -0
  2. package/README.md +37 -0
  3. package/bin.js +3 -1
  4. package/build.zig +52 -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 +286 -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 +329 -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 +367 -0
@@ -0,0 +1,50 @@
1
+ // Tests for parsing coordinates, regions, directions, and keyboard modifiers.
2
+
3
+ import { describe, expect, test } from 'vitest'
4
+ import { parseDirection, parseModifiers, parsePoint, parseRegion } from './command-parsers.js'
5
+
6
+ describe('command parsers', () => {
7
+ test('parses x,y points', () => {
8
+ const result = parsePoint('100,200')
9
+ expect(result).toMatchInlineSnapshot(`
10
+ {
11
+ "x": 100,
12
+ "y": 200,
13
+ }
14
+ `)
15
+ })
16
+
17
+ test('rejects invalid points', () => {
18
+ const result = parsePoint('100')
19
+ expect(result instanceof Error).toBe(true)
20
+ expect(result instanceof Error ? result.message : '').toMatchInlineSnapshot(`"Invalid point "100". Expected x,y"`)
21
+ })
22
+
23
+ test('parses x,y,width,height regions', () => {
24
+ const result = parseRegion('10,20,300,400')
25
+ expect(result).toMatchInlineSnapshot(`
26
+ {
27
+ "height": 400,
28
+ "width": 300,
29
+ "x": 10,
30
+ "y": 20,
31
+ }
32
+ `)
33
+ })
34
+
35
+ test('parses modifiers with normalization', () => {
36
+ expect(parseModifiers(' CMD,shift, alt ')).toMatchInlineSnapshot(`
37
+ [
38
+ "cmd",
39
+ "shift",
40
+ "alt",
41
+ ]
42
+ `)
43
+ })
44
+
45
+ test('validates scroll direction', () => {
46
+ expect(parseDirection('down')).toBe('down')
47
+ const invalid = parseDirection('diagonal')
48
+ expect(invalid instanceof Error).toBe(true)
49
+ })
50
+ })
@@ -0,0 +1,60 @@
1
+ // Parser helpers for CLI values such as coordinates, regions, and key modifiers.
2
+
3
+ import type { Point, Region, ScrollDirection } from './types.js'
4
+
5
+ export function parsePoint(input: string): Error | Point {
6
+ const parts = input.split(',').map((value) => {
7
+ return value.trim()
8
+ })
9
+ if (parts.length !== 2) {
10
+ return new Error(`Invalid point \"${input}\". Expected x,y`)
11
+ }
12
+ const x = Number(parts[0])
13
+ const y = Number(parts[1])
14
+ if (!Number.isFinite(x) || !Number.isFinite(y)) {
15
+ return new Error(`Invalid point \"${input}\". Coordinates must be numbers`)
16
+ }
17
+ return { x, y }
18
+ }
19
+
20
+ export function parseRegion(input: string): Error | Region {
21
+ const parts = input.split(',').map((value) => {
22
+ return value.trim()
23
+ })
24
+ if (parts.length !== 4) {
25
+ return new Error(`Invalid region \"${input}\". Expected x,y,width,height`)
26
+ }
27
+ const x = Number(parts[0])
28
+ const y = Number(parts[1])
29
+ const width = Number(parts[2])
30
+ const height = Number(parts[3])
31
+ if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(width) || !Number.isFinite(height)) {
32
+ return new Error(`Invalid region \"${input}\". Values must be numbers`)
33
+ }
34
+ if (width <= 0 || height <= 0) {
35
+ return new Error(`Invalid region \"${input}\". Width and height must be greater than 0`)
36
+ }
37
+ return { x, y, width, height }
38
+ }
39
+
40
+ export function parseModifiers(input?: string): string[] {
41
+ if (!input) {
42
+ return []
43
+ }
44
+ return input
45
+ .split(',')
46
+ .map((value) => {
47
+ return value.trim().toLowerCase()
48
+ })
49
+ .filter((value) => {
50
+ return value.length > 0
51
+ })
52
+ }
53
+
54
+ export function parseDirection(input: string): Error | ScrollDirection {
55
+ const normalized = input.trim().toLowerCase()
56
+ if (normalized === 'up' || normalized === 'down' || normalized === 'left' || normalized === 'right') {
57
+ return normalized
58
+ }
59
+ return new Error(`Invalid direction \"${input}\". Expected up, down, left, or right`)
60
+ }
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
- // usecomputer fast computer automation CLI for AI agents.
2
- // Controls any desktop via accessibility APIs, mouse, keyboard, and screen capture.
3
- // Cross-platform: macOS, Linux, Windows.
1
+ // Public API exports for usecomputer parser, bridge, and CLI modules.
4
2
 
5
- export {}
3
+ export { createCli } from './cli.js'
4
+ export { createBridge, createBridgeFromNative } from './bridge.js'
5
+ export * from './types.js'
6
+ export * from './command-parsers.js'
@@ -0,0 +1,131 @@
1
+ // Optional host smoke test for the real native click command.
2
+
3
+ import { describe, expect, test } from 'vitest'
4
+ import { native } from './native-lib.js'
5
+
6
+ const runNativeSmoke = process.env.USECOMPUTER_NATIVE_SMOKE === '1'
7
+
8
+ describe('native click smoke', () => {
9
+ const smokeTest = runNativeSmoke ? test : test.skip
10
+
11
+ smokeTest('executes click command without crashing', () => {
12
+ expect(native).toBeTruthy()
13
+ if (!native) {
14
+ return
15
+ }
16
+
17
+ const response = native.execute(
18
+ 'click',
19
+ JSON.stringify({
20
+ point: { x: 10, y: 10 },
21
+ button: 'left',
22
+ count: 1,
23
+ }),
24
+ )
25
+
26
+ const parsed = JSON.parse(response) as { ok: boolean; data?: null; error?: string }
27
+ expect(parsed).toMatchInlineSnapshot(`
28
+ {
29
+ "data": null,
30
+ "ok": true,
31
+ }
32
+ `)
33
+ expect(typeof parsed.ok).toBe('boolean')
34
+ })
35
+
36
+ smokeTest('executes mouse-move/down/up/position/hover/drag without crashing', () => {
37
+ expect(native).toBeTruthy()
38
+ if (!native) {
39
+ return
40
+ }
41
+
42
+ const moveResponse = JSON.parse(
43
+ native.execute(
44
+ 'mouse-move',
45
+ JSON.stringify({
46
+ x: 20,
47
+ y: 20,
48
+ }),
49
+ ),
50
+ ) as { ok: boolean; data?: null; error?: string }
51
+
52
+ const downResponse = JSON.parse(
53
+ native.execute('mouse-down', JSON.stringify({ button: 'left' })),
54
+ ) as { ok: boolean; data?: null; error?: string }
55
+
56
+ const upResponse = JSON.parse(
57
+ native.execute('mouse-up', JSON.stringify({ button: 'left' })),
58
+ ) as { ok: boolean; data?: null; error?: string }
59
+
60
+ const positionResponse = JSON.parse(
61
+ native.execute('mouse-position', JSON.stringify({})),
62
+ ) as { ok: boolean; data?: { x: number; y: number }; error?: string }
63
+
64
+ const hoverResponse = JSON.parse(
65
+ native.execute(
66
+ 'hover',
67
+ JSON.stringify({
68
+ x: 24,
69
+ y: 24,
70
+ }),
71
+ ),
72
+ ) as { ok: boolean; data?: null; error?: string }
73
+
74
+ const dragResponse = JSON.parse(
75
+ native.execute(
76
+ 'drag',
77
+ JSON.stringify({
78
+ from: { x: 24, y: 24 },
79
+ to: { x: 30, y: 30 },
80
+ button: 'left',
81
+ durationMs: 10,
82
+ }),
83
+ ),
84
+ ) as { ok: boolean; data?: null; error?: string }
85
+
86
+ expect({
87
+ moveResponse,
88
+ downResponse,
89
+ upResponse,
90
+ positionResponse,
91
+ hoverResponse,
92
+ dragResponse,
93
+ }).toMatchInlineSnapshot(`
94
+ {
95
+ "downResponse": {
96
+ "data": null,
97
+ "ok": true,
98
+ },
99
+ "dragResponse": {
100
+ "data": null,
101
+ "ok": true,
102
+ },
103
+ "hoverResponse": {
104
+ "data": null,
105
+ "ok": true,
106
+ },
107
+ "moveResponse": {
108
+ "data": null,
109
+ "ok": true,
110
+ },
111
+ "positionResponse": {
112
+ "data": {
113
+ "x": 20,
114
+ "y": 20,
115
+ },
116
+ "ok": true,
117
+ },
118
+ "upResponse": {
119
+ "data": null,
120
+ "ok": true,
121
+ },
122
+ }
123
+ `)
124
+ expect(moveResponse.ok).toBe(true)
125
+ expect(downResponse.ok).toBe(true)
126
+ expect(upResponse.ok).toBe(true)
127
+ expect(positionResponse.ok).toBe(true)
128
+ expect(hoverResponse.ok).toBe(true)
129
+ expect(dragResponse.ok).toBe(true)
130
+ })
131
+ })
@@ -0,0 +1,38 @@
1
+ // ESM native loader for the usecomputer Zig addon using createRequire.
2
+
3
+ import os from 'node:os'
4
+ import { createRequire } from 'node:module'
5
+
6
+ const require = createRequire(import.meta.url)
7
+
8
+ export interface NativeModule {
9
+ execute(command: string, payloadJson: string): string
10
+ }
11
+
12
+ function loadCandidate(path: string): NativeModule | null {
13
+ try {
14
+ return require(path) as NativeModule
15
+ } catch {
16
+ return null
17
+ }
18
+ }
19
+
20
+ function loadNativeModule(): NativeModule | null {
21
+ const dev = loadCandidate('../zig-out/lib/usecomputer.node')
22
+ if (dev) {
23
+ return dev
24
+ }
25
+
26
+ const platform = os.platform()
27
+ const arch = os.arch()
28
+ const target = `${platform}-${arch}`
29
+
30
+ const packaged = loadCandidate(`../dist/${target}/usecomputer.node`)
31
+ if (packaged) {
32
+ return packaged
33
+ }
34
+
35
+ return null
36
+ }
37
+
38
+ export const native = loadNativeModule()
package/src/types.ts ADDED
@@ -0,0 +1,87 @@
1
+ // Shared types for usecomputer command parsing and backend bridge calls.
2
+
3
+ export type MouseButton = 'left' | 'right' | 'middle'
4
+
5
+ export type ScrollDirection = 'up' | 'down' | 'left' | 'right'
6
+
7
+ export type Point = {
8
+ x: number
9
+ y: number
10
+ }
11
+
12
+ export type Region = {
13
+ x: number
14
+ y: number
15
+ width: number
16
+ height: number
17
+ }
18
+
19
+ export type DisplayInfo = {
20
+ id: number
21
+ name: string
22
+ x: number
23
+ y: number
24
+ width: number
25
+ height: number
26
+ scale: number
27
+ isPrimary: boolean
28
+ }
29
+
30
+ export type ScreenshotInput = {
31
+ path?: string
32
+ display?: number
33
+ region?: Region
34
+ annotate?: boolean
35
+ }
36
+
37
+ export type ScreenshotResult = {
38
+ path: string
39
+ }
40
+
41
+ export type ClickInput = {
42
+ point: Point
43
+ button: MouseButton
44
+ count: number
45
+ modifiers: string[]
46
+ }
47
+
48
+ export type TypeInput = {
49
+ text: string
50
+ delayMs?: number
51
+ }
52
+
53
+ export type PressInput = {
54
+ key: string
55
+ count: number
56
+ delayMs?: number
57
+ }
58
+
59
+ export type ScrollInput = {
60
+ direction: ScrollDirection
61
+ amount: number
62
+ at?: Point
63
+ }
64
+
65
+ export type DragInput = {
66
+ from: Point
67
+ to: Point
68
+ durationMs?: number
69
+ button: MouseButton
70
+ }
71
+
72
+ export interface UseComputerBridge {
73
+ screenshot(input: ScreenshotInput): Promise<ScreenshotResult>
74
+ click(input: ClickInput): Promise<void>
75
+ typeText(input: TypeInput): Promise<void>
76
+ press(input: PressInput): Promise<void>
77
+ scroll(input: ScrollInput): Promise<void>
78
+ drag(input: DragInput): Promise<void>
79
+ hover(input: Point): Promise<void>
80
+ mouseMove(input: Point): Promise<void>
81
+ mouseDown(input: { button: MouseButton }): Promise<void>
82
+ mouseUp(input: { button: MouseButton }): Promise<void>
83
+ mousePosition(): Promise<Point>
84
+ displayList(): Promise<DisplayInfo[]>
85
+ clipboardGet(): Promise<string>
86
+ clipboardSet(input: { text: string }): Promise<void>
87
+ }