usecomputer 0.0.3 → 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/README.md +324 -0
- package/build.zig +95 -11
- package/build.zig.zon +5 -0
- package/dist/bridge-contract.test.js +61 -67
- 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 +19 -5
- package/src/bridge-contract.test.ts +68 -73
- 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 +1966 -270
- package/zig/src/main.zig +382 -0
- package/zig/src/scroll.zig +213 -0
- package/zig/src/window.zig +123 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,73 @@
|
|
|
4
4
|
|
|
5
5
|
All notable changes to `usecomputer` will be documented in this file.
|
|
6
6
|
|
|
7
|
+
## 0.1.0
|
|
8
|
+
|
|
9
|
+
1. **Standalone executable** — `usecomputer` now ships as a self-contained binary.
|
|
10
|
+
Install once and run anywhere without needing Node.js at runtime:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install -g usecomputer
|
|
14
|
+
usecomputer screenshot ./shot.png --json
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
2. **Linux X11 screenshot support** — capture screens on Linux desktops via XShm
|
|
18
|
+
(with automatic fallback to XGetImage on XWayland). Returns the same JSON
|
|
19
|
+
output shape as macOS:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
usecomputer screenshot ./shot.png --json
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
3. **Screenshot coord-map and scaling** — screenshots are scaled so the longest edge
|
|
26
|
+
is at most 1568 px (model-friendly size). Output includes a `coordMap` field
|
|
27
|
+
for accurate pointer remapping:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
usecomputer screenshot ./shot.png --json
|
|
31
|
+
# use the emitted coord-map for all subsequent pointer commands
|
|
32
|
+
usecomputer click -x 400 -y 220 --coord-map "0,0,1600,900,1568,882"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
4. **New `debug-point` command** — validate a click target before clicking. Captures
|
|
36
|
+
a screenshot and draws a red marker at the mapped coordinate:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
usecomputer debug-point -x 400 -y 220 --coord-map "0,0,1600,900,1568,882"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
5. **Keyboard synthesis** — new `type` and `press` commands for text input and key
|
|
43
|
+
chords:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
usecomputer type "hello from usecomputer"
|
|
47
|
+
usecomputer press "cmd+s"
|
|
48
|
+
usecomputer press "down" --count 10 --delay 30
|
|
49
|
+
cat ./notes.txt | usecomputer type --stdin --chunk-size 4000
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
6. **Native scroll support** — scroll in any direction at any position:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
usecomputer scroll --direction down --amount 5
|
|
56
|
+
usecomputer scroll --direction up --amount 3 -x 800 -y 400
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
7. **Library exports** — import `usecomputer` as a Node.js library to reuse all
|
|
60
|
+
commands in your own agent harness:
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
import * as usecomputer from 'usecomputer'
|
|
64
|
+
|
|
65
|
+
const shot = await usecomputer.screenshot({ path: './shot.png', display: null, window: null, region: null, annotate: null })
|
|
66
|
+
const coordMap = usecomputer.parseCoordMapOrThrow(shot.coordMap)
|
|
67
|
+
await usecomputer.click({ point: usecomputer.mapPointFromCoordMap({ point: { x: 400, y: 220 }, coordMap }), button: 'left', count: 1 })
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
8. **OpenAI and Anthropic computer-use examples** — README now includes full
|
|
71
|
+
agentic loop examples for both providers showing screenshot → action → result
|
|
72
|
+
cycles.
|
|
73
|
+
|
|
7
74
|
## 0.0.3
|
|
8
75
|
|
|
9
76
|
- Implement real screenshot capture + PNG file writing on macOS.
|
package/README.md
CHANGED
|
@@ -7,6 +7,14 @@
|
|
|
7
7
|
It can move the mouse, click, drag, and query cursor position using native
|
|
8
8
|
Quartz events through a Zig N-API module.
|
|
9
9
|
|
|
10
|
+
Keyboard synthesis (`type` and `press`) is also available. The native backend
|
|
11
|
+
includes platform-specific key injection paths for macOS, Windows, and Linux
|
|
12
|
+
X11.
|
|
13
|
+
|
|
14
|
+
The package also exports the native commands as plain library functions, so you
|
|
15
|
+
can `import * as usecomputer from "usecomputer"` and reuse the same screenshot,
|
|
16
|
+
mouse, keyboard, and coord-map behavior from Node.js.
|
|
17
|
+
|
|
10
18
|
## Install
|
|
11
19
|
|
|
12
20
|
```bash
|
|
@@ -24,8 +32,321 @@ npm install -g usecomputer
|
|
|
24
32
|
usecomputer mouse position --json
|
|
25
33
|
usecomputer mouse move -x 500 -y 500
|
|
26
34
|
usecomputer click -x 500 -y 500 --button left --count 1
|
|
35
|
+
usecomputer type "hello"
|
|
36
|
+
usecomputer press "cmd+s"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Library usage
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import * as usecomputer from 'usecomputer'
|
|
43
|
+
|
|
44
|
+
const screenshot = await usecomputer.screenshot({
|
|
45
|
+
path: './tmp/shot.png',
|
|
46
|
+
display: null,
|
|
47
|
+
window: null,
|
|
48
|
+
region: null,
|
|
49
|
+
annotate: null,
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
const coordMap = usecomputer.parseCoordMapOrThrow(screenshot.coordMap)
|
|
53
|
+
const point = usecomputer.mapPointFromCoordMap({
|
|
54
|
+
point: { x: 400, y: 220 },
|
|
55
|
+
coordMap,
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
await usecomputer.click({
|
|
59
|
+
point,
|
|
60
|
+
button: 'left',
|
|
61
|
+
count: 1,
|
|
62
|
+
})
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
These exported functions intentionally mirror the native command shapes used by
|
|
66
|
+
the Zig N-API module. Optional native fields are passed as `null` when absent.
|
|
67
|
+
|
|
68
|
+
## OpenAI computer tool example
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import fs from 'node:fs'
|
|
72
|
+
import * as usecomputer from 'usecomputer'
|
|
73
|
+
|
|
74
|
+
async function sendComputerScreenshot() {
|
|
75
|
+
const screenshot = await usecomputer.screenshot({
|
|
76
|
+
path: './tmp/computer-tool.png',
|
|
77
|
+
display: null,
|
|
78
|
+
window: null,
|
|
79
|
+
region: null,
|
|
80
|
+
annotate: null,
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
screenshot,
|
|
85
|
+
imageBase64: await fs.promises.readFile(screenshot.path, 'base64'),
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function runComputerAction(action, coordMap) {
|
|
90
|
+
if (action.type === 'click') {
|
|
91
|
+
await usecomputer.click({
|
|
92
|
+
point: usecomputer.mapPointFromCoordMap({
|
|
93
|
+
point: { x: action.x, y: action.y },
|
|
94
|
+
coordMap: usecomputer.parseCoordMapOrThrow(coordMap),
|
|
95
|
+
}),
|
|
96
|
+
button: action.button ?? 'left',
|
|
97
|
+
count: 1,
|
|
98
|
+
})
|
|
99
|
+
return
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (action.type === 'double_click') {
|
|
103
|
+
await usecomputer.click({
|
|
104
|
+
point: usecomputer.mapPointFromCoordMap({
|
|
105
|
+
point: { x: action.x, y: action.y },
|
|
106
|
+
coordMap: usecomputer.parseCoordMapOrThrow(coordMap),
|
|
107
|
+
}),
|
|
108
|
+
button: action.button ?? 'left',
|
|
109
|
+
count: 2,
|
|
110
|
+
})
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (action.type === 'scroll') {
|
|
115
|
+
await usecomputer.scroll({
|
|
116
|
+
direction: action.scrollY && action.scrollY < 0 ? 'up' : 'down',
|
|
117
|
+
amount: Math.abs(action.scrollY ?? 0),
|
|
118
|
+
at: typeof action.x === 'number' && typeof action.y === 'number'
|
|
119
|
+
? usecomputer.mapPointFromCoordMap({
|
|
120
|
+
point: { x: action.x, y: action.y },
|
|
121
|
+
coordMap: usecomputer.parseCoordMapOrThrow(coordMap),
|
|
122
|
+
})
|
|
123
|
+
: null,
|
|
124
|
+
})
|
|
125
|
+
return
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (action.type === 'keypress') {
|
|
129
|
+
await usecomputer.press({
|
|
130
|
+
key: action.keys.join('+'),
|
|
131
|
+
count: 1,
|
|
132
|
+
delayMs: null,
|
|
133
|
+
})
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (action.type === 'type') {
|
|
138
|
+
await usecomputer.typeText({
|
|
139
|
+
text: action.text,
|
|
140
|
+
delayMs: null,
|
|
141
|
+
})
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Anthropic computer use example
|
|
147
|
+
|
|
148
|
+
Anthropic's computer tool uses action names like `left_click`, `double_click`,
|
|
149
|
+
`mouse_move`, `key`, `type`, `scroll`, and `screenshot`. `usecomputer`
|
|
150
|
+
provides the execution layer for those actions.
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
import fs from 'node:fs'
|
|
154
|
+
import Anthropic from '@anthropic-ai/sdk'
|
|
155
|
+
import type {
|
|
156
|
+
BetaToolResultBlockParam,
|
|
157
|
+
BetaToolUseBlock,
|
|
158
|
+
} from '@anthropic-ai/sdk/resources/beta/messages/messages'
|
|
159
|
+
import * as usecomputer from 'usecomputer'
|
|
160
|
+
|
|
161
|
+
const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY })
|
|
162
|
+
|
|
163
|
+
const message = await anthropic.beta.messages.create({
|
|
164
|
+
model: 'claude-opus-4-6',
|
|
165
|
+
max_tokens: 1024,
|
|
166
|
+
tools: [
|
|
167
|
+
{
|
|
168
|
+
type: 'computer_20251124',
|
|
169
|
+
name: 'computer',
|
|
170
|
+
display_width_px: 1024,
|
|
171
|
+
display_height_px: 768,
|
|
172
|
+
display_number: 1,
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
messages: [{ role: 'user', content: 'Open Safari and search for usecomputer.' }],
|
|
176
|
+
betas: ['computer-use-2025-11-24'],
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
for (const block of message.content) {
|
|
180
|
+
if (block.type !== 'tool_use' || block.name !== 'computer') {
|
|
181
|
+
continue
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const toolUse = block as BetaToolUseBlock
|
|
185
|
+
await usecomputer.screenshot({
|
|
186
|
+
path: './tmp/claude-current-screen.png',
|
|
187
|
+
display: null,
|
|
188
|
+
window: null,
|
|
189
|
+
region: null,
|
|
190
|
+
annotate: null,
|
|
191
|
+
})
|
|
192
|
+
const coordinate = Array.isArray(toolUse.input.coordinate)
|
|
193
|
+
? toolUse.input.coordinate
|
|
194
|
+
: null
|
|
195
|
+
const point = coordinate
|
|
196
|
+
? { x: coordinate[0] ?? 0, y: coordinate[1] ?? 0 }
|
|
197
|
+
: null
|
|
198
|
+
|
|
199
|
+
switch (toolUse.input.action) {
|
|
200
|
+
case 'screenshot': {
|
|
201
|
+
break
|
|
202
|
+
}
|
|
203
|
+
case 'left_click': {
|
|
204
|
+
if (point) {
|
|
205
|
+
await usecomputer.click({ point, button: 'left', count: 1 })
|
|
206
|
+
}
|
|
207
|
+
break
|
|
208
|
+
}
|
|
209
|
+
case 'double_click': {
|
|
210
|
+
if (point) {
|
|
211
|
+
await usecomputer.click({ point, button: 'left', count: 2 })
|
|
212
|
+
}
|
|
213
|
+
break
|
|
214
|
+
}
|
|
215
|
+
case 'mouse_move': {
|
|
216
|
+
if (point) {
|
|
217
|
+
await usecomputer.mouseMove(point)
|
|
218
|
+
}
|
|
219
|
+
break
|
|
220
|
+
}
|
|
221
|
+
case 'type': {
|
|
222
|
+
if (typeof toolUse.input.text === 'string') {
|
|
223
|
+
await usecomputer.typeText({ text: toolUse.input.text, delayMs: null })
|
|
224
|
+
}
|
|
225
|
+
break
|
|
226
|
+
}
|
|
227
|
+
case 'key': {
|
|
228
|
+
if (typeof toolUse.input.text === 'string') {
|
|
229
|
+
await usecomputer.press({ key: toolUse.input.text, count: 1, delayMs: null })
|
|
230
|
+
}
|
|
231
|
+
break
|
|
232
|
+
}
|
|
233
|
+
case 'scroll': {
|
|
234
|
+
await usecomputer.scroll({
|
|
235
|
+
direction: toolUse.input.scroll_direction === 'up' || toolUse.input.scroll_direction === 'down' || toolUse.input.scroll_direction === 'left' || toolUse.input.scroll_direction === 'right'
|
|
236
|
+
? toolUse.input.scroll_direction
|
|
237
|
+
: 'down',
|
|
238
|
+
amount: typeof toolUse.input.scroll_amount === 'number' ? toolUse.input.scroll_amount : 3,
|
|
239
|
+
at: point,
|
|
240
|
+
})
|
|
241
|
+
break
|
|
242
|
+
}
|
|
243
|
+
default: {
|
|
244
|
+
throw new Error(`Unsupported Claude computer action: ${String(toolUse.input.action)}`)
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const afterActionScreenshot = await usecomputer.screenshot({
|
|
249
|
+
path: './tmp/claude-computer-tool.png',
|
|
250
|
+
display: null,
|
|
251
|
+
window: null,
|
|
252
|
+
region: null,
|
|
253
|
+
annotate: null,
|
|
254
|
+
})
|
|
255
|
+
const imageBase64 = await fs.promises.readFile(afterActionScreenshot.path, 'base64')
|
|
256
|
+
const toolResult: BetaToolResultBlockParam = {
|
|
257
|
+
type: 'tool_result',
|
|
258
|
+
tool_use_id: toolUse.id,
|
|
259
|
+
content: [
|
|
260
|
+
{
|
|
261
|
+
type: 'image',
|
|
262
|
+
source: {
|
|
263
|
+
type: 'base64',
|
|
264
|
+
media_type: 'image/png',
|
|
265
|
+
data: imageBase64,
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
],
|
|
269
|
+
}
|
|
270
|
+
// Append toolResult to the next user message in your agent loop.
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## Screenshot scaling and coord-map
|
|
275
|
+
|
|
276
|
+
`usecomputer screenshot` always scales the output image so the longest edge is
|
|
277
|
+
at most `1568` px. This keeps screenshots in a model-friendly size for
|
|
278
|
+
computer-use agents.
|
|
279
|
+
|
|
280
|
+
Screenshot output includes:
|
|
281
|
+
|
|
282
|
+
- `desktopIndex` (display index used for capture)
|
|
283
|
+
- `coordMap` in the form `captureX,captureY,captureWidth,captureHeight,imageWidth,imageHeight`
|
|
284
|
+
- `hint` with usage text for coordinate mapping
|
|
285
|
+
|
|
286
|
+
Always pass the exact `--coord-map` value emitted by `usecomputer screenshot`
|
|
287
|
+
to pointer commands when you are clicking coordinates from that screenshot.
|
|
288
|
+
This maps screenshot-space coordinates back to real screen coordinates:
|
|
289
|
+
|
|
290
|
+
```bash
|
|
291
|
+
usecomputer screenshot ./shot.png --json
|
|
292
|
+
usecomputer click -x 400 -y 220 --coord-map "0,0,1600,900,1568,882"
|
|
293
|
+
usecomputer mouse move -x 100 -y 80 --coord-map "0,0,1600,900,1568,882"
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
To validate a target before clicking, use `debug-point`. It takes the same
|
|
297
|
+
coordinates and `--coord-map`, captures a fresh full-desktop screenshot, and
|
|
298
|
+
draws a red marker where the click would land. When `--coord-map` is present,
|
|
299
|
+
it captures that same region so the overlay matches the screenshot you are
|
|
300
|
+
targeting:
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
usecomputer debug-point -x 400 -y 220 --coord-map "0,0,1600,900,1568,882"
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
## Keyboard commands
|
|
307
|
+
|
|
308
|
+
### Type text
|
|
309
|
+
|
|
310
|
+
```bash
|
|
311
|
+
# Short text
|
|
312
|
+
usecomputer type "hello from usecomputer"
|
|
313
|
+
|
|
314
|
+
# Type from stdin (good for multiline or very long text)
|
|
315
|
+
cat ./notes.txt | usecomputer type --stdin --chunk-size 4000 --chunk-delay 15
|
|
316
|
+
|
|
317
|
+
# Simulate slower typing for apps that drop fast input
|
|
318
|
+
usecomputer type "hello" --delay 20
|
|
27
319
|
```
|
|
28
320
|
|
|
321
|
+
`--delay` is the per-character delay in milliseconds.
|
|
322
|
+
|
|
323
|
+
For very long text, prefer `--stdin` + `--chunk-size` so shell argument limits
|
|
324
|
+
and app input buffers are less likely to cause dropped characters.
|
|
325
|
+
|
|
326
|
+
### Press keys and shortcuts
|
|
327
|
+
|
|
328
|
+
```bash
|
|
329
|
+
# Single key
|
|
330
|
+
usecomputer press "enter"
|
|
331
|
+
|
|
332
|
+
# Chords
|
|
333
|
+
usecomputer press "cmd+s"
|
|
334
|
+
usecomputer press "cmd+shift+p"
|
|
335
|
+
usecomputer press "ctrl+s"
|
|
336
|
+
|
|
337
|
+
# Repeats
|
|
338
|
+
usecomputer press "down" --count 10 --delay 30
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
Modifier aliases: `cmd`/`command`/`meta`, `ctrl`/`control`, `alt`/`option`,
|
|
342
|
+
`shift`, `fn`.
|
|
343
|
+
|
|
344
|
+
Platform note:
|
|
345
|
+
|
|
346
|
+
- macOS: `cmd` maps to Command.
|
|
347
|
+
- Windows/Linux: `cmd` maps to Win/Super.
|
|
348
|
+
- For app shortcuts that should work on Windows/Linux too, prefer `ctrl+...`.
|
|
349
|
+
|
|
29
350
|
## Coordinate options
|
|
30
351
|
|
|
31
352
|
Commands that target coordinates accept `-x` and `-y` flags:
|
|
@@ -34,6 +355,9 @@ Commands that target coordinates accept `-x` and `-y` flags:
|
|
|
34
355
|
- `usecomputer hover -x <n> -y <n>`
|
|
35
356
|
- `usecomputer mouse move -x <n> -y <n>`
|
|
36
357
|
|
|
358
|
+
`mouse move` is optional before `click` when click coordinates are already
|
|
359
|
+
provided.
|
|
360
|
+
|
|
37
361
|
Legacy coordinate forms are also accepted where available.
|
|
38
362
|
|
|
39
363
|
## Display index options
|
package/build.zig
CHANGED
|
@@ -1,36 +1,64 @@
|
|
|
1
|
-
// Build script for usecomputer
|
|
1
|
+
// Build script for usecomputer — produces both:
|
|
2
|
+
// 1. Dynamic library (.node) for N-API consumption from Node.js
|
|
3
|
+
// 2. Standalone executable CLI (no Node.js required, uses zeke)
|
|
2
4
|
|
|
3
5
|
const std = @import("std");
|
|
4
6
|
const napigen = @import("napigen");
|
|
5
7
|
|
|
6
8
|
const LIB_NAME = "usecomputer";
|
|
7
9
|
|
|
10
|
+
/// Link platform-specific libraries needed by the native core.
|
|
11
|
+
fn linkPlatformDeps(mod: *std.Build.Module, target_os: std.Target.Os.Tag) void {
|
|
12
|
+
if (target_os == .macos) {
|
|
13
|
+
mod.linkFramework("CoreGraphics", .{});
|
|
14
|
+
mod.linkFramework("CoreFoundation", .{});
|
|
15
|
+
mod.linkFramework("ImageIO", .{});
|
|
16
|
+
}
|
|
17
|
+
if (target_os == .linux) {
|
|
18
|
+
mod.linkSystemLibrary("X11", .{});
|
|
19
|
+
mod.linkSystemLibrary("Xext", .{});
|
|
20
|
+
mod.linkSystemLibrary("Xtst", .{});
|
|
21
|
+
mod.linkSystemLibrary("png", .{});
|
|
22
|
+
}
|
|
23
|
+
if (target_os == .windows) {
|
|
24
|
+
mod.linkSystemLibrary("user32", .{});
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
8
28
|
pub fn build(b: *std.Build) void {
|
|
9
29
|
const target = b.standardTargetOptions(.{});
|
|
10
30
|
const optimize = b.standardOptimizeOption(.{});
|
|
31
|
+
const target_os = target.result.os.tag;
|
|
32
|
+
|
|
33
|
+
// ── N-API dynamic library (.node) ──
|
|
34
|
+
|
|
35
|
+
// Build options for lib.zig: enable_napigen controls N-API glue
|
|
36
|
+
const lib_options = b.addOptions();
|
|
37
|
+
lib_options.addOption(bool, "enable_napigen", true);
|
|
38
|
+
const lib_options_mod = lib_options.createModule();
|
|
11
39
|
|
|
12
40
|
const lib_mod = b.createModule(.{
|
|
13
41
|
.root_source_file = b.path("zig/src/lib.zig"),
|
|
14
42
|
.target = target,
|
|
15
43
|
.optimize = optimize,
|
|
16
44
|
});
|
|
45
|
+
lib_mod.addImport("build_options", lib_options_mod);
|
|
17
46
|
lib_mod.addImport("napigen", b.dependency("napigen", .{}).module("napigen"));
|
|
18
|
-
|
|
19
|
-
.
|
|
20
|
-
|
|
21
|
-
|
|
47
|
+
if (target_os == .macos) {
|
|
48
|
+
if (b.lazyDependency("zig_objc", .{
|
|
49
|
+
.target = target,
|
|
50
|
+
.optimize = optimize,
|
|
51
|
+
})) |dep| {
|
|
52
|
+
lib_mod.addImport("objc", dep.module("objc"));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
22
55
|
|
|
23
56
|
const lib = b.addLibrary(.{
|
|
24
57
|
.name = LIB_NAME,
|
|
25
58
|
.root_module = lib_mod,
|
|
26
59
|
.linkage = .dynamic,
|
|
27
60
|
});
|
|
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
|
-
}
|
|
61
|
+
linkPlatformDeps(lib.root_module, target_os);
|
|
34
62
|
|
|
35
63
|
napigen.setup(lib);
|
|
36
64
|
b.installArtifact(lib);
|
|
@@ -38,16 +66,72 @@ pub fn build(b: *std.Build) void {
|
|
|
38
66
|
const copy_node_step = b.addInstallLibFile(lib.getEmittedBin(), LIB_NAME ++ ".node");
|
|
39
67
|
b.getInstallStep().dependOn(©_node_step.step);
|
|
40
68
|
|
|
69
|
+
// ── Standalone executable CLI ──
|
|
70
|
+
//
|
|
71
|
+
// Uses a separate copy of lib.zig WITHOUT napigen so the executable
|
|
72
|
+
// doesn't try to link N-API symbols (those only exist in Node.js).
|
|
73
|
+
|
|
74
|
+
const exe_options = b.addOptions();
|
|
75
|
+
exe_options.addOption(bool, "enable_napigen", false);
|
|
76
|
+
const exe_options_mod = exe_options.createModule();
|
|
77
|
+
|
|
78
|
+
const exe_lib_mod = b.createModule(.{
|
|
79
|
+
.root_source_file = b.path("zig/src/lib.zig"),
|
|
80
|
+
.target = target,
|
|
81
|
+
.optimize = optimize,
|
|
82
|
+
});
|
|
83
|
+
exe_lib_mod.addImport("build_options", exe_options_mod);
|
|
84
|
+
if (target_os == .macos) {
|
|
85
|
+
if (b.lazyDependency("zig_objc", .{
|
|
86
|
+
.target = target,
|
|
87
|
+
.optimize = optimize,
|
|
88
|
+
})) |dep| {
|
|
89
|
+
exe_lib_mod.addImport("objc", dep.module("objc"));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const exe_mod = b.createModule(.{
|
|
94
|
+
.root_source_file = b.path("zig/src/main.zig"),
|
|
95
|
+
.target = target,
|
|
96
|
+
.optimize = optimize,
|
|
97
|
+
});
|
|
98
|
+
exe_mod.addImport("usecomputer_lib", exe_lib_mod);
|
|
99
|
+
exe_mod.addImport("zeke", b.dependency("zeke", .{
|
|
100
|
+
.target = target,
|
|
101
|
+
.optimize = optimize,
|
|
102
|
+
}).module("zeke"));
|
|
103
|
+
|
|
104
|
+
const exe = b.addExecutable(.{
|
|
105
|
+
.name = LIB_NAME,
|
|
106
|
+
.root_module = exe_mod,
|
|
107
|
+
});
|
|
108
|
+
linkPlatformDeps(exe.root_module, target_os);
|
|
109
|
+
b.installArtifact(exe);
|
|
110
|
+
|
|
111
|
+
const run_exe = b.addRunArtifact(exe);
|
|
112
|
+
if (b.args) |args| {
|
|
113
|
+
run_exe.addArgs(args);
|
|
114
|
+
}
|
|
115
|
+
const run_step = b.step("run", "Run the CLI");
|
|
116
|
+
run_step.dependOn(&run_exe.step);
|
|
117
|
+
|
|
118
|
+
// ── Tests ──
|
|
119
|
+
|
|
120
|
+
const test_options = b.addOptions();
|
|
121
|
+
test_options.addOption(bool, "enable_napigen", false);
|
|
122
|
+
|
|
41
123
|
const test_mod = b.createModule(.{
|
|
42
124
|
.root_source_file = b.path("zig/src/lib.zig"),
|
|
43
125
|
.target = target,
|
|
44
126
|
.optimize = optimize,
|
|
45
127
|
});
|
|
128
|
+
test_mod.addImport("build_options", test_options.createModule());
|
|
46
129
|
|
|
47
130
|
const test_step = b.step("test", "Run Zig unit tests");
|
|
48
131
|
const test_exe = b.addTest(.{
|
|
49
132
|
.root_module = test_mod,
|
|
50
133
|
});
|
|
134
|
+
linkPlatformDeps(test_exe.root_module, target_os);
|
|
51
135
|
const run_test = b.addRunArtifact(test_exe);
|
|
52
136
|
test_step.dependOn(&run_test.step);
|
|
53
137
|
}
|
package/build.zig.zon
CHANGED
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
.zig_objc = .{
|
|
12
12
|
.url = "git+https://github.com/mitchellh/zig-objc?ref=main#27d0e03242e7ee6842bf8a86d2e0bb1f586a9847",
|
|
13
13
|
.hash = "zig_objc-0.0.0-Ir_Sp7oUAQC3JpeR9EGUFGcHRSx_33IehitnjBCy-CwD",
|
|
14
|
+
.lazy = true,
|
|
15
|
+
},
|
|
16
|
+
.zeke = .{
|
|
17
|
+
.url = "https://github.com/remorses/zeke/archive/refs/heads/main.tar.gz",
|
|
18
|
+
.hash = "zeke-0.1.0-fnPIzP2mAADBDhCqMNuyU5TV7PEG9rEb2GDDjwMXCZYN",
|
|
14
19
|
},
|
|
15
20
|
},
|
|
16
21
|
.paths = .{
|