@lardexx/arenacode 0.0.0-stage → 2.5.3
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/LICENSE +21 -0
- package/README.md +288 -2
- package/bin/arenacode.js +19 -0
- package/docs/ANALYSIS.md +201 -0
- package/docs/PUBLISHING.md +65 -0
- package/docs/TUI.md +114 -0
- package/docs/tui-approve.html +28 -0
- package/docs/tui-approve.png +0 -0
- package/docs/tui-home.html +28 -0
- package/docs/tui-home.png +0 -0
- package/docs/tui-palette.html +28 -0
- package/docs/tui-palette.png +0 -0
- package/docs/tui-plan.html +28 -0
- package/docs/tui-plan.png +0 -0
- package/docs/tui-preview.html +28 -0
- package/docs/tui-session.png +0 -0
- package/docs/tui-slash.html +28 -0
- package/package.json +32 -4
- package/src/bridge.js +135 -0
- package/src/cli.js +720 -0
- package/src/config.js +141 -0
- package/src/local.js +507 -0
- package/src/meta.js +143 -0
- package/src/prompts/agent-prompt.md +237 -0
- package/src/tui/app.js +1368 -0
- package/src/tui/dialogs.js +136 -0
- package/src/tui/keys.js +134 -0
- package/src/tui/logo.js +54 -0
- package/src/tui/render.js +256 -0
- package/src/tui/term.js +93 -0
- package/src/tui/theme.js +246 -0
- package/src/tui/widgets.js +374 -0
- package/src/turn.js +213 -0
- package/src/ui.js +130 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 arenacode contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,289 @@
|
|
|
1
|
-
#
|
|
1
|
+
# arenacode
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Terminal client for **ArenaCode** — a full-screen TUI (or a plain CLI, when you prefer) that talks to an
|
|
4
|
+
[arena-bridge](https://github.com/) over HTTP, streams the Agent Mode timeline, and executes on your own machine
|
|
5
|
+
the actions the agent asks for (terminal commands, file operations, plan approval, files the agent sends back).
|
|
6
|
+
|
|
7
|
+
It is a Node port of the ArenaCode 2.5.2 desktop client (`ArenaCode-x64.exe`): same wire protocol, same `meta`
|
|
8
|
+
payload, same local cards. **The bridge is untouched** — this is a client only, so it works with any existing
|
|
9
|
+
`arena-bridge` 2.5.x (Python) instance, local or exposed through a tunnl.gg URL.
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
you ──▶ arenacode CLI ──▶ arena-bridge ──▶ agent
|
|
13
|
+
(this package) (unchanged) (Arena, or any agent on agentctl)
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install -g arenacode
|
|
20
|
+
arenacode --version # 2.5.2
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Or without installing:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npx arenacode ask "list the files in my folder"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Requires Node.js 18 or newer. No runtime dependencies.
|
|
30
|
+
|
|
31
|
+
## Terminal UI
|
|
32
|
+
|
|
33
|
+
The interface is a copy of [opencode](https://opencode.ai)'s TUI — same screen shape, same widgets, same keybinds —
|
|
34
|
+
driving this bridge instead of an LLM provider. Nothing was approximated from screenshots alone: the layout was
|
|
35
|
+
rebuilt from opencode 1.19.5's own source (`packages/tui/src/component/prompt`, `routes/session`, `ui/dialog`,
|
|
36
|
+
`logo.tsx`, `theme/assets/*.json`) and checked against frames captured from the installed binary in a pty.
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
arenacode # opens the TUI (the default when stdout is a terminal)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Home screen — two-tone block wordmark, centred prompt, tips, folder and version in the footer:
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
▄
|
|
46
|
+
█▀▀█ █▀▀█ █▀▀█ █▀▀▄ █▀▀█ █▀▀▀ █▀▀█ █▀▀█ █▀▀█
|
|
47
|
+
█ █ █▀▀▄ █▀▀▀ █ █ █ █ █ █ █ █ █ █▀▀▀
|
|
48
|
+
▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀
|
|
49
|
+
|
|
50
|
+
/connect bridge offline at http://127.0.0.1:9
|
|
51
|
+
┃
|
|
52
|
+
┃ Ask anything… "Read package.json and add a lint script"
|
|
53
|
+
┃
|
|
54
|
+
┃ Build · arenacode bridge 2.5.2
|
|
55
|
+
╹▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
|
56
|
+
tab agents ctrl+p commands
|
|
57
|
+
|
|
58
|
+
● Tip Press ctrl+p for every command
|
|
59
|
+
|
|
60
|
+
~/progetto v2.5.2
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
A session — user blocks on a `┃` border, assistant parts indented three cells, tool calls as `$ command` cards,
|
|
64
|
+
`▣ Build · model · 1.2s` when the turn ends, `esc interrupt` while it runs:
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
┃
|
|
68
|
+
┃ read hello.txt and run a command
|
|
69
|
+
┃
|
|
70
|
+
⠹ Thinking: Looking at the folder
|
|
71
|
+
┃
|
|
72
|
+
┃ → Reading file: hello.txt (23 B)
|
|
73
|
+
┃ hello from the project
|
|
74
|
+
┃
|
|
75
|
+
┃ $ echo from-pc (exit 0)
|
|
76
|
+
┃ from-pc
|
|
77
|
+
┃
|
|
78
|
+
▣ Build · arenacode · 0.4s
|
|
79
|
+
┃
|
|
80
|
+
┃ Ask anything… "Summarise what this project does"
|
|
81
|
+
┃
|
|
82
|
+
┃ Build · arenacode bridge 2.5.2
|
|
83
|
+
╹▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
|
84
|
+
tab agents ctrl+p commands
|
|
85
|
+
~/progetto 4cabe1 · v2.5.2
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Real frames captured from a pty, colours included — as rendered images
|
|
89
|
+
[home](docs/tui-home.png) · [a session](docs/tui-session.png) · [the palette](docs/tui-palette.png),
|
|
90
|
+
and as HTML snapshots [home](docs/tui-home.html) · [session](docs/tui-preview.html) ·
|
|
91
|
+
[palette](docs/tui-palette.html) · [slash commands](docs/tui-slash.html) ·
|
|
92
|
+
[confirm dialog](docs/tui-approve.html) · [plan approval](docs/tui-plan.html).
|
|
93
|
+
How the copy is built and tested: [`docs/TUI.md`](docs/TUI.md).
|
|
94
|
+
|
|
95
|
+
**Keys** (opencode's defaults, leader is `ctrl+x`):
|
|
96
|
+
|
|
97
|
+
| Key | Action |
|
|
98
|
+
|---|---|
|
|
99
|
+
| `enter` | send · `shift+enter` newline |
|
|
100
|
+
| `esc` | interrupt the turn · clear the input · quit |
|
|
101
|
+
| `tab` | switch agent ↔ plan |
|
|
102
|
+
| `ctrl+p` | command palette (`↑↓` select, `enter` run, `esc` close) |
|
|
103
|
+
| `/` | slash commands with an inline list above the prompt |
|
|
104
|
+
| `@file` | attach a file from the open folder |
|
|
105
|
+
| `!command` | run a command locally |
|
|
106
|
+
| `↑` / `↓` | prompt history (or move through the slash list) |
|
|
107
|
+
| `pgup` / `pgdn` | scroll the transcript |
|
|
108
|
+
| `ctrl+x` then | `q` quit · `n` new session · `l` task queue · `t` theme · `d` tool output · `b` sidebar · `s` stop |
|
|
109
|
+
| `ctrl+c` | interrupt, then quit |
|
|
110
|
+
|
|
111
|
+
**Dialogs.** Local actions open opencode-shaped dialogs — panel background, title on the left, `esc` on the right,
|
|
112
|
+
the selected row painted in the primary colour, hints underneath: `Run once / Don't run` for terminal commands,
|
|
113
|
+
`Approve / Reject / Revise` for the agent's plan, and file operations reported back to the bridge. `--yes` skips the
|
|
114
|
+
terminal confirmation, `--confirm-writes` adds one for writes and removals.
|
|
115
|
+
|
|
116
|
+
**Themes.** `ctrl+x t` cycles five palettes lifted from opencode's theme assets:
|
|
117
|
+
`arenacode` (opencode's peach accent), `tokyonight`, `catppuccin`, `gruvbox`, `nord`.
|
|
118
|
+
|
|
119
|
+
## Quick start
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
# 1. point the client at your bridge (URL + API key come from the bridge's runtime/connection.txt)
|
|
123
|
+
arenacode init
|
|
124
|
+
|
|
125
|
+
# 2. open the TUI in the folder you want the agent to work on
|
|
126
|
+
arenacode --folder ~/progetto
|
|
127
|
+
|
|
128
|
+
# 3. or stay in the plain CLI
|
|
129
|
+
arenacode ask "read package.json and add a lint script"
|
|
130
|
+
arenacode chat
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
`init` saves the URL, the API key and the open folder, then tests `/health` and `/auth/check` so you know the
|
|
134
|
+
bridge accepts the key before you send anything.
|
|
135
|
+
|
|
136
|
+
## Commands
|
|
137
|
+
|
|
138
|
+
| Command | What it does |
|
|
139
|
+
|---|---|
|
|
140
|
+
| `arenacode init` | configure URL, API key and open folder; test the connection |
|
|
141
|
+
| `arenacode config [show\|path\|set <k> <v>\|clear]` | read or change the saved configuration |
|
|
142
|
+
| `arenacode ping` | `/health` + `/auth/check`: version, public URL, queue, key |
|
|
143
|
+
| `arenacode doctor` | local checks: node, platform, folder, git, bridge, API key |
|
|
144
|
+
| `arenacode` (no args) | full-screen TUI — the default on a terminal |
|
|
145
|
+
| `arenacode ui` | full-screen TUI, explicitly |
|
|
146
|
+
| `arenacode ask "<instruction>"` | one task, streamed timeline, exit when it finishes |
|
|
147
|
+
| `arenacode chat` | interactive session (`/help` shows the in-session commands) |
|
|
148
|
+
| `arenacode stop [task-id]` | Stop: cancels the turn and the queued tasks on the bridge |
|
|
149
|
+
| `arenacode handshake` | vision/handshake check task, like the client's |
|
|
150
|
+
|
|
151
|
+
**Inspect the bridge**
|
|
152
|
+
|
|
153
|
+
| Command | What it does |
|
|
154
|
+
|---|---|
|
|
155
|
+
| `arenacode tasks [state]` | task queue (`all`, `claimable`, `pending`, `claimed`, `done`, `error`) |
|
|
156
|
+
| `arenacode status <task-id>` | state, claimant, result of one task |
|
|
157
|
+
| `arenacode events <task-id>` | replayed timeline of a task |
|
|
158
|
+
| `arenacode files [path]` | bridge workspace listing (`GET /fs`) |
|
|
159
|
+
| `arenacode cat <path>` | preview a text file in the bridge workspace (`GET /file`) |
|
|
160
|
+
| `arenacode attach <file…>` | upload files into the bridge workspace (`POST /upload`) |
|
|
161
|
+
|
|
162
|
+
**Options**
|
|
163
|
+
|
|
164
|
+
| Flag | Meaning |
|
|
165
|
+
|---|---|
|
|
166
|
+
| `--folder <dir>` | open folder: the project the agent reads and edits |
|
|
167
|
+
| `--url <url>` / `--key <key>` | override the saved bridge settings |
|
|
168
|
+
| `--mode agent\|plan` | task mode (default `agent`) |
|
|
169
|
+
| `--lang it\|en\|es` | language for prompts and cards (default `it`) |
|
|
170
|
+
| `--no-pc` | tell the agent `pc_access: false` (no terminal on this PC) |
|
|
171
|
+
| `--yes` | run local commands without asking |
|
|
172
|
+
| `--confirm-writes` | ask before `write` / `replace` / `rm` / `mv` in your folder |
|
|
173
|
+
| `--attach a,b` | upload files together with the task |
|
|
174
|
+
| `--idle-timeout <s>` | close the session after N idle seconds (default 180, like the desktop client) |
|
|
175
|
+
| `--json` | raw JSON output where available |
|
|
176
|
+
|
|
177
|
+
## What runs on your machine
|
|
178
|
+
|
|
179
|
+
The agent never touches your PC directly: it asks, and this client shows a card. With `--yes` (or by pressing
|
|
180
|
+
`r`) the card is executed locally and the result goes back to the bridge.
|
|
181
|
+
|
|
182
|
+
| Card | Source | Executed here |
|
|
183
|
+
|---|---|---|
|
|
184
|
+
| **terminal** | `POST /task/<id>/local-exec` | PowerShell / `cmd` / WSL on Windows, `/bin/sh` elsewhere — asked before running unless `--yes` |
|
|
185
|
+
| **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` |
|
|
186
|
+
| **plan** | `local-ui` `plan` | approve / reject / ask for a revision, right in the terminal |
|
|
187
|
+
| **file from the agent** | `local_file` | downloaded from `GET /file?raw=1` into the open folder |
|
|
188
|
+
| **wait / sysinfo** | `local-ui` | sleeps, or reports the local machine |
|
|
189
|
+
|
|
190
|
+
Every file path is resolved **inside the open folder**; paths that escape it are refused. `rm` refuses to delete
|
|
191
|
+
the folder itself. Local operations run with your user's permissions — `--confirm-writes` adds a confirmation for
|
|
192
|
+
mutating file operations, `--yes` skips confirmations for terminal commands.
|
|
193
|
+
|
|
194
|
+
### Not available in a terminal
|
|
195
|
+
|
|
196
|
+
The desktop client can drive the whole PC (mouse, keyboard, screenshots, app launching — `local-ui`
|
|
197
|
+
`control` / `type` / `keystroke` / `screenshot` / `app` / `state` / `apps` / `multi_*`). A terminal cannot, so
|
|
198
|
+
those cards are answered with an explicit error instead of silently doing nothing:
|
|
199
|
+
|
|
200
|
+
```json
|
|
201
|
+
{ "ok": false, "error": "operation \"screenshot\" is not available in the terminal client: the ArenaCode desktop client is required" }
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Everything else in the protocol is implemented.
|
|
205
|
+
|
|
206
|
+
## How it talks to the bridge
|
|
207
|
+
|
|
208
|
+
Same requests as the desktop client (see [`docs/ANALYSIS.md`](docs/ANALYSIS.md) for the reverse-engineered
|
|
209
|
+
details):
|
|
210
|
+
|
|
211
|
+
```
|
|
212
|
+
POST /task
|
|
213
|
+
{"instruction":"[ArenaCode mode: agent]\n…","wait":0,
|
|
214
|
+
"meta":{"client":"ArenaCode 2.5.2","os":"…","lang":"it","policy":"…","pc_access":true,
|
|
215
|
+
"git":{"available":true,"repo":true,"branch":"main"},
|
|
216
|
+
"workspace":{"root":"…","cwd":"…","shell":"…","pwsh7":false,"powershell":false,"wsl":false,
|
|
217
|
+
"active_file":"","selection":"","selection_line":0,"open_files":[],
|
|
218
|
+
"top_level":["src/","package.json"],"tools":{"git":true,"node":true}}},
|
|
219
|
+
"agent_prompt":"…", // the client's built-in prompt, sent with every task
|
|
220
|
+
"project_context":"…"} // content of context_<project>.md, when present
|
|
221
|
+
|
|
222
|
+
GET /task/<id>/events?format=json&since=<cursor>&wait=25 ← timeline (long-poll)
|
|
223
|
+
POST /task/<id>/local-result {"rid":"lx-…","output":"…","code":0}
|
|
224
|
+
POST /task/<id>/cancel ← Stop
|
|
225
|
+
GET /auth/check ← key valid on this PC
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Headers: `Authorization: Bearer <API key>`, `X-Arena-Device: <machine id>`, `Content-Type: application/json`.
|
|
229
|
+
The device header is what binds an API key to one PC (`403 device_mismatch` if it is already bound elsewhere:
|
|
230
|
+
release it on the bridge with `keys unbind <ID>`).
|
|
231
|
+
|
|
232
|
+
## Configuration
|
|
233
|
+
|
|
234
|
+
`arenacode config path` prints the file:
|
|
235
|
+
|
|
236
|
+
* Windows — `%APPDATA%\ArenaCode\config.json`
|
|
237
|
+
* macOS / Linux — `~/.arenacode/config.json`
|
|
238
|
+
|
|
239
|
+
```json
|
|
240
|
+
{
|
|
241
|
+
"url": "https://nome-del-bridge.tunnl.gg",
|
|
242
|
+
"key": "arn_…",
|
|
243
|
+
"folder": "/home/me/progetto",
|
|
244
|
+
"lang": "it",
|
|
245
|
+
"mode": "agent",
|
|
246
|
+
"pc_access": true,
|
|
247
|
+
"device": "",
|
|
248
|
+
"vision": false,
|
|
249
|
+
"idle_timeout": 180
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
The file is written with mode `0600`. The desktop client stores the key encrypted with Windows DPAPI; a portable
|
|
254
|
+
Node client cannot, so the key sits in that file — keep it readable only by you, and use a dedicated API key
|
|
255
|
+
(`arenacode keys` on the bridge side: `python3 bridge_server.py keys create "laptop CLI"`).
|
|
256
|
+
|
|
257
|
+
Environment variables override the file: `ARENACODE_URL`, `ARENACODE_KEY`, `ARENACODE_FOLDER`,
|
|
258
|
+
`ARENACODE_CONFIG`.
|
|
259
|
+
|
|
260
|
+
## Troubleshooting
|
|
261
|
+
|
|
262
|
+
| Symptom | Fix |
|
|
263
|
+
|---|---|
|
|
264
|
+
| `auth failed: unauthorized` | wrong API key — `arenacode config set key <key>` (from `runtime/connection.txt`) |
|
|
265
|
+
| `403 device_mismatch` | key bound to another PC — on the bridge: `python3 bridge_server.py keys unbind <ID>` |
|
|
266
|
+
| `bridge unreachable` | tunnel down or wrong URL — check `arenacode ping`, and `/health` on the bridge |
|
|
267
|
+
| agent never answers | the agent is not listening: run `bash listen.sh` in the bridge folder |
|
|
268
|
+
| everything hangs after a turn | `--idle-timeout` closes the session; check `arenacode status <id>` |
|
|
269
|
+
|
|
270
|
+
## Development
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
npm test # CLI integration: real Python bridge + fake agent
|
|
274
|
+
SCENARIO=approve node test/tui.js # TUI, terminal card confirmation
|
|
275
|
+
SCENARIO=plan node test/tui.js # TUI, plan approval
|
|
276
|
+
node test/tui.js # TUI: drives the real pty, checks the rendered frames
|
|
277
|
+
node tools/frame2html.js /tmp/tui-frames.raw docs/tui-preview.html --match done # frame -> HTML snapshot
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
The integration test spins up `bridge_server.py` from a private copy, runs a fake agent that claims the task,
|
|
281
|
+
then checks that the CLI answered the `local-fs` / `local-exec` cards with the right payloads, applied the file
|
|
282
|
+
write, rendered the timeline and bound the API key to the device.
|
|
283
|
+
|
|
284
|
+
Set `BRIDGE_DIR` to point at the bridge sources if they are not in `/home/user/bridge_extract/bridge`, and
|
|
285
|
+
`VERBOSE=1` to see bridge logs.
|
|
286
|
+
|
|
287
|
+
## License
|
|
288
|
+
|
|
289
|
+
MIT.
|
package/bin/arenacode.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const { main } = require('../src/cli');
|
|
5
|
+
|
|
6
|
+
// Piping into `head`/`less` closes stdout early: that is not an error.
|
|
7
|
+
process.stdout.on('error', (e) => {
|
|
8
|
+
if (e && e.code === 'EPIPE') process.exit(0);
|
|
9
|
+
throw e;
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
// Exit explicitly: undici keeps keep-alive sockets open, which would keep the
|
|
13
|
+
// process alive after the turn is finished.
|
|
14
|
+
main(process.argv.slice(2))
|
|
15
|
+
.then((code) => process.exit(typeof code === 'number' ? code : 0))
|
|
16
|
+
.catch((e) => {
|
|
17
|
+
process.stderr.write(`arenacode: ${e && e.stack ? e.stack : e}\n`);
|
|
18
|
+
process.exit(1);
|
|
19
|
+
});
|
package/docs/ANALYSIS.md
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# Reverse-engineering notes — `ArenaCode-x64.exe` (2.5.2)
|
|
2
|
+
|
|
3
|
+
What this document is: the evidence behind the port. Every claim about the desktop client's behaviour below is
|
|
4
|
+
backed by a static observation on the binary, the strings it embeds, or the payloads the bridge expects. Nothing
|
|
5
|
+
here was obtained by running the executable (it is a Windows GUI binary; the analysis host is Linux).
|
|
6
|
+
|
|
7
|
+
## 1. Target
|
|
8
|
+
|
|
9
|
+
| | |
|
|
10
|
+
|---|---|
|
|
11
|
+
| File | `ArenaCode-x64.exe` (inside `arenacode-x64.zip`, 1.85 MB zip / 5 372 416 B exe) |
|
|
12
|
+
| SHA-256 | `1be348946c5c101a0122941c9c1b0c48b831138b8bbc98ac9c6eba66ba6e9c54` |
|
|
13
|
+
| Format | PE32+ executable, x86-64, 10 sections, stripped to external PDB |
|
|
14
|
+
| Toolchain | MinGW/GCC + MSVCRT (`__wgetmainargs`, `_beginthreadex`, `mingw` style imports), Win32 GUI subsystem |
|
|
15
|
+
| Manifest | `<assemblyIdentity version="2.5.2.0" … name="ArenaCode" type="win32"/>` |
|
|
16
|
+
| Entropy | 6.30 file-wide (max block 6.40) → **not packed, not encrypted** |
|
|
17
|
+
| TLS | 3 callbacks in `.text` (`0x140135c80`, `0x140135c50`, `0x140148ea0`) — normal MinGW runtime setup |
|
|
18
|
+
|
|
19
|
+
## 2. How it was analysed
|
|
20
|
+
|
|
21
|
+
[REA](https://github.com/morluto/rea) 6.8.0 (`rea-agents` on npm), run with Node 22.23.4 — provider-free static
|
|
22
|
+
commands, since no Ghidra/Hopper/IDA instance was available for decompilation on this host:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
rea doctor # provider audit (no native engine present)
|
|
26
|
+
rea inspect-pe-imports ArenaCode-x64.exe --format json # ev_51542780…583e
|
|
27
|
+
rea inspect-byte-entropy ArenaCode-x64.exe --format json # ev_aca82405…eb14
|
|
28
|
+
rea inspect-pe-load-config ArenaCode-x64.exe --format json # ev_3d8aab65…1339
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Deep decompilation (`rea analyze`, `rea decompile`) failed closed with `provider_unavailable`: it needs
|
|
32
|
+
`GHIDRA_INSTALL_DIR` / Hopper / IDA. That is why the reconstruction below rests on imports, embedded wide
|
|
33
|
+
strings and the payload contract rather than on pseudocode. For a C/GUI client that is enough: the interesting
|
|
34
|
+
surface is the HTTP layer and the JSON it builds, and both are visible as literal format strings.
|
|
35
|
+
|
|
36
|
+
## 3. What the binary is
|
|
37
|
+
|
|
38
|
+
A Win32 GUI client (USER32 + COMCTL32 + GDI32/GDI+ widgets, `GetOpenFileNameW`, `SHBrowseForFolderW`,
|
|
39
|
+
drag-and-drop) that
|
|
40
|
+
|
|
41
|
+
* keeps its state in `%APPDATA%\ArenaCode\` — `config.ini`, `sessions\`, `chats\`;
|
|
42
|
+
* talks HTTPS to an **arena-bridge** with **WinHTTP** (`WINHTTP.dll`: `WinHttpOpen`, `WinHttpOpenRequest`,
|
|
43
|
+
`WinHttpSendRequest`, `WinHttpReadData`, …) plus `WS2_32` for sockets;
|
|
44
|
+
* executes things locally with `CreateProcessW` (+ `PeekNamedPipe`, `CreatePipe`), `ShellExecuteW`,
|
|
45
|
+
`\\WindowsPowerShell\\v1.0\\powershell.exe`;
|
|
46
|
+
* protects the stored API key with **DPAPI**: `CRYPT32.dll!CryptProtectData` / `CryptUnprotectData`, stored as
|
|
47
|
+
`secret_dpapi`;
|
|
48
|
+
* identifies the machine with `HKLM\SOFTWARE\Microsoft\Cryptography\MachineGuid` (`ADVAPI32` registry +
|
|
49
|
+
`SystemFunction036` = `RtlGenRandom`);
|
|
50
|
+
* carries a bundled CA store (curl's `ca-bundle.crt`) and a runtime-DLL loader (`LoadLibraryW`/`GetProcAddress`)
|
|
51
|
+
for the optional `windows-use` automation DLL — the binary contains the message *"windows-use DLL not
|
|
52
|
+
embedded in the program"*.
|
|
53
|
+
|
|
54
|
+
String evidence (wide strings, file offset ≈ `0x22b108` onward for the prompt):
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
%s\%s\config.ini %APPDATA%\ArenaCode\config.ini
|
|
58
|
+
MachineGuid SOFTWARE\Microsoft\Cryptography
|
|
59
|
+
secret_dpapi sessions chats
|
|
60
|
+
User-Agent: ArenaCode/1.0 application/json
|
|
61
|
+
Content-Type: Authorization: Bearer X-Arena-Device:
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## 4. HTTP protocol (as the client sends it)
|
|
65
|
+
|
|
66
|
+
| Method | Path | Body |
|
|
67
|
+
|---|---|---|
|
|
68
|
+
| POST | `/task` | `{"instruction":"…","wait":0,"meta":{…},"agent_prompt":"…","project_context":"…"}` |
|
|
69
|
+
| GET | `/task/<id>/events?format=json&since=<cursor>&wait=25` | — (long-poll timeline; fields `events`, `next`, `done`) |
|
|
70
|
+
| GET | `/task/<id>/events?since=0&follow=1` | — (SSE fallback, `Accept: text/event-stream`) |
|
|
71
|
+
| POST | `/task/<id>/local-result` | `{"rid":"…","output":"…","code":0}` |
|
|
72
|
+
| POST | `/task/<id>/local-state` | `{"allow":true\|false}` — terminal consent |
|
|
73
|
+
| POST | `/task/<id>/user-event` | `{"kind":"reject","path":"…"}` |
|
|
74
|
+
| POST | `/task/<id>/cancel` | `{}` — Stop |
|
|
75
|
+
| GET | `/task/<id>/ui-payload/<rid>` | — (text typed by a `local-ui type` step, delivered once) |
|
|
76
|
+
| GET | `/health`, `/auth/check` | — (version check, key valid on this PC) |
|
|
77
|
+
| GET | `/fs?path=`, `/file?path=&raw=1` | — (bridge workspace preview) |
|
|
78
|
+
| POST | `/upload?path=` | raw bytes (`application/octet-stream`) |
|
|
79
|
+
|
|
80
|
+
`meta`, exactly as the client serialises it:
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
{"client":"ArenaCode 2.5.2","os":"…","lang":"it",
|
|
84
|
+
"policy":"work_directly_on_pc: read and edit files in the open folder on the PC, also for existing projects; …",
|
|
85
|
+
"pc_access":true,
|
|
86
|
+
"git":{"available":true,"repo":true,"branch":"main"},
|
|
87
|
+
"workspace":{"root":"…","cwd":"…","shell":"…","pwsh7":false,"powershell":true,"wsl":false,
|
|
88
|
+
"active_file":"","selection":"","selection_line":0,
|
|
89
|
+
"open_files":[],"top_level":[],"tools":{}}}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Task framing: `instruction` starts with a mode line — `[ArenaCode mode: agent]`, `[ArenaCode mode: plan]`,
|
|
93
|
+
`[ArenaCode mode: computer-agent]`, or `[ArenaCode handshake] …` — followed by the mode paragraph and the user's
|
|
94
|
+
text. Plan approval answers come back through `local-result` as
|
|
95
|
+
`{"ok":true,"approved":true}`, `{"ok":false,"approved":false}` or
|
|
96
|
+
`{"ok":false,"approved":false,"revise":true,"feedback":"…"}`.
|
|
97
|
+
|
|
98
|
+
## 5. Embedded agent prompt
|
|
99
|
+
|
|
100
|
+
The client embeds its whole system prompt (33 147 characters, UTF-16LE at ≈`0x22b108`) and ships it with every
|
|
101
|
+
task as `agent_prompt`; the bridge writes it to `runtime/agent_instructions.txt` on the first task after a start.
|
|
102
|
+
The port sends the same text byte-for-byte: [`src/prompts/agent-prompt.md`](../src/prompts/agent-prompt.md)
|
|
103
|
+
(extracted with `strings -e l` + a UTF-16 decode around the marker `# ArenaCode agent: system prompt`).
|
|
104
|
+
|
|
105
|
+
That prompt doubles as protocol documentation from the agent's side: it lists the `agentctl` commands, the IDE
|
|
106
|
+
vs Computer Agent modes, the git rules, the vision handshake (`vision-png`,
|
|
107
|
+
`--has-vision-enabled:true|false`) and the "closing a task" contract (`context_<project>.md`).
|
|
108
|
+
|
|
109
|
+
## 6. Local card types the client must execute
|
|
110
|
+
|
|
111
|
+
| Event field | Ops | Executed by |
|
|
112
|
+
|---|---|---|
|
|
113
|
+
| `local_exec` | `command`, `cwd`, `shell` = `powershell` \| `cmd` \| `wsl`, `admin` (UAC), `sandbox_reason` | `CreateProcessW` + pipes; card shows *"Command not run: rejected by the user"* (`code -1`) when declined |
|
|
114
|
+
| `local_fs` | `ls`, `stat`, `read`, `write`, `replace`, `mkdir`, `mv`, `rm`, `pull`, `find`, `git_status`, `git_log`, `git_diff`, `git_commit`, `git_revert`, `git_publish` | in-process file APIs + `git` CLI; the result is a JSON document returned as `output` |
|
|
115
|
+
| `local_ui` | `plan`, `control`, `type`, `keystroke`, `screenshot`, `app`, `state`, `sysinfo`, `apps`, `wait`, `multi_select`, `multi_edit` | GUI automation (`windows-use` DLL) + the Accept/Reject approval cards |
|
|
116
|
+
| `local_file` | `remote`, `name`, `dest` | `GET /file?raw=1` → write into the open folder |
|
|
117
|
+
|
|
118
|
+
Event shapes on the timeline: `thought`, `tool` (with `icon`, `state`, `secs`, `detail`), `text` (with `delta`),
|
|
119
|
+
`status`, `vision` (`{"vision":bool}`), `user` (`{"kind":"reject","path":"…"}`), `done`.
|
|
120
|
+
|
|
121
|
+
## 7. What the port reproduces, and what it cannot
|
|
122
|
+
|
|
123
|
+
| Desktop client behaviour | `arenacode` (this package) |
|
|
124
|
+
|---|---|
|
|
125
|
+
| WinHTTP + bearer key + `X-Arena-Device` (MachineGuid) | `fetch` + same headers; device id from the registry on Windows, `/etc/machine-id` elsewhere |
|
|
126
|
+
| `POST /task` with `meta` / `agent_prompt` / `project_context` | identical payload (see `src/meta.js`) |
|
|
127
|
+
| Long-poll timeline, mode-line framing, `wait: 0` | identical (`src/turn.js`) |
|
|
128
|
+
| `local-exec` with PowerShell/cmd/WSL and refusal payload | same, with `r`/`d` confirmation (`src/local.js`) |
|
|
129
|
+
| `local-fs` ops incl. git, JSON result contract | same operations, same JSON keys |
|
|
130
|
+
| Plan approval / revision / `wait` / `sysinfo` cards | same |
|
|
131
|
+
| mouse, keyboard, screenshots, app launch, UIA element dumps | **not possible from a terminal** — answered with an explicit `{"ok":false,"error":"…"}` |
|
|
132
|
+
| DPAPI-encrypted `secret_dpapi` in `config.ini` | JSON config, mode `0600` (a portable client has no DPAPI) |
|
|
133
|
+
| GUI session/chat list, drag-and-drop, tray | out of scope; history stays on the bridge (`/tasks`, `/task/<id>`) |
|
|
134
|
+
|
|
135
|
+
## 8. Reproducing the analysis
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
# REA (needs Node ≥ 22.19; no analysis engine required for the commands below)
|
|
139
|
+
npx -y rea-agents@latest doctor
|
|
140
|
+
npx -y rea-agents@latest inspect-pe-imports ArenaCode-x64.exe --format json
|
|
141
|
+
npx -y rea-agents@latest inspect-byte-entropy ArenaCode-x64.exe --format json
|
|
142
|
+
|
|
143
|
+
# protocol strings (the client stores them as UTF-16LE)
|
|
144
|
+
strings -a -e l -n 4 ArenaCode-x64.exe | grep -E '^/(task|health|events|file|fs|upload)|Authorization|X-Arena'
|
|
145
|
+
|
|
146
|
+
# the embedded system prompt, byte-exact
|
|
147
|
+
python3 - <<'PY'
|
|
148
|
+
data = open('ArenaCode-x64.exe','rb').read()
|
|
149
|
+
i = data.find('# ArenaCode agent: system prompt'.encode('utf-16le'))
|
|
150
|
+
end = i
|
|
151
|
+
while data[end:end+2] != b'\x00\x00':
|
|
152
|
+
end += 2
|
|
153
|
+
print(data[i:end].decode('utf-16le'))
|
|
154
|
+
PY
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## 9. Deep analysis with Ghidra
|
|
158
|
+
|
|
159
|
+
REA's provider-free commands (section 2) reconstruct the protocol surface, but the decompiler is what turns
|
|
160
|
+
"these strings exist" into "this function builds this request". Setup on this host, and what it cost:
|
|
161
|
+
|
|
162
|
+
| Step | Result |
|
|
163
|
+
|---|---|
|
|
164
|
+
| Ghidra **12.2** + Temurin JDK 21, `GHIDRA_INSTALL_DIR` set | provider refused it: `unsupported_version` — REA 6.8.0 accepts the **12.1.x** line (verified with 12.1.4) |
|
|
165
|
+
| Ghidra **12.1.4**, `MAXMEM=1300M`, 5 min deadline | killed by the kernel OOM killer (`resource_constraint`): the container has 2 GB and Ghidra forks one native decompiler per CPU |
|
|
166
|
+
| Ghidra **12.1.4**, `taskset -c 0`, `MAXMEM=900M`, 30 min deadline | **succeeded** — `ev_63854c5644837a356103bc1033918e6946018a8bacd457b1256a8b911a309d0f` |
|
|
167
|
+
|
|
168
|
+
The successful import + analysis of the 5 MB image reports:
|
|
169
|
+
|
|
170
|
+
```
|
|
171
|
+
document target-1be348946c5c.exe
|
|
172
|
+
image_base 0x140000000
|
|
173
|
+
segments 11
|
|
174
|
+
procedure_count 6376
|
|
175
|
+
string_count 5260
|
|
176
|
+
provider rea-workflow / ghidra 12.1.4
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### What it changes about the port
|
|
180
|
+
|
|
181
|
+
Nothing in the wire format: every field in section 4 had already been confirmed by running the port against a real
|
|
182
|
+
bridge. What it changes is *where to look next*. `rea decompile --file-offset` on a string's offset does **not**
|
|
183
|
+
land inside a procedure — string literals live in `.rdata`, so the useful move is procedure-name search plus
|
|
184
|
+
decompiling the callers of `WinHttpOpenRequest`, which is the next step if the port ever needs the internals:
|
|
185
|
+
|
|
186
|
+
1. the exact `local-fs` JSON response shapes — currently written against the consumer contract in `agentctl.py`
|
|
187
|
+
and verified empirically;
|
|
188
|
+
2. the Accept/Reject card state machine (file-change cards and `user-event` on reject);
|
|
189
|
+
3. the `windows-use` DLL ABI behind `local-ui` mouse/keyboard actions.
|
|
190
|
+
|
|
191
|
+
None of these block a working client: (1) is implemented and tested end to end, (2) has a terminal-appropriate
|
|
192
|
+
equivalent (`--confirm-writes` + the confirm modal), and (3) is answered with an explicit
|
|
193
|
+
"not available in the terminal client" error, which is what the protocol expects from a client that cannot drive
|
|
194
|
+
a GUI.
|
|
195
|
+
|
|
196
|
+
### Practical note
|
|
197
|
+
|
|
198
|
+
Decompiling a GUI client is the expensive path and usually the wrong first one. For a program whose interesting
|
|
199
|
+
behaviour is `WinHttpOpenRequest` plus `swprintf` of JSON, **the literals are the specification**. The eight
|
|
200
|
+
sections above were reconstructed with zero decompilation, deterministically, in minutes; the Ghidra run cost
|
|
201
|
+
about 25 minutes of a single core to add an inventory, not new protocol facts.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Publishing
|
|
2
|
+
|
|
3
|
+
The package is ready: `arenacode@2.5.2`, 23 files, ~60 kB tarball, no runtime dependencies.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
cd arenacode-cli
|
|
7
|
+
npm publish --dry-run # + arenacode@2.5.2
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Status of the publish attempt
|
|
11
|
+
|
|
12
|
+
| Attempt | Result |
|
|
13
|
+
|---|---|
|
|
14
|
+
| `npm whoami` with the supplied token | ✅ authenticated as `lardexx` |
|
|
15
|
+
| `npm view arenacode version` | ✅ 404 → the name `arenacode` is free |
|
|
16
|
+
| `npm publish` (unscoped `arenacode`) | ❌ `403 Forbidden — You may not perform that action with these credentials.` |
|
|
17
|
+
| `npm publish` (`@lardexx/arenacode`, public) | ❌ same 403 |
|
|
18
|
+
|
|
19
|
+
A 403 on PUT with a token that authenticates fine means the token itself is not allowed to write. Three usual
|
|
20
|
+
causes, in order of likelihood:
|
|
21
|
+
|
|
22
|
+
1. **The token is read-only.** Granular tokens have a *Permissions → Read and write* switch; the default in some
|
|
23
|
+
flows is read-only. Create a new one with **Read and write** and package access **All packages**.
|
|
24
|
+
2. **The token is restricted to existing packages.** A granular token limited to a selection of packages cannot
|
|
25
|
+
create a *new* package name: pick “All packages”.
|
|
26
|
+
3. **The account requires 2FA for publishing.** Then `npm publish --otp=<code>` (or an interactive
|
|
27
|
+
`npm login` + `npm publish`) is needed; automation tokens must be created with the *bypass 2FA* option.
|
|
28
|
+
|
|
29
|
+
## Commands
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# A. with a fresh token that can write
|
|
33
|
+
npm login # or: npm config set //registry.npmjs.org/:_authToken=<token>
|
|
34
|
+
npm publish # unscoped: arenacode
|
|
35
|
+
|
|
36
|
+
# B. with the tarball
|
|
37
|
+
npm publish arenacode-2.5.2.tgz
|
|
38
|
+
|
|
39
|
+
# C. under your scope (always available to your account)
|
|
40
|
+
node -e "const p=require('./package.json');p.name='@lardexx/arenacode';p.publishConfig={access:'public'};require('fs').writeFileSync('package.json',JSON.stringify(p,null,2)+'\n')"
|
|
41
|
+
npm publish --access public
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The `bin` stays `arenacode` in all three cases, so the installed command is the same:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npx arenacode # or npx @lardexx/arenacode
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Checklist before publishing
|
|
51
|
+
|
|
52
|
+
- [x] `npm pack --dry-run` clean, no stray files (`test/`, `tools/` are excluded by the `files` field)
|
|
53
|
+
- [x] `npm test` green (bridge + fake agent + CLI)
|
|
54
|
+
- [x] `node test/tui.js` green (real pty, rendered frames)
|
|
55
|
+
- [x] `LICENSE` (MIT), `README.md`, `docs/`
|
|
56
|
+
- [ ] version policy: `2.5.2` tracks the ArenaCode client it ports; use `2.5.3`/`2.6.0` afterwards, and keep the
|
|
57
|
+
client number in `src/config.js` (`VERSION`) in sync
|
|
58
|
+
|
|
59
|
+
## After publishing
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
npm view arenacode version dist.tarball
|
|
63
|
+
npm install -g arenacode
|
|
64
|
+
arenacode doctor
|
|
65
|
+
```
|