@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 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. No runtime dependencies.
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
- No extra install on **Windows**: PowerShell + `user32.dll` + the .NET UI Automation assemblies. The desktop client
152
- ships a `windows-use.dll` (exports `wu_call_json`, `wu_free`, `wu_version`) for the same job; the terminal client
153
- reproduces its behaviour instead of depending on it — the same JSON keys (`bounding_box`, `center`, `control_type`,
154
- `interactive_elements_text`, `scrollable_elements`, `screenshot_png_base64`), the same ideas: the UI Automation tree of
155
- the focused window for `ui state`, **unicode typing through `SendInput`** so the clipboard is untouched, extended-key
156
- flags for arrows/navigation, fuzzy window matching (`ui app --name chrome` finds *"Google Chrome — New Tab"*), and
157
- numbered labels drawn on the elements for `ui shot --annotated`.
158
-
159
- **macOS**: grant Accessibility to your terminal, and `brew install cliclick` for the mouse. **Linux** (X11):
160
- `sudo apt install xdotool wmctrl imagemagick`; on Wayland `xdotool` cannot inject input, so Computer Agent needs
161
- XWayland. On macOS/Linux the accessibility tree is not read: `ui state` returns windows and geometry as elements.
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` | PowerShell / `cmd` / WSL on Windows, `/bin/sh` elsewhere — asked before running unless `--yes` |
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
- ### Not available in a terminal
274
+ ### Shells
255
275
 
256
- The desktop client can drive the whole PC (mouse, keyboard, screenshots, app launching — `local-ui`
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
- ```json
261
- { "ok": false, "error": "operation \"screenshot\" is not available in the terminal client: the ArenaCode desktop client is required" }
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
- Everything else in the protocol is implemented.
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.8.0",
4
- "description": "Terminal client for ArenaCode: talks to an arena-bridge, streams the Agent Mode timeline, and runs local commands and file operations on your PC. Node port of the ArenaCode desktop client.",
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 };