agentpal 0.1.0 → 0.1.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/Cargo.toml CHANGED
@@ -9,7 +9,7 @@ members = [
9
9
  [workspace.package]
10
10
  edition = "2024"
11
11
  license = "MIT"
12
- version = "0.1.0"
12
+ version = "0.1.1"
13
13
 
14
14
  [workspace.dependencies]
15
15
  agentpal-protocol = { path = "crates/protocol" }
@@ -21,9 +21,10 @@ local-ip-address = "0.6.13"
21
21
  qrcode = { version = "0.14.1", default-features = false }
22
22
  serde = { version = "1.0.228", features = ["derive"] }
23
23
  serde_json = "1.0.145"
24
+ rustls = { version = "0.23.37", default-features = false, features = ["ring", "std", "tls12"] }
24
25
  time = { version = "0.3.44", features = ["serde", "formatting", "macros", "parsing"] }
25
26
  tokio = { version = "1.48.0", features = ["macros", "net", "process", "rt-multi-thread", "signal", "sync", "time"] }
26
- tokio-tungstenite = "0.29.0"
27
+ tokio-tungstenite = { version = "0.29.0", features = ["rustls-tls-webpki-roots"] }
27
28
  tracing = "0.1.41"
28
29
  tracing-subscriber = { version = "0.3.22", features = ["env-filter"] }
29
30
  url = "2.5.7"
package/README.md CHANGED
@@ -1,43 +1,147 @@
1
1
  # AgentPal
2
2
 
3
- AgentPal connects a local coding-agent host to the AgentPal mobile app through a Cloud Relay pairing flow.
3
+ **Your coding agents keep working. You keep control.**
4
4
 
5
- This first npm release is a source-based CLI package: it runs the packaged Rust host and relay through `cargo run`.
5
+ AgentPal is a pocket control surface for coding agents running on your own machine. Pair your phone with a local Codex host, step away from the desk, and stay close enough to review progress, catch blockers, and send the next instruction.
6
6
 
7
- ## Install
7
+ ```bash
8
+ npx agentpal@latest pair
9
+ ```
10
+
11
+ AgentPal is not a remote desktop, not a phone terminal, and not another AI chat box. Your computer remains the place where code is read, edited, tested, and committed. Your phone becomes the place where you keep the work visible and steer it when attention is needed.
12
+
13
+ ## Why
14
+
15
+ Long-running coding agents are useful because they can keep moving while you are not staring at the terminal. They are also useful only if you can stay in the loop:
16
+
17
+ - a command needs approval;
18
+ - a diff needs a quick review;
19
+ - a task gets blocked and needs a nudge;
20
+ - a session finishes while you are away;
21
+ - you want to send one short follow-up without reopening your laptop.
8
22
 
9
- Run without installing:
23
+ AgentPal is built for that gap between "the agent is running" and "I still need to be responsible for what it does."
24
+
25
+ ## Start Here
26
+
27
+ Run this from the project directory on the computer where Codex is available:
10
28
 
11
29
  ```bash
12
30
  npx agentpal@latest pair
13
31
  ```
14
32
 
15
- Or install globally:
33
+ The command starts the AgentPal host, creates a Cloud Relay pairing, and prints a pairing URL plus QR code for the mobile app.
34
+
35
+ You can also install the CLI globally:
16
36
 
17
37
  ```bash
18
38
  npm install -g agentpal
19
39
  agentpal pair
20
40
  ```
21
41
 
42
+ To pair a different workspace:
43
+
44
+ ```bash
45
+ agentpal pair --workspace /path/to/project
46
+ ```
47
+
48
+ ## What Works Today
49
+
50
+ AgentPal `0.1.x` is the first public release line of the desktop/CLI side:
51
+
52
+ - public npm package: `agentpal`;
53
+ - one-command pairing entry: `npx agentpal@latest pair`;
54
+ - Cloud Relay pairing flow;
55
+ - local Codex host bridge;
56
+ - QR/link pairing payloads;
57
+ - workspace-aware host startup;
58
+ - local relay command for development and advanced setups.
59
+
60
+ The mobile app and broader agent adapters are still evolving. This release is intended to make the public pairing path installable and testable.
61
+
62
+ ## How It Works
63
+
64
+ ```text
65
+ Phone app <-> Cloud Relay <-> AgentPal Host <-> Codex CLI
66
+ |
67
+ v
68
+ your workspace
69
+ ```
70
+
71
+ The host runs on your computer and talks to the local coding-agent process. The relay helps your phone and host find each other when they are not on the same network. Your repository stays on your machine; the phone is a control and review surface, not the execution environment.
72
+
22
73
  ## Commands
23
74
 
24
75
  ```bash
25
76
  agentpal pair
26
77
  agentpal pair --workspace .
78
+ agentpal pair --workspace . --relay-url ws://127.0.0.1:8790/ws
79
+
27
80
  agentpal relay --host 0.0.0.0 --port 8790
28
81
  agentpal host codex connect --workspace .
29
82
  ```
30
83
 
31
- `agentpal pair` uses the current directory as the default workspace and the public Cloud Relay by default. For local development, pass a local Relay URL:
84
+ `agentpal pair` uses the current directory as the default workspace and the public Cloud Relay unless you pass `--relay-url` or set `AGENTPAL_RELAY_URL`.
85
+
86
+ ## Requirements
87
+
88
+ The current npm package is source-based. It installs a Node.js wrapper and runs the packaged Rust host/relay through `cargo run`.
89
+
90
+ You need:
91
+
92
+ - Node.js 18 or newer;
93
+ - Rust toolchain with `cargo`;
94
+ - Codex CLI available as `codex` for live host sessions.
95
+
96
+ Prebuilt binaries are planned, but they are not part of the `0.1.x` release line.
97
+
98
+ ## Security Model
99
+
100
+ AgentPal is designed around local execution:
101
+
102
+ - your code stays in your workspace on your computer;
103
+ - the host launches and supervises the local coding agent;
104
+ - the relay forwards pairing and realtime messages;
105
+ - the phone sends control intent and receives structured session state.
106
+
107
+ Do not treat the current public relay as a private enterprise deployment boundary. For sensitive work, run your own relay or wait for hardened deployment guidance.
108
+
109
+ ## Development
110
+
111
+ Run the CLI from source:
32
112
 
33
113
  ```bash
34
- agentpal pair --workspace . --relay-url ws://127.0.0.1:8790/ws
114
+ npm run agentpal -- --help
115
+ npm run agentpal -- pair --workspace .
35
116
  ```
36
117
 
37
- ## Requirements
118
+ Run a local relay:
119
+
120
+ ```bash
121
+ cargo run -p agentpal-relay -- --host 0.0.0.0 --port 8790
122
+ ```
123
+
124
+ Check the Rust workspace:
125
+
126
+ ```bash
127
+ cargo fmt --check
128
+ cargo check --workspace
129
+ cargo test -p agentpal-relay
130
+ ```
131
+
132
+ Mobile app code lives under `apps/mobile`. Host, relay, and protocol crates live under `crates/`.
133
+
134
+ ## Roadmap
135
+
136
+ Near-term work:
137
+
138
+ - mobile pairing and session UX hardening;
139
+ - prebuilt CLI binaries so users do not need Rust;
140
+ - more complete Codex session controls;
141
+ - Claude Code and other adapter paths;
142
+ - self-hosted relay deployment guide;
143
+ - stronger auth, device trust, and production relay hardening.
38
144
 
39
- - Node.js
40
- - Rust toolchain with `cargo`
41
- - Codex CLI available as `codex` for live host sessions
145
+ ## Status
42
146
 
43
- The npm package currently builds and runs the Rust host/relay from source through `cargo run`.
147
+ AgentPal is early. The public npm package is live, but the product surface is still being shaped. Expect sharp edges, fast iteration, and honest limits.
@@ -11,6 +11,7 @@ clap.workspace = true
11
11
  futures-util.workspace = true
12
12
  local-ip-address.workspace = true
13
13
  qrcode.workspace = true
14
+ rustls.workspace = true
14
15
  serde.workspace = true
15
16
  serde_json.workspace = true
16
17
  time.workspace = true
@@ -1,9 +1,10 @@
1
1
  use std::{
2
- collections::{HashMap, HashSet},
3
- env,
4
- path::{Path, PathBuf},
5
- process::Stdio,
6
- sync::Arc,
2
+ collections::{HashMap, HashSet},
3
+ env,
4
+ io::{self, Write},
5
+ path::{Path, PathBuf},
6
+ process::Stdio,
7
+ sync::Arc,
7
8
  time::{Duration, Instant},
8
9
  };
9
10
 
@@ -15,7 +16,10 @@ use agentpal_protocol::{
15
16
  RiskLevel, SessionEvent, SessionState, SessionSummary, WorkspaceSnapshot, WorktreeSummary,
16
17
  };
17
18
  use clap::Args;
18
- use futures_util::{SinkExt, StreamExt, stream::SplitSink};
19
+ use futures_util::{
20
+ SinkExt, StreamExt,
21
+ stream::{SplitSink, SplitStream},
22
+ };
19
23
  use local_ip_address::local_ip;
20
24
  use qrcode::{QrCode, render::unicode};
21
25
  use serde::{Deserialize, Serialize};
@@ -396,26 +400,17 @@ async fn run_connect_loop(
396
400
  codex_version: Option<String>,
397
401
  codex_websocket_url: &str,
398
402
  ) -> anyhow::Result<()> {
399
- tokio::time::sleep(Duration::from_millis(600)).await;
400
- let relay_url = normalize_ws_url(&args.relay_url);
401
- let (relay_socket, _) = connect_async(relay_url.as_str()).await?;
402
- let (codex_socket, _) = connect_async(codex_websocket_url).await?;
403
- let (relay_write, mut relay_read) = relay_socket.split();
404
- let (codex_write, codex_read) = codex_socket.split();
405
- let relay_write = Arc::new(Mutex::new(relay_write));
406
- let codex_write = Arc::new(Mutex::new(codex_write));
407
- let (codex_tx, mut codex_rx) = mpsc::unbounded_channel::<Value>();
408
- let host_id = args.host_id.clone().unwrap_or_else(default_host_id);
409
- let session_id = args.session_id.clone();
410
- let workspace_owned = workspace.to_owned();
411
- let thread_ids = Arc::new(Mutex::new(HashMap::<String, String>::new()));
412
- let pending_thread_starts = Arc::new(Mutex::new(HashMap::<u64, String>::new()));
413
- let pending_thread_loads = Arc::new(Mutex::new(HashMap::<u64, String>::new()));
414
- let picker_items = Arc::new(Mutex::new(default_picker_items()));
415
- let seq = Arc::new(Mutex::new(0_u64));
416
-
417
- relay_send(
418
- &relay_write,
403
+ tokio::time::sleep(Duration::from_millis(600)).await;
404
+ let relay_url = normalize_ws_url(&args.relay_url);
405
+ let (relay_socket, _) = connect_async(relay_url.as_str()).await?;
406
+ let (relay_write, mut relay_read) = relay_socket.split();
407
+ let relay_write = Arc::new(Mutex::new(relay_write));
408
+ let host_id = args.host_id.clone().unwrap_or_else(default_host_id);
409
+ let session_id = args.session_id.clone();
410
+ let workspace_owned = workspace.to_owned();
411
+
412
+ relay_send(
413
+ &relay_write,
419
414
  &RelayClientMessage::Register {
420
415
  role: RelayClientRole::Host,
421
416
  client_id: format!("{host_id}-host"),
@@ -442,11 +437,23 @@ async fn run_connect_loop(
442
437
  pair_token: None,
443
438
  expires_in_seconds,
444
439
  },
445
- },
446
- )
447
- .await?;
448
- }
449
- publish_host_status(&relay_write, &host_id, host_name, workspace, 1).await?;
440
+ },
441
+ )
442
+ .await?;
443
+ print_pairing_response(&mut relay_read, &host_id, args.no_qr).await?;
444
+ }
445
+
446
+ let (codex_socket, _) = connect_async(codex_websocket_url).await?;
447
+ let (codex_write, codex_read) = codex_socket.split();
448
+ let codex_write = Arc::new(Mutex::new(codex_write));
449
+ let (codex_tx, mut codex_rx) = mpsc::unbounded_channel::<Value>();
450
+ let thread_ids = Arc::new(Mutex::new(HashMap::<String, String>::new()));
451
+ let pending_thread_starts = Arc::new(Mutex::new(HashMap::<u64, String>::new()));
452
+ let pending_thread_loads = Arc::new(Mutex::new(HashMap::<u64, String>::new()));
453
+ let picker_items = Arc::new(Mutex::new(default_picker_items()));
454
+ let seq = Arc::new(Mutex::new(0_u64));
455
+
456
+ publish_host_status(&relay_write, &host_id, host_name, workspace, 1).await?;
450
457
 
