@beeuni/agent-tunnel 0.0.0-stage → 0.1.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
@@ -1,3 +1,208 @@
1
- # Temporary Holding Version
1
+ # `@beeuni/agent-tunnel`
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ `@beeuni/agent-tunnel` connects a local computer to Beeuni through an authenticated reverse tunnel.
4
+
5
+ Each connected computer is an account of a project's **computer** connector,
6
+ private to the person who connected it unless they share it with the project.
7
+ The Beeuni desktop app bundles this agent and connects in one click. Without
8
+ the desktop app, use the command below.
9
+
10
+ ## Connect once
11
+
12
+ Run the command shown under **Connect your computer** in a project. Then
13
+ approve the device code in your browser:
14
+
15
+ ```bash
16
+ npx --yes @beeuni/agent-tunnel@latest connect \
17
+ --api-url https://api.beeuni.ai/v1/tunnel
18
+ ```
19
+
20
+ The computer belongs to you in every project and workspace you are a member
21
+ of, in your private sessions. `--project-id` only offers "Also share with
22
+ <project>" on the approval page; without it the computer is still yours
23
+ everywhere.
24
+
25
+ Connecting the same computer again reuses its entry. The agent sends a SHA-256
26
+ hash of the hardware ID (IOPlatformUUID, `/etc/machine-id`, or `MachineGuid`);
27
+ the raw ID never leaves the computer.
28
+
29
+ After approval, the interactive flow asks whether it should install a persistent background service. The default answer is yes.
30
+
31
+ ## Run in the background
32
+
33
+ Install the operating-system background service during connection:
34
+
35
+ ```bash
36
+ npx --yes @beeuni/agent-tunnel@latest connect \
37
+ --daemon \
38
+ --api-url https://api.beeuni.ai/v1/tunnel
39
+ ```
40
+
41
+ The service uses LaunchAgent on macOS, a user systemd service on Linux, and Task Scheduler on Windows.
42
+ It starts at login, and the supervisor restarts it after every exit (launchd
43
+ `KeepAlive` with a 10 s throttle, systemd `Restart=always`, a 5 s Windows loop).
44
+ On Linux, install enables `loginctl enable-linger` so the user service keeps
45
+ running after logout and starts at boot; when that is not allowed, install
46
+ prints a warning. The Windows task runs hidden, on battery, with no time limit.
47
+ The agent itself never stops on its own:
48
+
49
+ - It reconnects with exponential backoff from 1 s to 30 s, ±20 % jitter, forever.
50
+ - It drops a socket that received no relay ping for 75 s, and it reconnects at
51
+ once after the machine wakes from sleep (a 5 s timer that sees a jump of more
52
+ than 30 s). A wake resets the backoff.
53
+ - A refused credential puts it in state `rejected`. It checks `config.json`
54
+ every 5 s and connects as soon as a new pairing writes another credential;
55
+ it also retries the old one every 5 minutes.
56
+ - The relay accepts 5 connections per machine per minute. A process that
57
+ restarts faster than that waits up to 12 s for the next slot.
58
+ - A second process with the same credential puts it in state `standby`. It
59
+ tries again after 1 minute, doubling each time up to 30 minutes, so two
60
+ holders of one credential do not trade the connection back and forth.
61
+ - A handshake that does not reach `auth_ok` within 20 s is dropped and retried.
62
+ - No saved credential: it waits and checks `config.json` every 60 s.
63
+
64
+ It changes the computer's sleep settings only when `access.json` sets
65
+ `keepAwake` (see below).
66
+
67
+ ## Manage the background service
68
+
69
+ ```bash
70
+ npx --yes @beeuni/agent-tunnel@latest service-status
71
+ npx --yes @beeuni/agent-tunnel@latest logs
72
+ npx --yes @beeuni/agent-tunnel@latest restart
73
+ npx --yes @beeuni/agent-tunnel@latest stop
74
+ npx --yes @beeuni/agent-tunnel@latest uninstall-service
75
+ ```
76
+
77
+ `stop` disables the job (`launchctl disable`, `systemctl --user disable --now`,
78
+ `schtasks /Change /DISABLE`), so it stays stopped until `start`.
79
+
80
+ `logout` first calls `DELETE /v1/tunnel/self` with the machine's own credential,
81
+ which removes the machine and its accounts in Beeuni. It then clears the local
82
+ credential and removes the service. If Beeuni cannot be reached, the local
83
+ sign-out still completes; `logout --json` then reports `"serverUnpaired": false`.
84
+
85
+ `service-status --json` prints the pairing, the service (`installed`, `active`,
86
+ `enabled` = not paused, `upToDate` = the unit matches what `install-service`
87
+ writes now), the live connection state, and `access`.
88
+
89
+ Credentials are stored in `~/.agent-tunnel/config.json`. Agent Tunnel requires
90
+ the file to be regular, owned by the current user, and mode `0600` on POSIX.
91
+ Protect the operating-system account because this setup token can authenticate
92
+ the machine until you rotate or delete the connection.
93
+
94
+ Remote API URLs must use HTTPS. Plain HTTP is accepted only for `localhost`,
95
+ `127.0.0.1`, and `::1` development endpoints.
96
+
97
+ ## Files and isolation
98
+
99
+ | Path (under the config directory) | Content |
100
+ | --- | --- |
101
+ | `config.json` | Credential and local limits, mode `0600`. |
102
+ | `state.json` | `{ tunnelId, apiUrl, status: online\|offline\|connecting\|rejected\|standby, since, agentVersion, pid }`, rewritten atomically on every connection change. No credential. |
103
+ | `access.json` | `{ mode: ask\|always\|off, grantedUntil, deniedUntil, keepAwake }`, mode `0600`. See "Access control". |
104
+ | `access-request.json` | `{ id, requestedAt, capability, method }`: the call waiting for the owner. |
105
+ | `desktop-app.json` | Written by the Beeuni desktop app: how to start it to show an access prompt. |
106
+ | `logs/` | Service output. |
107
+
108
+ `AGENT_TUNNEL_HOME` replaces the config directory (default `~/.agent-tunnel`).
109
+ A non-default directory also gets its own service,
110
+ `ai.beeuni.agent-tunnel.<first 8 hex of sha256(directory)>`, so a second
111
+ identity never replaces the default `ai.beeuni.agent-tunnel` service. The
112
+ service definition carries `AGENT_TUNNEL_HOME` into the service environment.
113
+
114
+ ## Access control
115
+
116
+ The machine decides, before every call, whether an agent may use it.
117
+ `access.json` holds the owner's answer:
118
+
119
+ | `mode` | Effect |
120
+ | --- | --- |
121
+ | `always` | Every call runs. A machine without `access.json` behaves like this. |
122
+ | `ask` | A call runs while `grantedUntil` is in the future (at most 24 h ahead). Otherwise the agent writes `access-request.json`, starts the desktop app if it is not running, and holds the call for 20 s. A grant runs it. No answer returns `-32010 computer_access_pending`. A denial sets `deniedUntil` 10 minutes ahead and returns `-32011 computer_access_denied`; calls fail at once until then. |
123
+ | `off` | Every call returns `-32012 computer_access_off`. |
124
+
125
+ A pairing made from the Beeuni desktop app starts in `ask`: the app shows the
126
+ prompt. A pairing made with this CLI alone starts in `always`, because nothing
127
+ on the machine could answer a prompt. This is a deliberate deviation from
128
+ "new pairings default to ask". Every new pairing resets `access.json`, so a
129
+ grant or `always` from an earlier pairing never carries over, and `logout`
130
+ deletes it. The agent home (`config.json`, `access.json`, `desktop-app.json`)
131
+ is always a blocked path: no file or shell call can change these files. The agent reports `{ mode, grantedUntil }`
132
+ to Beeuni as the signed notification `tunnel.access.state` on connect and on
133
+ every change.
134
+
135
+ `keepAwake: true` keeps the computer from sleeping while the agent runs:
136
+ `caffeinate -s` on macOS (on AC power only), `systemd-inhibit --what=sleep` on
137
+ Linux. Windows is not supported.
138
+
139
+ ## Machine-readable connect
140
+
141
+ `connect --json` prints newline-delimited JSON events on stdout instead of the
142
+ terminal UI. It never prompts and never opens a browser; the caller shows the
143
+ approval URL.
144
+
145
+ ```json
146
+ {"event":"challenge","deviceCode":"ABCD-1234","verificationUrl":"https://…","expiresAt":"…"}
147
+ {"event":"approved","tunnelId":"…","capabilities":["filesystem","shell"]}
148
+ {"event":"service","action":"install","ok":true,"active":true,"detail":"…"}
149
+ {"event":"error","message":"…"}
150
+ ```
151
+
152
+ `approved` carries `"existing": true` when a saved pairing was still valid.
153
+ `service` follows only with `--daemon`.
154
+
155
+ Run under Electron (`ELECTRON_RUN_AS_NODE=1`), the installed service runs the
156
+ same Electron binary with `ELECTRON_RUN_AS_NODE=1` and the bundle in place.
157
+
158
+ ## Permission boundaries
159
+
160
+ Beeuni checks connector profile assignment, connector grants, connector tool
161
+ policy, and the machine permission before relaying an operation. The local agent
162
+ then checks the machine permission again.
163
+
164
+ The local config is the maximum boundary. The server cannot widen configured
165
+ filesystem paths, blocked paths, shell commands, timeouts, file sizes, or desktop
166
+ features.
167
+
168
+ A `shell` grant is local code execution. The allow list matches the executable
169
+ name exactly and the block list also matches every path ending in that name
170
+ (`/bin/rm` is `rm`). Arguments are not restricted: an allowed `git`, `find`, or
171
+ `python` can run any code through its own flags. Grant `shell` only to a session
172
+ you trust with this computer.
173
+
174
+ ## Computer Use driver
175
+
176
+ Agent Tunnel never downloads or executes a desktop driver. Install `cua-driver`
177
+ locally before enabling Computer Use. The tunnel uses an existing binary from
178
+ `CUA_DRIVER_BIN`, `~/.local/bin`, `/usr/local/bin`, or `/opt/homebrew/bin`.
179
+ Treat that binary as trusted local code. Agent Tunnel does not verify or update
180
+ it.
181
+
182
+ ### Transfer a binary file without copying base64
183
+
184
+ Run the client where the source file exists. Set `TUNNEL_API_URL`, `TUNNEL_TOKEN`,
185
+ and `TUNNEL_ID` for the target connection, then run:
186
+
187
+ ```sh
188
+ agent-tunnel-cli fs_upload '{"source":"/tmp/report.xlsx","path":"/Users/me/Desktop/report.xlsx"}'
189
+ ```
190
+
191
+ `fs_upload` reads bytes from `source`, computes SHA-256, and sends the bytes
192
+ programmatically over the authenticated tunnel. It requires filesystem write
193
+ permission. A pending approval returns `success: false` and exit code 1; retry
194
+ only after approval. The command accepts regular files up to 3 MiB, which leaves
195
+ room under the relay's 5 MiB message limit after base64 encoding.
196
+
197
+ The connected agent checks the supplied `sha256` before modifying the destination.
198
+ It reads the file after writing and returns its persisted `sha256` and `size`.
199
+ The CLI succeeds only when both match the source. An older agent without checksum
200
+ support causes verification to fail; the file may already exist. Update the agent
201
+ before retrying.
202
+
203
+ For raw `fs.write`, `sha256` is optional for compatibility. Supply it for binary
204
+ content. Never copy an opaque base64 payload from model context. Generate the file
205
+ on the destination when a programmatic transfer is unavailable. A matching hash
206
+ proves byte integrity, not format validity: validate XLSX/ZIP structure and workbook
207
+ contents at the source. File size and magic bytes are insufficient. Do not use
208
+ public file-host relays for this workflow.