@lardexx/arenacode 2.8.0 → 2.10.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/README.md +56 -22
- package/package.json +8 -4
- package/src/automation/ffi.js +62 -0
- package/src/automation/linux.js +771 -0
- package/src/automation/mac.js +657 -0
- package/src/automation/png.js +47 -0
- package/src/automation/win-dll.js +493 -0
- package/src/automation.js +88 -0
- package/src/cli.js +5 -2
- package/src/config.js +1 -0
- package/src/local.js +106 -11
- package/src/tui/app.js +40 -2
- package/vendor/windows-use/windows-use.dll +0 -0
package/README.md
CHANGED
|
@@ -30,7 +30,8 @@ Published as [`@lardexx/arenacode`](https://www.npmjs.com/package/@lardexx/arena
|
|
|
30
30
|
is blocked by npm's similarity check against the existing `arena-code` package, so the scope is required. The
|
|
31
31
|
command is plain `arenacode` in every case.
|
|
32
32
|
|
|
33
|
-
Requires Node.js 18 or newer.
|
|
33
|
+
Requires Node.js 18 or newer. One runtime dependency, `koffi` (prebuilt FFI bindings for Windows, macOS and Linux,
|
|
34
|
+
no compiler needed): it is how the client reaches `windows-use.dll` on Windows and `libX11`/`libXtst` on Linux.
|
|
34
35
|
|
|
35
36
|
## Terminal UI
|
|
36
37
|
|
|
@@ -148,17 +149,36 @@ from the same bridge request the desktop app answers:
|
|
|
148
149
|
| `ui apps` | the Start menu (Windows), `/Applications` (macOS), `.desktop` entries (Linux) |
|
|
149
150
|
| `ui multi-select`, `ui multi-edit` | built from the primitives above |
|
|
150
151
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
152
|
+
**Windows** runs the *same* DLL the desktop client runs. `windows-use.dll` ships inside this package
|
|
153
|
+
(`vendor/windows-use/windows-use.dll`, extracted from `ArenaCode-x64.exe`'s resources) and is called over FFI with
|
|
154
|
+
[koffi](https://koffi.dev) — `wu_call_json` / `wu_free` / `wu_version` — so nothing has to be installed and nothing is
|
|
155
|
+
reimplemented: the UI Automation tree, `SendInput` unicode typing, extended keys for arrows/navigation, Start-menu apps,
|
|
156
|
+
annotated screenshots and fuzzy window matching are all the desktop's own code. The tools and argument names the client
|
|
157
|
+
sends are the ones in the DLL's symbol table (`click_tool`, `move_tool`, `mouse_button_tool`, `scroll_tool`, `type_tool`,
|
|
158
|
+
`shortcut_tool`, `wait_tool`, `app_tool`, `apps_list_tool`, `desktop_state`), and the answers are the same JSON the
|
|
159
|
+
desktop returns (`{"ok":true,"result":…}`, `bounding_box`, `center`, `control_type`, `interactive_elements_text`,
|
|
160
|
+
`screenshot_png_base64` …). `ARENACODE_WINDOWS_USE_DLL` points the client at another copy; if the file is missing it is
|
|
161
|
+
carved out of an installed `ArenaCode-x64.exe` (`ARENACODE_EXE`) by the client's own PE resource reader.
|
|
162
|
+
|
|
163
|
+
**Linux** talks to the X server directly: `libX11` + `libXtst` through the same FFI layer, **no external tools** — no
|
|
164
|
+
`xdotool`, no `wmctrl`, no `scrot`, no ImageMagick. The pointer is moved with `XTestFakeMotionEvent`, clicks and the wheel
|
|
165
|
+
are `XTestFakeButtonEvent` with buttons 1–5, keys are keysyms mapped to keycodes with `XGetKeyboardMapping`, characters
|
|
166
|
+
outside the keymap borrow an unused keycode with `XChangeKeyboardMapping` and give it back a moment later, windows come
|
|
167
|
+
from `XQueryTree` + `XGetGeometry` + `XTranslateCoordinates` + `_NET_WM_NAME`, focus from `XSetInputFocus`, and
|
|
168
|
+
screenshots are `XGetImage` + `XGetPixel` encoded to PNG by the client itself (zlib). This is what makes it testable:
|
|
169
|
+
`node test/native.js` runs it against a real X server (Xvfb is enough) — a window is created, the client types into it
|
|
170
|
+
and the X events the server delivered are compared with the requested keycodes, chords, buttons and pixels. On Wayland
|
|
171
|
+
the XWayland display is used. `ui app launch` still starts the program's own `.desktop` command; nothing else is spawned.
|
|
172
|
+
|
|
173
|
+
**macOS** is native too, again with no helper binaries: mouse with `CGEventCreateMouseEvent` + `CGEventPost`
|
|
174
|
+
(left/right/middle, double and triple click through the click-state field, drag interpolated), keyboard with
|
|
175
|
+
`CGEventCreateKeyboardEvent` plus modifier *flags* (`cmd`, `ctrl`, `alt`, `shift`) and Apple's fixed `kVK_*`
|
|
176
|
+
keycodes, typing through `CGEventKeyboardSetUnicodeString` so any character or emoji goes in without a layout,
|
|
177
|
+
windows from `CGWindowListCopyWindowInfo` (number, bounds, owner pid), screenshots from `CGDisplayCreateImage`
|
|
178
|
+
converted from BGRA to PNG by the client. `brew install cliclick` is gone. Only what CoreGraphics cannot do —
|
|
179
|
+
launch an app, bring a window to the front, resize it — still uses `osascript`, which ships with macOS. Grant the
|
|
180
|
+
terminal **Accessibility** (input) and **Screen Recording** (pixels) in System Settings → Privacy & Security.
|
|
181
|
+
On macOS/Linux the accessibility tree is not read: `ui state` returns windows and geometry as elements.
|
|
162
182
|
|
|
163
183
|
**Ask and Proceed / Proceed.** The desktop's Settings switch, `ctrl+x a` here (also `/ask`, `/proceed`,
|
|
164
184
|
`--proceed`, and `arenacode config set approval proceed`). The default, **Ask and Proceed**, asks before commands,
|
|
@@ -241,7 +261,7 @@ The agent never touches your PC directly: it asks, and this client shows a card.
|
|
|
241
261
|
|
|
242
262
|
| Card | Source | Executed here |
|
|
243
263
|
|---|---|---|
|
|
244
|
-
| **terminal** | `POST /task/<id>/local-exec` |
|
|
264
|
+
| **terminal** | `POST /task/<id>/local-exec` | your shell — zsh/bash/sh/fish on macOS and Linux, PowerShell/pwsh/cmd/WSL/Git Bash on Windows (see [Shells](#shells)) — asked before running unless `--yes` |
|
|
245
265
|
| **files** | `local-fs` | `ls`, `stat`, `read`, `write`, `replace`, `find`, `mkdir`, `mv`, `rm`, `pull`, `git_status`, `git_log`, `git_diff`, `git_commit`, `git_revert`, `git_publish` |
|
|
246
266
|
| **plan** | `local-ui` `plan` | approve / reject / ask for a revision, right in the terminal |
|
|
247
267
|
| **file from the agent** | `local_file` | downloaded from `GET /file?raw=1` into the open folder |
|
|
@@ -251,17 +271,30 @@ Every file path is resolved **inside the open folder**; paths that escape it are
|
|
|
251
271
|
the folder itself. Local operations run with your user's permissions — `--confirm-writes` adds a confirmation for
|
|
252
272
|
mutating file operations, `--yes` skips confirmations for terminal commands.
|
|
253
273
|
|
|
254
|
-
###
|
|
274
|
+
### Shells
|
|
255
275
|
|
|
256
|
-
|
|
257
|
-
`control` / `type` / `keystroke` / `screenshot` / `app` / `state` / `apps` / `multi_*`). A terminal cannot, so
|
|
258
|
-
those cards are answered with an explicit error instead of silently doing nothing:
|
|
276
|
+
Terminal cards run in a real shell, and which one is not fixed:
|
|
259
277
|
|
|
260
|
-
|
|
261
|
-
|
|
278
|
+
| Platform | Shells |
|
|
279
|
+
|---|---|
|
|
280
|
+
| **Windows** | `powershell` (default), `pwsh`, `cmd`, `wsl`, `bash` (Git Bash when installed, otherwise the WSL bash) |
|
|
281
|
+
| **macOS / Linux** | `zsh`, `bash`, `sh`, `fish`, `dash`, `ksh` — or any absolute path |
|
|
282
|
+
|
|
283
|
+
The order is: the shell the **bridge** names in the `local-exec` request → the shell **you pinned** →
|
|
284
|
+
your **login shell** (`$SHELL`, e.g. `zsh` on macOS, `bash` on most Linux boxes; PowerShell on Windows).
|
|
285
|
+
Pin it with any of:
|
|
286
|
+
|
|
287
|
+
```
|
|
288
|
+
arenacode --shell zsh
|
|
289
|
+
arenacode config set shell zsh
|
|
290
|
+
/shell # in the TUI: pick from the shells installed on this machine
|
|
262
291
|
```
|
|
263
292
|
|
|
264
|
-
|
|
293
|
+
`sysinfo` reports `shell` and the full `shells` list, and the `!command` prompt line in the TUI runs in the
|
|
294
|
+
same shell as the agent's cards. Commands are passed as a single argv entry (`zsh -lc '<command>'`,
|
|
295
|
+
`cmd.exe /d /s /c '<command>'`, `powershell.exe -NoProfile -NonInteractive -Command '<command>'`), so quotes,
|
|
296
|
+
`$variables` and pipes are never re-quoted on the way through. `test/shell.js` checks every mapping and runs
|
|
297
|
+
real commands through `sh`/`bash`.
|
|
265
298
|
|
|
266
299
|
## How it talks to the bridge
|
|
267
300
|
|
|
@@ -306,7 +339,8 @@ release it on the bridge with `keys unbind <ID>`).
|
|
|
306
339
|
"pc_access": true,
|
|
307
340
|
"device": "",
|
|
308
341
|
"vision": false,
|
|
309
|
-
"idle_timeout": 180
|
|
342
|
+
"idle_timeout": 180,
|
|
343
|
+
"shell": ""
|
|
310
344
|
}
|
|
311
345
|
```
|
|
312
346
|
|
|
@@ -315,7 +349,7 @@ Node client cannot, so the key sits in that file — keep it readable only by yo
|
|
|
315
349
|
(`arenacode keys` on the bridge side: `python3 bridge_server.py keys create "laptop CLI"`).
|
|
316
350
|
|
|
317
351
|
Environment variables override the file: `ARENACODE_URL`, `ARENACODE_KEY`, `ARENACODE_FOLDER`,
|
|
318
|
-
`ARENACODE_CONFIG`.
|
|
352
|
+
`ARENACODE_SHELL`, `ARENACODE_CONFIG`.
|
|
319
353
|
|
|
320
354
|
## Troubleshooting
|
|
321
355
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lardexx/arenacode",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "Terminal client for ArenaCode: talks to an arena-bridge, streams the Agent Mode timeline, and runs local commands
|
|
3
|
+
"version": "2.10.0",
|
|
4
|
+
"description": "Terminal client for ArenaCode: talks to an arena-bridge, streams the Agent Mode timeline, and runs local commands, file operations and computer automation on your PC (windows-use.dll on Windows, Xlib/XTest on Linux, osascript on macOS). Node port of the ArenaCode desktop client.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "commonjs",
|
|
7
7
|
"bin": {
|
|
@@ -16,10 +16,11 @@
|
|
|
16
16
|
"src/",
|
|
17
17
|
"docs/",
|
|
18
18
|
"README.md",
|
|
19
|
-
"LICENSE"
|
|
19
|
+
"LICENSE",
|
|
20
|
+
"vendor/"
|
|
20
21
|
],
|
|
21
22
|
"scripts": {
|
|
22
|
-
"test": "node test/integration.js && node test/parity.js && node test/automation.js"
|
|
23
|
+
"test": "node test/shell.js && node test/integration.js && node test/parity.js && node test/automation.js && node test/native.js"
|
|
23
24
|
},
|
|
24
25
|
"keywords": [
|
|
25
26
|
"arenacode",
|
|
@@ -30,5 +31,8 @@
|
|
|
30
31
|
],
|
|
31
32
|
"publishConfig": {
|
|
32
33
|
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"koffi": "^3.3.6"
|
|
33
37
|
}
|
|
34
38
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// koffi is the only native piece: it ships prebuilt binaries for Windows, macOS and
|
|
4
|
+
// Linux, so the client needs no compiler and no helper executables (xdotool, cliclick,
|
|
5
|
+
// scrot …). Everything below talks to the OS libraries directly.
|
|
6
|
+
|
|
7
|
+
let koffi = null;
|
|
8
|
+
let loadError = null;
|
|
9
|
+
try {
|
|
10
|
+
// eslint-disable-next-line global-require
|
|
11
|
+
koffi = require('koffi');
|
|
12
|
+
} catch (err) {
|
|
13
|
+
loadError = err;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function available() {
|
|
17
|
+
return Boolean(koffi);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function requireFfi() {
|
|
21
|
+
if (!koffi) {
|
|
22
|
+
throw new Error(
|
|
23
|
+
`native bindings unavailable (koffi): ${loadError ? loadError.message : 'not installed'}`
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
return koffi;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const cache = new Map();
|
|
30
|
+
|
|
31
|
+
/** lua: loads (and caches) a shared library by name. */
|
|
32
|
+
function lib(name) {
|
|
33
|
+
if (cache.has(name)) return cache.get(name);
|
|
34
|
+
const k = requireFfi();
|
|
35
|
+
let handle = null;
|
|
36
|
+
try {
|
|
37
|
+
handle = k.load(name);
|
|
38
|
+
} catch (err) {
|
|
39
|
+
cache.set(name, null);
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
cache.set(name, handle);
|
|
43
|
+
return handle;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Defines a function once per library: fn(lib, 'sig', 'name') */
|
|
47
|
+
function fn(library, name, signature) {
|
|
48
|
+
const holder = lib(library);
|
|
49
|
+
if (!holder) return null;
|
|
50
|
+
const key = `${library}#${name}`;
|
|
51
|
+
if (cache.has(key)) return cache.get(key);
|
|
52
|
+
let defined = null;
|
|
53
|
+
try {
|
|
54
|
+
defined = holder.func(signature);
|
|
55
|
+
} catch {
|
|
56
|
+
defined = null;
|
|
57
|
+
}
|
|
58
|
+
cache.set(key, defined);
|
|
59
|
+
return defined;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
module.exports = { available, lib, fn, requireFfi, loadError };
|