451
458
  send_codex_initialize(&codex_write).await?;
452
459
  request_codex_picker_sources(&codex_write, &seq, workspace).await?;
@@ -726,12 +733,45 @@ async fn run_connect_loop(
726
733
  if let Some(version) = codex_version {
727
734
  eprintln!("Codex connected through AgentPal Host ({version})");
728
735
  }
729
- Ok(())
730
- }
731
-
732
- fn relay_task_result(
733
- result: Result<anyhow::Result<()>, tokio::task::JoinError>,
734
- ) -> anyhow::Result<()> {
736
+ Ok(())
737
+ }
738
+
739
+ async fn print_pairing_response(
740
+ relay_read: &mut SplitStream<WebSocketStream<MaybeTlsStream<TcpStream>>>,
741
+ host_id: &str,
742
+ no_qr: bool,
743
+ ) -> anyhow::Result<()> {
744
+ while let Some(message) = relay_read.next().await {
745
+ let message = message?;
746
+ match message {
747
+ Message::Text(text) => {
748
+ let server_message: RelayServerMessage = serde_json::from_str(&text)?;
749
+ match server_message {
750
+ RelayServerMessage::PairCreated { pairing } => {
751
+ if pairing.host_id == host_id {
752
+ print_pairing_payload(&pairing, no_qr)?;
753
+ return Ok(());
754
+ }
755
+ }
756
+ RelayServerMessage::Error { message } => {
757
+ anyhow::bail!("AgentPal Relay error: {message}");
758
+ }
759
+ _ => {}
760
+ }
761
+ }
762
+ Message::Close(frame) => {
763
+ anyhow::bail!("relay websocket closed before pair response: {frame:?}");
764
+ }
765
+ Message::Binary(_) | Message::Ping(_) | Message::Pong(_) | Message::Frame(_) => {}
766
+ }
767
+ }
768
+
769
+ anyhow::bail!("relay websocket ended before pair response");
770
+ }
771
+
772
+ fn relay_task_result(
773
+ result: Result<anyhow::Result<()>, tokio::task::JoinError>,
774
+ ) -> anyhow::Result<()> {
735
775
  match result {
736
776
  Ok(Ok(())) => Ok(()),
737
777
  Ok(Err(error)) => Err(error),
@@ -2327,12 +2367,14 @@ fn print_pairing_payload(payload: &PairingPayload, no_qr: bool) -> anyhow::Resul
2327
2367
  .quiet_zone(true)
2328
2368
  .module_dimensions(2, 1)
2329
2369
  .build();
2330
- println!();
2331
- println!("{qr}");
2332
- }
2333
-
2334
- Ok(())
2335
- }
2370
+ println!();
2371
+ println!("{qr}");
2372
+ }
2373
+
2374
+ io::stdout().flush()?;
2375
+
2376
+ Ok(())
2377
+ }
2336
2378
 
2337
2379
  fn pair_url(payload: &PairingPayload) -> String {
2338
2380
  let mut url = url::Url::parse("agentpal://pair").expect("static pair url parses");
@@ -32,6 +32,8 @@ enum CodexSubcommand {
32
32
 
33
33
  #[tokio::main]
34
34
  async fn main() -> Result<()> {
35
+ let _ = rustls::crypto::ring::default_provider().install_default();
36
+
35
37
  let cli = Cli::parse();
36
38
 
37
39
  match cli.command {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentpal",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "AgentPal CLI for pairing local coding agents with the AgentPal mobile app.",
5
5
  "license": "MIT",
6
6
  "keywords": [