@lardexx/arenacode 2.9.0 → 2.11.1
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 +68 -12
- package/package.json +2 -2
- package/src/automation/linux.js +2 -40
- package/src/automation/mac.js +657 -0
- package/src/automation/png.js +47 -0
- package/src/automation.js +20 -3
- package/src/cli.js +34 -4
- package/src/config.js +1 -0
- package/src/context.js +234 -0
- package/src/local.js +121 -13
- package/src/meta.js +5 -18
- package/src/tui/app.js +49 -2
package/README.md
CHANGED
|
@@ -170,8 +170,15 @@ screenshots are `XGetImage` + `XGetPixel` encoded to PNG by the client itself (z
|
|
|
170
170
|
and the X events the server delivered are compared with the requested keycodes, chords, buttons and pixels. On Wayland
|
|
171
171
|
the XWayland display is used. `ui app launch` still starts the program's own `.desktop` command; nothing else is spawned.
|
|
172
172
|
|
|
173
|
-
**macOS
|
|
174
|
-
|
|
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.
|
|
175
182
|
|
|
176
183
|
**Ask and Proceed / Proceed.** The desktop's Settings switch, `ctrl+x a` here (also `/ask`, `/proceed`,
|
|
177
184
|
`--proceed`, and `arenacode config set approval proceed`). The default, **Ask and Proceed**, asks before commands,
|
|
@@ -254,7 +261,7 @@ The agent never touches your PC directly: it asks, and this client shows a card.
|
|
|
254
261
|
|
|
255
262
|
| Card | Source | Executed here |
|
|
256
263
|
|---|---|---|
|
|
257
|
-
| **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` |
|
|
258
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` |
|
|
259
266
|
| **plan** | `local-ui` `plan` | approve / reject / ask for a revision, right in the terminal |
|
|
260
267
|
| **file from the agent** | `local_file` | downloaded from `GET /file?raw=1` into the open folder |
|
|
@@ -264,17 +271,65 @@ Every file path is resolved **inside the open folder**; paths that escape it are
|
|
|
264
271
|
the folder itself. Local operations run with your user's permissions — `--confirm-writes` adds a confirmation for
|
|
265
272
|
mutating file operations, `--yes` skips confirmations for terminal commands.
|
|
266
273
|
|
|
267
|
-
###
|
|
274
|
+
### Project context
|
|
268
275
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
those cards are answered with an explicit error instead of silently doing nothing:
|
|
276
|
+
Every folder gets its own log, `context_<project>.md`, next to the files you are working on. It is the desktop
|
|
277
|
+
client's file, with the desktop's own template (mined from `ArenaCode-x64.exe`):
|
|
272
278
|
|
|
273
|
-
```
|
|
274
|
-
|
|
279
|
+
```markdown
|
|
280
|
+
# Contesto del progetto progetto-web
|
|
281
|
+
|
|
282
|
+
Sessione: 2026-11-02 21:45 (nome-del-bridge.tunnl.gg)
|
|
283
|
+
_Nessun task ancora._
|
|
284
|
+
<!-- agente:inizio -->
|
|
285
|
+
<!-- agente:fine -->
|
|
286
|
+
|
|
287
|
+
Aggiornato automaticamente da ArenaCode a fine di ogni job. La API key non è scritta qui.
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
The title, the session label, the placeholder and the footer follow the interface language (`it`, `en`, `es`).
|
|
291
|
+
What happens when:
|
|
292
|
+
|
|
293
|
+
* **The folder opens or a session starts** → the client creates the file if it is missing, and adds a `Sessione:`
|
|
294
|
+
line for the new session. An existing file is **never overwritten**: the agent's block lives inside it.
|
|
295
|
+
* **At the end of every task** the agent rewrites the block between `<!-- agente:inizio -->` and
|
|
296
|
+
`<!-- agente:fine -->` (date, task id, files changed, commits, decisions, what is still open, how it was checked —
|
|
297
|
+
at most 40 lines). The client saves it **at once, without Accept or Reject**: the card only shows what changed.
|
|
298
|
+
* **The first task of a new bridge session** carries the whole file as `project_context`, so the bridge writes it
|
|
299
|
+
to `runtime/project_context.txt` and tells the agent to read it in parts (`agentctl.py context --part N`). Later
|
|
300
|
+
tasks do not repeat it — a new bridge (`/health` → `started_at`) or a different folder makes it travel again.
|
|
301
|
+
The file is never summarised or truncated.
|
|
302
|
+
* **Commits** never include it: `git add -A` runs, then the sheet is unstaged — `git add -A, never
|
|
303
|
+
context_<project>.md`, as the desktop puts it.
|
|
304
|
+
|
|
305
|
+
`test/context.js` checks the template verbatim in the three languages, the "create once, never overwrite" rule,
|
|
306
|
+
the send-once-per-session rule and the git exclusion; the integration test checks that a real task creates the
|
|
307
|
+
sheet and that the bridge receives it.
|
|
308
|
+
|
|
309
|
+
### Shells
|
|
310
|
+
|
|
311
|
+
Terminal cards run in a real shell, and which one is not fixed:
|
|
312
|
+
|
|
313
|
+
| Platform | Shells |
|
|
314
|
+
|---|---|
|
|
315
|
+
| **Windows** | `powershell` (default), `pwsh`, `cmd`, `wsl`, `bash` (Git Bash when installed, otherwise the WSL bash) |
|
|
316
|
+
| **macOS / Linux** | `zsh`, `bash`, `sh`, `fish`, `dash`, `ksh` — or any absolute path |
|
|
317
|
+
|
|
318
|
+
The order is: the shell the **bridge** names in the `local-exec` request → the shell **you pinned** →
|
|
319
|
+
your **login shell** (`$SHELL`, e.g. `zsh` on macOS, `bash` on most Linux boxes; PowerShell on Windows).
|
|
320
|
+
Pin it with any of:
|
|
321
|
+
|
|
322
|
+
```
|
|
323
|
+
arenacode --shell zsh
|
|
324
|
+
arenacode config set shell zsh
|
|
325
|
+
/shell # in the TUI: pick from the shells installed on this machine
|
|
275
326
|
```
|
|
276
327
|
|
|
277
|
-
|
|
328
|
+
`sysinfo` reports `shell` and the full `shells` list, and the `!command` prompt line in the TUI runs in the
|
|
329
|
+
same shell as the agent's cards. Commands are passed as a single argv entry (`zsh -lc '<command>'`,
|
|
330
|
+
`cmd.exe /d /s /c '<command>'`, `powershell.exe -NoProfile -NonInteractive -Command '<command>'`), so quotes,
|
|
331
|
+
`$variables` and pipes are never re-quoted on the way through. `test/shell.js` checks every mapping and runs
|
|
332
|
+
real commands through `sh`/`bash`.
|
|
278
333
|
|
|
279
334
|
## How it talks to the bridge
|
|
280
335
|
|
|
@@ -319,7 +374,8 @@ release it on the bridge with `keys unbind <ID>`).
|
|
|
319
374
|
"pc_access": true,
|
|
320
375
|
"device": "",
|
|
321
376
|
"vision": false,
|
|
322
|
-
"idle_timeout": 180
|
|
377
|
+
"idle_timeout": 180,
|
|
378
|
+
"shell": ""
|
|
323
379
|
}
|
|
324
380
|
```
|
|
325
381
|
|
|
@@ -328,7 +384,7 @@ Node client cannot, so the key sits in that file — keep it readable only by yo
|
|
|
328
384
|
(`arenacode keys` on the bridge side: `python3 bridge_server.py keys create "laptop CLI"`).
|
|
329
385
|
|
|
330
386
|
Environment variables override the file: `ARENACODE_URL`, `ARENACODE_KEY`, `ARENACODE_FOLDER`,
|
|
331
|
-
`ARENACODE_CONFIG`.
|
|
387
|
+
`ARENACODE_SHELL`, `ARENACODE_CONFIG`.
|
|
332
388
|
|
|
333
389
|
## Troubleshooting
|
|
334
390
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lardexx/arenacode",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.11.1",
|
|
4
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",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"vendor/"
|
|
21
21
|
],
|
|
22
22
|
"scripts": {
|
|
23
|
-
"test": "node test/integration.js && node test/parity.js && node test/automation.js && node test/native.js"
|
|
23
|
+
"test": "node test/shell.js && node test/context.js && node test/integration.js && node test/parity.js && node test/automation.js && node test/native.js"
|
|
24
24
|
},
|
|
25
25
|
"keywords": [
|
|
26
26
|
"arenacode",
|
package/src/automation/linux.js
CHANGED
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
const fs = require('node:fs');
|
|
8
8
|
const os = require('node:os');
|
|
9
9
|
const path = require('node:path');
|
|
10
|
-
const zlib = require('node:zlib');
|
|
11
10
|
const { spawn } = require('node:child_process');
|
|
12
11
|
const { lib, fn } = require('./ffi');
|
|
12
|
+
const { encodePng } = require('./png');
|
|
13
13
|
|
|
14
14
|
const DISPLAY = () => process.env.DISPLAY || ':0';
|
|
15
15
|
|
|
@@ -356,8 +356,7 @@ function pressKeys(combo) {
|
|
|
356
356
|
requireX();
|
|
357
357
|
const parts = String(combo || '')
|
|
358
358
|
.toLowerCase()
|
|
359
|
-
.split(
|
|
360
|
-
.map((p) => p.trim())
|
|
359
|
+
.split(/[\s+]+/)
|
|
361
360
|
.filter(Boolean);
|
|
362
361
|
if (!parts.length) return { ok: false, error: `unreadable key combination: ${combo}` };
|
|
363
362
|
let repeat = 1;
|
|
@@ -553,43 +552,6 @@ function resizeWindow(handle, x, y, width, height) {
|
|
|
553
552
|
|
|
554
553
|
// ---------------------------------------------------------------- screenshot
|
|
555
554
|
|
|
556
|
-
function crc32(buf) {
|
|
557
|
-
let c = 0xffffffff;
|
|
558
|
-
for (let i = 0; i < buf.length; i += 1) {
|
|
559
|
-
c ^= buf[i];
|
|
560
|
-
for (let k = 0; k < 8; k += 1) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
|
|
561
|
-
}
|
|
562
|
-
return (c ^ 0xffffffff) >>> 0;
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
function pngChunk(type, data) {
|
|
566
|
-
const len = Buffer.alloc(4);
|
|
567
|
-
len.writeUInt32BE(data.length);
|
|
568
|
-
const body = Buffer.concat([Buffer.from(type, 'ascii'), data]);
|
|
569
|
-
const crc = Buffer.alloc(4);
|
|
570
|
-
crc.writeUInt32BE(crc32(body));
|
|
571
|
-
return Buffer.concat([len, body, crc]);
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
function encodePng(rgb, width, height) {
|
|
575
|
-
const raw = Buffer.alloc((width * 3 + 1) * height);
|
|
576
|
-
for (let y = 0; y < height; y += 1) {
|
|
577
|
-
raw[y * (width * 3 + 1)] = 0;
|
|
578
|
-
rgb.copy(raw, y * (width * 3 + 1) + 1, y * width * 3, (y + 1) * width * 3);
|
|
579
|
-
}
|
|
580
|
-
const ihdr = Buffer.alloc(13);
|
|
581
|
-
ihdr.writeUInt32BE(width, 0);
|
|
582
|
-
ihdr.writeUInt32BE(height, 4);
|
|
583
|
-
ihdr[8] = 8;
|
|
584
|
-
ihdr[9] = 2;
|
|
585
|
-
return Buffer.concat([
|
|
586
|
-
Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]),
|
|
587
|
-
pngChunk('IHDR', ihdr),
|
|
588
|
-
pngChunk('IDAT', zlib.deflateSync(raw, { level: 6 })),
|
|
589
|
-
pngChunk('IEND', Buffer.alloc(0)),
|
|
590
|
-
]);
|
|
591
|
-
}
|
|
592
|
-
|
|
593
555
|
function screenshot(dest) {
|
|
594
556
|
openDisplay();
|
|
595
557
|
const XGetImage = fn(
|