agentpal 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/Cargo.lock +2155 -0
- package/Cargo.toml +30 -0
- package/LICENSE +21 -0
- package/README.md +43 -0
- package/bin/agentpal.mjs +256 -0
- package/crates/host/Cargo.toml +20 -0
- package/crates/host/src/codex.rs +2486 -0
- package/crates/host/src/main.rs +61 -0
- package/crates/protocol/Cargo.toml +11 -0
- package/crates/protocol/src/lib.rs +576 -0
- package/crates/relay/Cargo.toml +22 -0
- package/crates/relay/src/main.rs +2097 -0
- package/package.json +46 -0
package/Cargo.toml
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
[workspace]
|
|
2
|
+
resolver = "2"
|
|
3
|
+
members = [
|
|
4
|
+
"crates/host",
|
|
5
|
+
"crates/protocol",
|
|
6
|
+
"crates/relay",
|
|
7
|
+
]
|
|
8
|
+
|
|
9
|
+
[workspace.package]
|
|
10
|
+
edition = "2024"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
version = "0.1.0"
|
|
13
|
+
|
|
14
|
+
[workspace.dependencies]
|
|
15
|
+
agentpal-protocol = { path = "crates/protocol" }
|
|
16
|
+
anyhow = "1.0.100"
|
|
17
|
+
axum = { version = "0.8.9", features = ["ws"] }
|
|
18
|
+
clap = { version = "4.5.53", features = ["derive", "env"] }
|
|
19
|
+
futures-util = "0.3.31"
|
|
20
|
+
local-ip-address = "0.6.13"
|
|
21
|
+
qrcode = { version = "0.14.1", default-features = false }
|
|
22
|
+
serde = { version = "1.0.228", features = ["derive"] }
|
|
23
|
+
serde_json = "1.0.145"
|
|
24
|
+
time = { version = "0.3.44", features = ["serde", "formatting", "macros", "parsing"] }
|
|
25
|
+
tokio = { version = "1.48.0", features = ["macros", "net", "process", "rt-multi-thread", "signal", "sync", "time"] }
|
|
26
|
+
tokio-tungstenite = "0.29.0"
|
|
27
|
+
tracing = "0.1.41"
|
|
28
|
+
tracing-subscriber = { version = "0.3.22", features = ["env-filter"] }
|
|
29
|
+
url = "2.5.7"
|
|
30
|
+
uuid = { version = "1.19.0", features = ["serde", "v4", "v7"] }
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 LnYo-Cly
|
|
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
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# AgentPal
|
|
2
|
+
|
|
3
|
+
AgentPal connects a local coding-agent host to the AgentPal mobile app through a Cloud Relay pairing flow.
|
|
4
|
+
|
|
5
|
+
This first npm release is a source-based CLI package: it runs the packaged Rust host and relay through `cargo run`.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
Run without installing:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npx agentpal@latest pair
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Or install globally:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install -g agentpal
|
|
19
|
+
agentpal pair
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Commands
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
agentpal pair
|
|
26
|
+
agentpal pair --workspace .
|
|
27
|
+
agentpal relay --host 0.0.0.0 --port 8790
|
|
28
|
+
agentpal host codex connect --workspace .
|
|
29
|
+
```
|
|
30
|
+
|
|
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:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
agentpal pair --workspace . --relay-url ws://127.0.0.1:8790/ws
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Requirements
|
|
38
|
+
|
|
39
|
+
- Node.js
|
|
40
|
+
- Rust toolchain with `cargo`
|
|
41
|
+
- Codex CLI available as `codex` for live host sessions
|
|
42
|
+
|
|
43
|
+
The npm package currently builds and runs the Rust host/relay from source through `cargo run`.
|
package/bin/agentpal.mjs
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import http from "node:http";
|
|
4
|
+
import https from "node:https";
|
|
5
|
+
import { spawn } from "node:child_process";
|
|
6
|
+
import process from "node:process";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
import { dirname, join, resolve } from "node:path";
|
|
10
|
+
|
|
11
|
+
const DEFAULT_PUBLIC_RELAY_URL = "wss://openagentpal-production.up.railway.app/ws";
|
|
12
|
+
const UPDATE_CHECK_URL = "https://registry.npmjs.org/agentpal/latest";
|
|
13
|
+
const UPDATE_CHECK_TIMEOUT_MS = 900;
|
|
14
|
+
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
15
|
+
const callerCwd = process.cwd();
|
|
16
|
+
const defaultCargoTargetDir = join(homedir(), ".agentpal", "cargo-target");
|
|
17
|
+
const packageMetadata = readPackageMetadata();
|
|
18
|
+
|
|
19
|
+
const args = process.argv.slice(2);
|
|
20
|
+
const command = args[0] ?? "help";
|
|
21
|
+
|
|
22
|
+
if (command === "help" || command === "--help" || command === "-h") {
|
|
23
|
+
printHelp();
|
|
24
|
+
process.exit(0);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (command === "pair") {
|
|
28
|
+
await maybeShowUpdateNotice();
|
|
29
|
+
runCargo([
|
|
30
|
+
"run",
|
|
31
|
+
"-p",
|
|
32
|
+
"agentpal-host",
|
|
33
|
+
"--",
|
|
34
|
+
"codex",
|
|
35
|
+
"connect",
|
|
36
|
+
"--create-pair",
|
|
37
|
+
...defaultRelayArgs(args.slice(1)),
|
|
38
|
+
...withDefaultWorkspaceArgs(args.slice(1))
|
|
39
|
+
]);
|
|
40
|
+
} else if (command === "relay") {
|
|
41
|
+
await maybeShowUpdateNotice();
|
|
42
|
+
runCargo(["run", "-p", "agentpal-relay", "--", ...args.slice(1)]);
|
|
43
|
+
} else if (command === "host") {
|
|
44
|
+
await maybeShowUpdateNotice();
|
|
45
|
+
runCargo(["run", "-p", "agentpal-host", "--", ...normalizeHostArgs(args.slice(1))]);
|
|
46
|
+
} else {
|
|
47
|
+
console.error(`Unknown agentpal command: ${command}`);
|
|
48
|
+
console.error("");
|
|
49
|
+
printHelp();
|
|
50
|
+
process.exit(2);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function defaultRelayArgs(passThrough) {
|
|
54
|
+
if (hasFlagValue(passThrough, "--relay-url")) {
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
const relayUrl = process.env.AGENTPAL_RELAY_URL ?? DEFAULT_PUBLIC_RELAY_URL;
|
|
58
|
+
return ["--relay-url", relayUrl];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function hasFlagValue(items, flag) {
|
|
62
|
+
return items.some((item) => item === flag || item.startsWith(`${flag}=`));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function normalizeWorkspaceArgs(items) {
|
|
66
|
+
const normalized = [];
|
|
67
|
+
for (let index = 0; index < items.length; index += 1) {
|
|
68
|
+
const item = items[index];
|
|
69
|
+
if (item === "--workspace" && index + 1 < items.length) {
|
|
70
|
+
normalized.push(item, resolveWorkspaceValue(items[index + 1]));
|
|
71
|
+
index += 1;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (item.startsWith("--workspace=")) {
|
|
75
|
+
normalized.push(`--workspace=${resolveWorkspaceValue(item.slice("--workspace=".length))}`);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
normalized.push(item);
|
|
79
|
+
}
|
|
80
|
+
return normalized;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function withDefaultWorkspaceArgs(items) {
|
|
84
|
+
const normalized = normalizeWorkspaceArgs(items);
|
|
85
|
+
if (hasFlagValue(normalized, "--workspace")) {
|
|
86
|
+
return normalized;
|
|
87
|
+
}
|
|
88
|
+
return [...normalized, "--workspace", callerCwd];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function normalizeHostArgs(items) {
|
|
92
|
+
const normalized = normalizeWorkspaceArgs(items);
|
|
93
|
+
if (hostCommandNeedsDefaultWorkspace(normalized) && !hasFlagValue(normalized, "--workspace")) {
|
|
94
|
+
return [...normalized, "--workspace", callerCwd];
|
|
95
|
+
}
|
|
96
|
+
return normalized;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function hostCommandNeedsDefaultWorkspace(items) {
|
|
100
|
+
return items[0] === "codex" && (items[1] === "probe" || items[1] === "connect");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function resolveWorkspaceValue(value) {
|
|
104
|
+
if (!value) {
|
|
105
|
+
return value;
|
|
106
|
+
}
|
|
107
|
+
return resolve(callerCwd, value);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function maybeShowUpdateNotice() {
|
|
111
|
+
if (process.env.AGENTPAL_NO_UPDATE_CHECK === "1") {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const latest = await fetchLatestVersion().catch(() => null);
|
|
116
|
+
const current = packageMetadata.version;
|
|
117
|
+
if (!latest || !current || !isVersionGreater(latest, current)) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
console.error(`AgentPal ${latest} is available. Update with: npm install -g agentpal@latest`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function fetchLatestVersion() {
|
|
125
|
+
const url = process.env.AGENTPAL_UPDATE_CHECK_URL ?? UPDATE_CHECK_URL;
|
|
126
|
+
const response = await fetchJson(url, UPDATE_CHECK_TIMEOUT_MS);
|
|
127
|
+
const version = typeof response.version === "string" ? response.version.trim() : "";
|
|
128
|
+
return version || null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function fetchJson(url, timeoutMs) {
|
|
132
|
+
return new Promise((resolve, reject) => {
|
|
133
|
+
let parsed;
|
|
134
|
+
try {
|
|
135
|
+
parsed = new URL(url);
|
|
136
|
+
} catch (error) {
|
|
137
|
+
reject(error);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const transport = parsed.protocol === "http:" ? http : https;
|
|
142
|
+
const request = transport.get(parsed, { headers: { accept: "application/json" } }, (response) => {
|
|
143
|
+
if (response.statusCode !== 200) {
|
|
144
|
+
response.resume();
|
|
145
|
+
reject(new Error(`unexpected status ${response.statusCode}`));
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
let body = "";
|
|
150
|
+
response.setEncoding("utf8");
|
|
151
|
+
response.on("data", (chunk) => {
|
|
152
|
+
body += chunk;
|
|
153
|
+
if (body.length > 64 * 1024) {
|
|
154
|
+
request.destroy(new Error("response too large"));
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
response.on("end", () => {
|
|
158
|
+
try {
|
|
159
|
+
resolve(JSON.parse(body));
|
|
160
|
+
} catch (error) {
|
|
161
|
+
reject(error);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
request.setTimeout(timeoutMs, () => {
|
|
167
|
+
request.destroy(new Error("update check timed out"));
|
|
168
|
+
});
|
|
169
|
+
request.on("error", reject);
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function readPackageMetadata() {
|
|
174
|
+
try {
|
|
175
|
+
return JSON.parse(readFileSync(resolve(packageRoot, "package.json"), "utf8"));
|
|
176
|
+
} catch {
|
|
177
|
+
return {};
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function isVersionGreater(candidate, current) {
|
|
182
|
+
const candidateParts = parseVersion(candidate);
|
|
183
|
+
const currentParts = parseVersion(current);
|
|
184
|
+
if (!candidateParts || !currentParts) {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
for (let index = 0; index < 3; index += 1) {
|
|
189
|
+
if (candidateParts[index] > currentParts[index]) {
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
if (candidateParts[index] < currentParts[index]) {
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function parseVersion(version) {
|
|
200
|
+
const match = String(version).trim().match(/^v?(\d+)\.(\d+)\.(\d+)/);
|
|
201
|
+
if (!match) {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function runCargo(cargoArgs) {
|
|
208
|
+
const env = { ...process.env };
|
|
209
|
+
env.CARGO_TARGET_DIR ??= defaultCargoTargetDir;
|
|
210
|
+
|
|
211
|
+
const child = spawn("cargo", cargoArgs, {
|
|
212
|
+
cwd: packageRoot,
|
|
213
|
+
env,
|
|
214
|
+
stdio: "inherit"
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
child.on("exit", (code, signal) => {
|
|
218
|
+
if (signal) {
|
|
219
|
+
process.kill(process.pid, signal);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
process.exit(code ?? 1);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
child.on("error", (error) => {
|
|
226
|
+
console.error(`Failed to start cargo: ${error.message}`);
|
|
227
|
+
process.exit(1);
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function printHelp() {
|
|
232
|
+
console.log(`AgentPal CLI
|
|
233
|
+
|
|
234
|
+
Usage:
|
|
235
|
+
agentpal pair [options]
|
|
236
|
+
agentpal relay [agentpal-relay options]
|
|
237
|
+
agentpal host [agentpal-host options]
|
|
238
|
+
|
|
239
|
+
Commands:
|
|
240
|
+
pair Start the Codex Host, create a Cloud Relay pairing, and print URL + QR.
|
|
241
|
+
relay Run the local relay service.
|
|
242
|
+
host Pass through to the Rust host CLI.
|
|
243
|
+
|
|
244
|
+
Defaults:
|
|
245
|
+
agentpal pair uses AGENTPAL_RELAY_URL when set, otherwise ${DEFAULT_PUBLIC_RELAY_URL}.
|
|
246
|
+
agentpal pair uses the current directory as --workspace unless one is supplied.
|
|
247
|
+
Local development can pass --relay-url ws://127.0.0.1:8790/ws.
|
|
248
|
+
|
|
249
|
+
Examples:
|
|
250
|
+
agentpal relay --host 0.0.0.0 --port 8790
|
|
251
|
+
agentpal pair
|
|
252
|
+
agentpal pair --workspace .
|
|
253
|
+
agentpal pair --workspace . --relay-url ws://127.0.0.1:8790/ws
|
|
254
|
+
agentpal host codex connect --workspace .
|
|
255
|
+
`);
|
|
256
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "agentpal-host"
|
|
3
|
+
edition.workspace = true
|
|
4
|
+
license.workspace = true
|
|
5
|
+
version.workspace = true
|
|
6
|
+
|
|
7
|
+
[dependencies]
|
|
8
|
+
agentpal-protocol.workspace = true
|
|
9
|
+
anyhow.workspace = true
|
|
10
|
+
clap.workspace = true
|
|
11
|
+
futures-util.workspace = true
|
|
12
|
+
local-ip-address.workspace = true
|
|
13
|
+
qrcode.workspace = true
|
|
14
|
+
serde.workspace = true
|
|
15
|
+
serde_json.workspace = true
|
|
16
|
+
time.workspace = true
|
|
17
|
+
tokio.workspace = true
|
|
18
|
+
tokio-tungstenite.workspace = true
|
|
19
|
+
url.workspace = true
|
|
20
|
+
uuid.workspace = true
|