@aptove/bridge 0.2.4 → 0.2.5

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.
Files changed (2) hide show
  1. package/README.md +150 -152
  2. package/package.json +6 -6
package/README.md CHANGED
@@ -5,12 +5,9 @@
5
5
  [![npm](https://img.shields.io/npm/v/%40aptove%2Fbridge?logo=npm&label=npm)](https://www.npmjs.com/package/@aptove/bridge)
6
6
  [![Discord](https://img.shields.io/badge/Discord-Join-5865F2?logo=discord&logoColor=white)](https://discord.gg/gD7AMxBy9y)
7
7
 
8
- A bridge library and application between Agent Client Protocol (ACP) agents and clients.
8
+ Aptove Bridge is a stand-alone app to connect any AI Agents that support Agent Client Protocol (ACP) with the Aptove Mobile App to seamlessly manage the AI agents running in a server or computer.
9
9
 
10
- `aptove-bridge` can be used in two ways:
11
-
12
- - **Standalone binary** — run `bridge` as a separate process that spawns your ACP agent over stdio and exposes it over WebSocket to mobile or desktop clients.
13
- - **Embedded library** — add `aptove-bridge` as a Rust dependency and run the bridge server in-process alongside your agent, with no subprocess or stdio pipe required.
10
+ Bridge is actively developed and we are still experimenting. Until the first stable version, expect breaking changes in the configuration files and how it works.
14
11
 
15
12
  ## Transport Modes
16
13
 
@@ -20,8 +17,6 @@ A bridge library and application between Agent Client Protocol (ACP) agents and
20
17
  | **Cloudflare** | Remote access via Cloudflare Zero Trust (internet-accessible) | [docs/transport/cloudflare.md](docs/transport/cloudflare.md) |
21
18
  | **Tailscale** | Private overlay network via MagicDNS + HTTPS (Recommended) | [docs/transport/tailscale.md](docs/transport/tailscale.md) |
22
19
 
23
- One transport is active at a time. When multiple are enabled in `common.toml`, the bridge prompts you to select one at startup.
24
-
25
20
  ## Features
26
21
 
27
22
  - 📱 **QR Code Pairing**: Secure one-time code pairing via QR scan
@@ -29,98 +24,38 @@ One transport is active at a time. When multiple are enabled in `common.toml`, t
29
24
  - ⚡ **WebSocket Streaming**: Real-time bidirectional communication
30
25
  - 🌐 **Multi-Transport**: Local, Cloudflare, Tailscale — configure and switch between them
31
26
  - 🔑 **Stable Agent Identity**: `agent_id` UUID persisted in `common.toml` for multi-transport dedup on mobile
27
+ - 🔔 **Push Notifications**: Wake the mobile app when the agent responds while backgrounded
28
+ - 🖥️ **Terminal UI**: Interactive TUI for setup and day-to-day management
32
29
  - 🦀 **Embeddable**: Use as a library with `StdioBridge` for in-process deployment
33
30
 
34
31
  ---
35
32
 
36
- ## Using as a Library
37
-
38
- Add to your `Cargo.toml`:
39
-
40
- ```toml
41
- [dependencies]
42
- aptove-bridge = "0.2"
43
- ```
44
-
45
- ### Minimal Example
46
-
47
- ```rust
48
- use aptove_bridge::{
49
- bridge::StdioBridge,
50
- common_config::CommonConfig,
51
- tls::TlsConfig,
52
- };
53
-
54
- #[tokio::main]
55
- async fn main() -> anyhow::Result<()> {
56
- // Load (or initialise) shared config — generates agent_id and auth_token on first run
57
- let mut config = CommonConfig::load()?;
58
- config.ensure_agent_id();
59
- config.ensure_auth_token();
60
- config.save()?;
61
-
62
- // Generate (or load) a self-signed TLS cert
63
- let tls = TlsConfig::load_or_generate(&CommonConfig::config_dir(), &[])?;
64
-
65
- StdioBridge::new("copilot --acp".to_string(), 8765)
66
- .with_auth_token(Some(config.auth_token.clone()))
67
- .with_tls(tls)
68
- .start()
69
- .await
70
- }
71
- ```
72
-
73
- ### `StdioBridge` Builder Methods
74
-
75
- | Method | Description |
76
- |--------|-------------|
77
- | `new(agent_command, port)` | Create a bridge that spawns the given command; listen on `port` |
78
- | `.with_bind_addr(addr)` | Override bind address (default: `"0.0.0.0"`) |
79
- | `.with_auth_token(token)` | Require a bearer token for connections |
80
- | `.with_tls(tls_config)` | Enable TLS with a `TlsConfig` (self-signed cert) |
81
- | `.with_external_tls()` | Signal that TLS is handled upstream (Tailscale Serve, Cloudflare) |
82
- | `.with_pairing(manager)` | Enable QR pairing via a `PairingManager` |
83
- | `.with_agent_pool(pool)` | Enable keep-alive sessions via an `AgentPool` |
84
- | `.with_working_dir(dir)` | Set the working directory for the spawned agent process |
85
- | `.with_push_relay(client)` | Enable push notifications via a relay |
86
- | `.with_webhook_resolver(fn)` | Handle `POST /webhook/<token>` trigger requests |
87
- | `.start()` | Start the WebSocket listener (runs until shutdown) |
88
-
89
- ---
90
-
91
- ## Standalone Binary
92
-
93
33
  ### Quick Start
94
34
 
95
35
  ```bash
96
36
  # Build
97
37
  cargo build --release
98
38
 
99
- # Start the bridge — interactive agent selection menu
39
+ # Start the bridge — opens the interactive TUI (setup wizard on first run)
100
40
  ./target/release/bridge
101
41
 
102
- # Or specify the agent directly
103
- ./target/release/bridge run --agent-command "copilot --acp"
42
+ # Use a custom config directory
43
+ ./target/release/bridge -c ./my-config
104
44
  ```
105
45
 
106
- Running `bridge` with no subcommand defaults to `run`. When `--agent-command` is omitted, an interactive menu lets you pick from known agents (Copilot, Gemini, Goose) or enter a custom command.
46
+ On first launch the setup wizard runs automatically: it asks you to choose an agent command and a transport, then starts the bridge and shows a QR code for pairing.
107
47
 
108
48
  Scan the QR code with the mobile app to connect.
109
49
 
110
50
  ### Configuration — `common.toml`
111
51
 
112
- All transport settings live in `common.toml`. The file is created automatically with local transport enabled on first run.
52
+ All settings live in `common.toml`, created automatically by the setup wizard on first run.
113
53
 
114
- **Default location:**
115
-
116
- | Platform | Path |
117
- |----------|------|
118
- | macOS | `~/Library/Application Support/com.aptove.bridge/common.toml` |
119
- | Linux | `~/.config/bridge/common.toml` |
54
+ **Default location:** `.aptove-bridge/` directory inside the working directory where the bridge is launched.
120
55
 
121
56
  Override with `-c` / `--config-dir`:
122
57
  ```bash
123
- bridge -c ./my-config run --agent-command "copilot --acp"
58
+ bridge -c ./my-config
124
59
  ```
125
60
 
126
61
  #### Example `common.toml`
@@ -128,6 +63,10 @@ bridge -c ./my-config run --agent-command "copilot --acp"
128
63
  ```toml
129
64
  agent_id = "550e8400-e29b-41d4-a716-446655440000" # auto-generated UUID
130
65
  auth_token = "base64urltoken" # auto-generated
66
+ agent_command = "copilot --acp" # set by setup wizard
67
+
68
+ keep_alive = true # prevent system sleep while bridge is running
69
+ log_level = "WARN" # ERROR / WARN / INFO / DEBUG / TRACE
131
70
 
132
71
  [transports.local]
133
72
  enabled = true
@@ -135,7 +74,7 @@ port = 8765
135
74
  tls = true
136
75
 
137
76
  [transports.cloudflare]
138
- enabled = true
77
+ enabled = false
139
78
  hostname = "https://agent.example.com"
140
79
  tunnel_id = "abc123"
141
80
  tunnel_secret = "..."
@@ -144,10 +83,19 @@ client_id = "client.access"
144
83
  client_secret = "xxxxx"
145
84
 
146
85
  [transports.tailscale-serve]
147
- enabled = true
86
+ enabled = false
87
+
88
+ # Optional — enables push notifications (see Push Notifications section below)
89
+ [push_relay]
90
+ url = "https://push.aptove.com"
91
+ token_url = "https://token.aptove.com"
92
+ client_id = "your-client-id"
93
+ client_secret = "your-client-secret"
148
94
  ```
149
95
 
150
- Enable only the transports you need. `agent_id` and `auth_token` are generated automatically on first run and stay stable across restarts.
96
+ No transports are pre-enabled. The setup wizard asks you to choose one on first run.
97
+
98
+ `agent_id` and `auth_token` are generated automatically on first run and stay stable across restarts.
151
99
 
152
100
  #### Config Directory Files
153
101
 
@@ -155,33 +103,30 @@ All bridge state lives in the config directory. These files are created automati
155
103
 
156
104
  | File | Purpose |
157
105
  |------|---------|
158
- | `common.toml` | Main config — `agent_id`, `auth_token`, and transport settings. Permissions `0600`. |
106
+ | `common.toml` | Main config — `agent_id`, `auth_token`, `agent_command`, and transport settings. Permissions `0600`. |
159
107
  | `cert.pem` | Self-signed TLS certificate for the local transport WebSocket server. Its fingerprint is embedded in the QR pairing payload for certificate pinning. |
160
108
  | `key.pem` | Private key for the TLS certificate. |
161
- | `cert-extra-sans.json` | Tracks extra Subject Alternative Names (IPs/hostnames) baked into the TLS cert (e.g. `--advertise-addr` or Tailscale IP). When these change, the cert is automatically regenerated. |
109
+ | `cert-extra-sans.json` | Tracks extra Subject Alternative Names (IPs/hostnames) baked into the TLS cert. When these change, the cert is automatically regenerated. |
162
110
 
163
- ### Commands
111
+ ### CLI
164
112
 
165
- #### `run` — Start the bridge (default)
113
+ ```
114
+ bridge [OPTIONS] [COMMAND]
115
+ ```
166
116
 
167
- `run` is the default subcommand — running `bridge` with no arguments is equivalent to `bridge run`.
117
+ | Flag | Short | Description |
118
+ |------|-------|-------------|
119
+ | `--config-dir <PATH>` | `-c` | Use a custom config directory instead of `.aptove-bridge/` |
120
+ | `--version` | `-v` | Print version |
168
121
 
169
- ```bash
170
- # Interactive agent selection
171
- bridge
122
+ | Subcommand | Description |
123
+ |------------|-------------|
124
+ | *(none)* | Start the TUI (setup wizard if needed, then running screen) |
125
+ | `setup` | Re-run the Cloudflare Zero Trust setup wizard |
172
126
 
173
- # Or specify the agent directly
174
- bridge run --agent-command "copilot --acp"
175
- ```
176
-
177
- | Flag | Short | Description | Default |
178
- |------|-------|-------------|---------|
179
- | `--agent-command <CMD>` | `-a` | Command to spawn the ACP agent | Interactive menu |
180
- | `--bind <ADDR>` | `-b` | Address to bind the listener | `0.0.0.0` |
181
- | `--verbose` | | Enable info-level logging | Off (warn only) |
182
- | `--advertise-addr <ADDR>` | | Override LAN address in QR pairing URL | Auto-detected |
127
+ #### `bridge` (default — TUI)
183
128
 
184
- When `--agent-command` is omitted, the bridge presents an interactive menu:
129
+ Running `bridge` with no arguments opens the interactive TUI. On first launch it runs the setup wizard:
185
130
 
186
131
  ```
187
132
  Select an agent to run:
@@ -192,52 +137,123 @@ Select an agent to run:
192
137
  Enter number [1]:
193
138
  ```
194
139
 
195
- The QR code for pairing is always displayed at startup.
140
+ Select **Custom** to enter any ACP-compatible agent command, for example:
196
141
 
197
- Transport selection, port, TLS, and auth token are all read from `common.toml`.
142
+ ```
143
+ npx @agentclientprotocol/claude-agent-acp
144
+ ```
198
145
 
199
- #### `show-qr` — Show QR code for a second device
146
+ After the wizard completes the bridge starts and displays a QR code for pairing.
147
+
148
+ #### `bridge setup`
149
+
150
+ Re-runs the Cloudflare Zero Trust setup wizard as a TUI flow. Use this to reconfigure your Cloudflare tunnel credentials without restarting the running bridge.
200
151
 
201
152
  ```bash
202
- bridge show-qr
153
+ bridge setup
203
154
  ```
204
155
 
205
- Displays the connection QR code for the currently active transport. The bridge must already be running. Use this to pair an additional device without restarting.
156
+ No flags are needed — the wizard guides you through everything interactively.
206
157
 
207
- #### `setup` — Provision Cloudflare infrastructure
158
+ ### TUI slash commands
208
159
 
209
- ```bash
210
- bridge setup \
211
- --api-token "your-api-token" \
212
- --account-id "your-account-id" \
213
- --domain "example.com" \
214
- --subdomain "agent"
160
+ Type these in the input bar while the bridge is running:
161
+
162
+ | Command | Description |
163
+ |---------|-------------|
164
+ | `/qr` | Show QR pairing code |
165
+ | `/agent` | Change the AI agent command |
166
+ | `/push` | Configure push notifications |
167
+ | `/test-push` | Send a test push notification |
168
+ | `/reconnect` | Restart the transport |
169
+ | `/keep-alive` | Toggle prevent-sleep (on by default) |
170
+ | `/log-level` | Change log verbosity (default: WARN) |
171
+ | `/clear-logs` | Clear the log view |
172
+ | `/copy-logs` | Copy all logs to clipboard |
173
+ | `/help` | List all commands |
174
+ | `/quit` | Exit the bridge |
175
+
176
+ ---
177
+
178
+ ## Push Notifications
179
+
180
+ The bridge can alert the mobile app when the agent produces output while the app is backgrounded (WebSocket disconnected). The bridge never holds APNs/FCM credentials — it authenticates to a push relay service using a short-lived RS256 JWT and the relay handles APNs/FCM delivery.
181
+
182
+ ### How it works
183
+
184
+ ```
185
+ Mobile app (backgrounded) Bridge Push relay
186
+ (push.aptove.com)
187
+ │ │ │
188
+ │ [app backgrounds] │ │
189
+ │ │ │
190
+ │ │ agent produces output │
191
+ │ │◄───────────────────── │
192
+ │ │ │
193
+ │ │ no WebSocket receiver │
194
+ │ │ (broadcast send fails) │
195
+ │ │ │
196
+ │ │ POST /push (JWT Bearer) ───►│
197
+ │ │ │
198
+ │◄────────────────────────── APNs/FCM notification │
199
+ │ │ │
200
+ │ [app foregrounds] │ │
201
+ │ reconnects WebSocket ─────►│ │
202
+ │◄── buffered messages ───────│ │
215
203
  ```
216
204
 
217
- Creates the Cloudflare tunnel, DNS record, Access Application, and Service Token. Saves credentials to `common.toml` under `[transports.cloudflare]`. Only needed once.
205
+ ### Trigger logic
218
206
 
219
- | Flag | Description | Default |
220
- |------|-------------|---------|
221
- | `--api-token <TOKEN>` | Cloudflare API token | Required |
222
- | `--account-id <ID>` | Cloudflare account ID | Required |
223
- | `--domain <DOMAIN>` | Domain managed by Cloudflare | Required |
224
- | `--subdomain <SUB>` | Subdomain for the bridge endpoint | `agent` |
225
- | `--tunnel-name <NAME>` | Name for the Cloudflare tunnel | `aptove-tunnel` |
207
+ Push is triggered by **passive failure detection** — the bridge never polls for connectivity:
226
208
 
227
- #### `status` — Check configuration
209
+ | Site | Trigger | Source |
210
+ |------|---------|--------|
211
+ | Agent pool (main path) | Agent stdout arrives; `broadcast::Sender::send()` returns `Err` (zero receivers — no WebSocket client connected) | `src/agent_pool.rs` |
212
+ | Active connection drop | `ws_sender.send()` returns `Err` (client disconnected mid-stream); message is buffered before notify | `src/bridge.rs` |
228
213
 
229
- ```bash
230
- bridge status
214
+ A 30-second per-client debounce prevents notification storms when the agent is verbose.
215
+
216
+ ### Setup
217
+
218
+ Configure push via the `/push` TUI command or add the `[push_relay]` section directly to `common.toml`:
219
+
220
+ ```toml
221
+ [push_relay]
222
+ url = "https://push.aptove.com" # Push relay base URL
223
+ token_url = "https://token.aptove.com" # JWT token service URL
224
+ client_id = "your-client-id" # M2M client ID (JWT sub)
225
+ client_secret = "your-client-secret" # M2M client secret
231
226
  ```
232
227
 
233
- Prints the active `common.toml` path, `agent_id`, enabled transports, and Tailscale availability.
228
+ All four fields are required. If the section is absent or any field is empty, push is silently disabled. The push relay URL is included in the QR pairing payload so the mobile app knows where to register its device token.
229
+
230
+ ### Security
231
+
232
+ - Device tokens travel over the bridge's authenticated WebSocket (bearer `authToken` + TLS) — the same channel that carries the full LLM conversation.
233
+ - JWT credentials (`client_id`, `client_secret`) never leave the bridge process.
234
+ - The relay isolates registered devices per JWT `sub` (`client_id`) — one bridge cannot trigger pushes for another bridge's devices.
235
+ - Notification content is fixed (`"Your agent has new activity"`) to prevent leaking agent response text.
236
+
237
+ ### Library usage
238
+
239
+ ```rust
240
+ use aptove_bridge::push::PushRelayClient;
241
+
242
+ let push_client = PushRelayClient::new("https://push.aptove.com".into(), String::new())
243
+ .with_jwt_credentials(
244
+ "https://token.aptove.com".into(),
245
+ "your-client-id".into(),
246
+ "your-client-secret".into(),
247
+ );
248
+
249
+ let bridge = StdioBridge::new("copilot --acp".into(), 8765)
250
+ .with_push_relay(push_client);
251
+ ```
234
252
 
235
253
  ---
236
254
 
237
255
  ## Architecture
238
256
 
239
- ### Standalone
240
-
241
257
  ```
242
258
  ┌─────────────┐ ┌───────────────────────┐ ┌──────────────┐
243
259
  │ Client App │◄──────────────────►│ bridge binary │◄──────►│ ACP Agent │
@@ -245,16 +261,7 @@ Prints the active `common.toml` path, `agent_id`, enabled transports, and Tailsc
245
261
  └─────────────┘ └───────────────────────┘ └──────────────┘
246
262
  ```
247
263
 
248
- ### Embedded (library)
249
-
250
- ```
251
- ┌─────────────┐ ┌──────────────────────────────────┐ ┌──────────────┐
252
- │ Client App │◄──────────────────►│ Your process │◄──────►│ ACP Agent │
253
- │ (iOS/Android│ WebSocket (TLS) │ StdioBridge (transport listener)│ stdio │ (your cmd) │
254
- └─────────────┘ └──────────────────────────────────┘ └──────────────┘
255
- ```
256
-
257
- The agent process is still spawned as a subprocess via stdio — `StdioBridge` handles the WebSocket server, TLS, auth, and pairing in-process alongside your application.
264
+ The agent process is spawned as a subprocess via stdio — `StdioBridge` handles the WebSocket server, TLS, auth, and pairing in-process alongside your application.
258
265
 
259
266
  ---
260
267
 
@@ -266,16 +273,16 @@ The agent process is still spawned as a subprocess via stdio — `StdioBridge` h
266
273
  | "Unauthorized" on connect | Wrong or missing auth token | Re-scan QR code |
267
274
  | TLS handshake failure | Certificate mismatch | Delete the config dir and restart to regenerate certs; re-pair the device |
268
275
  | App cannot reach bridge | Firewall blocking the port | Check OS firewall; ensure the port in `common.toml` is open |
269
- | Transport fails to start | `common.toml` has no `enabled = true` transport | Run `bridge status` to see configured transports |
276
+ | Transport fails to start | No transport has `enabled = true` | Run the bridge and check the setup wizard |
270
277
 
271
278
  ### Debugging
272
279
 
273
280
  ```bash
274
- # Enable verbose logging
275
- bridge run --verbose
281
+ # Change log verbosity via TUI
282
+ # Type /log-level and select DEBUG or TRACE
276
283
 
277
- # Check which transports are configured
278
- bridge status
284
+ # Or set it permanently in common.toml
285
+ log_level = "DEBUG"
279
286
 
280
287
  # Test agent command independently
281
288
  echo '{"jsonrpc":"2.0","method":"initialize","id":1}' | copilot --acp
@@ -285,12 +292,7 @@ echo '{"jsonrpc":"2.0","method":"initialize","id":1}' | copilot --acp
285
292
 
286
293
  Please note that the project is extensively tested only with Copilot CLI. Open a bug report if you notice any issues with other ACP-Compatible Agents.
287
294
 
288
- The easiest way to test any supported agent is to run `bridge` and pick it from the interactive menu. Alternatively, pass the command directly:
289
-
290
- ```bash
291
- bridge run -a "gemini --experimental-acp" --verbose
292
- bridge run -a "goose acp" --verbose
293
- ```
295
+ Run `bridge` and pick an agent from the setup wizard, or edit `agent_command` directly in `common.toml`.
294
296
 
295
297
  ---
296
298
 
@@ -300,16 +302,12 @@ bridge run -a "goose acp" --verbose
300
302
  - **TLS**: self-signed certificate generated on first run. Certificate fingerprint is included in the QR pairing payload and pinned by the mobile app to prevent MITM attacks.
301
303
  - **Pairing codes**: 6-digit, single-use, expire after 60 seconds. Rate-limited to 5 attempts per code.
302
304
  - **`common.toml`**: contains all secrets. Permissions are set to `0600` automatically. Keep it secure.
303
- - **Agent command**: the `--agent-command` value (or interactive menu selection) is validated at startup — the binary must exist and be executable before the server accepts connections. The command is never persisted to `common.toml`; it must be supplied each time the bridge is started. The bridge is an operator tool: whoever can invoke it already has local shell access, so the agent command is implicitly trusted to the same degree as any other command that user could run.
305
+ - **Agent command**: saved to `agent_command` in `common.toml` by the setup wizard. It can be changed at any time via the `/agent` TUI command. The bridge is an operator tool: whoever can invoke it already has local shell access, so the agent command is implicitly trusted to the same degree as any other command that user could run.
304
306
 
305
307
  To rotate credentials (invalidates all paired devices):
306
308
 
307
309
  ```bash
308
- # macOS
309
- rm ~/Library/Application\ Support/com.aptove.bridge/common.toml
310
- # Linux
311
- rm ~/.config/bridge/common.toml
312
-
310
+ rm .aptove-bridge/common.toml
313
311
  bridge
314
312
  ```
315
313
 
@@ -324,4 +322,4 @@ cargo test # Run tests
324
322
 
325
323
  ## License
326
324
 
327
- Apache 2.0 — see [LICENSE](LICENSE)
325
+ MIT — see [LICENSE](LICENSE)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aptove/bridge",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "ACP bridge — connects ACP agents to mobile and desktop clients over WebSocket",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -33,10 +33,10 @@
33
33
  "node": ">=16"
34
34
  },
35
35
  "optionalDependencies": {
36
- "@aptove/bridge-darwin-arm64": "0.2.4",
37
- "@aptove/bridge-darwin-x64": "0.2.4",
38
- "@aptove/bridge-linux-arm64": "0.2.4",
39
- "@aptove/bridge-linux-x64": "0.2.4",
40
- "@aptove/bridge-win32-x64": "0.2.4"
36
+ "@aptove/bridge-darwin-arm64": "0.2.5",
37
+ "@aptove/bridge-darwin-x64": "0.2.5",
38
+ "@aptove/bridge-linux-arm64": "0.2.5",
39
+ "@aptove/bridge-linux-x64": "0.2.5",
40
+ "@aptove/bridge-win32-x64": "0.2.5"
41
41
  }
42
42
  }