@lardexx/arenacode 2.9.0 → 2.11.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
@@ -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**: grant Accessibility to your terminal, and `brew install cliclick` for the mouse. On macOS/Linux the
174
- accessibility tree is not read: `ui state` returns windows and geometry as elements.
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` | 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` |
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
- ### Not available in a terminal
274
+ ### Project context
268
275
 
269
- The desktop client can drive the whole PC (mouse, keyboard, screenshots, app launching — `local-ui`
270
- `control` / `type` / `keystroke` / `screenshot` / `app` / `state` / `apps` / `multi_*`). A terminal cannot, so
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
- ```json
274
- { "ok": false, "error": "operation \"screenshot\" is not available in the terminal client: the ArenaCode desktop client is required" }
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
- Everything else in the protocol is implemented.
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.9.0",
3
+ "version": "2.11.0",
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",
@@ -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(