agent-browser 0.3.1 → 0.3.2
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/bin/agent-browser-darwin-arm64 +0 -0
- package/bin/agent-browser-darwin-x64 +0 -0
- package/bin/agent-browser-linux-arm64 +0 -0
- package/bin/agent-browser-linux-x64 +0 -0
- package/package.json +6 -1
- package/.prettierrc +0 -7
- package/AGENTS.md +0 -26
- package/benchmark/benchmark.ts +0 -521
- package/benchmark/run.ts +0 -322
- package/cli/Cargo.lock +0 -114
- package/cli/Cargo.toml +0 -17
- package/cli/src/main.rs +0 -332
- package/docker/Dockerfile.build +0 -31
- package/docker/docker-compose.yml +0 -68
- package/src/actions.ts +0 -1670
- package/src/browser.test.ts +0 -157
- package/src/browser.ts +0 -686
- package/src/cli-light.ts +0 -457
- package/src/client.ts +0 -150
- package/src/daemon.ts +0 -187
- package/src/index.ts +0 -1185
- package/src/protocol.test.ts +0 -216
- package/src/protocol.ts +0 -852
- package/src/snapshot.ts +0 -380
- package/src/types.ts +0 -913
- package/tsconfig.json +0 -28
- package/vitest.config.ts +0 -9
package/cli/src/main.rs
DELETED
|
@@ -1,332 +0,0 @@
|
|
|
1
|
-
use serde::{Deserialize, Serialize};
|
|
2
|
-
use serde_json::{json, Value};
|
|
3
|
-
use std::env;
|
|
4
|
-
use std::fs;
|
|
5
|
-
use std::io::{BufRead, BufReader, Write};
|
|
6
|
-
use std::os::unix::net::UnixStream;
|
|
7
|
-
use std::path::PathBuf;
|
|
8
|
-
use std::process::{exit, Command, Stdio};
|
|
9
|
-
use std::thread;
|
|
10
|
-
use std::time::Duration;
|
|
11
|
-
|
|
12
|
-
#[derive(Serialize)]
|
|
13
|
-
struct Request {
|
|
14
|
-
id: String,
|
|
15
|
-
action: String,
|
|
16
|
-
#[serde(flatten)]
|
|
17
|
-
extra: Value,
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
#[derive(Deserialize, Serialize)]
|
|
21
|
-
struct Response {
|
|
22
|
-
success: bool,
|
|
23
|
-
data: Option<Value>,
|
|
24
|
-
error: Option<String>,
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
fn get_socket_path() -> PathBuf {
|
|
28
|
-
let session = env::var("AGENT_BROWSER_SESSION").unwrap_or_else(|_| "default".to_string());
|
|
29
|
-
let tmp = env::temp_dir();
|
|
30
|
-
tmp.join(format!("agent-browser-{}.sock", session))
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
fn get_pid_path() -> PathBuf {
|
|
34
|
-
let session = env::var("AGENT_BROWSER_SESSION").unwrap_or_else(|_| "default".to_string());
|
|
35
|
-
let tmp = env::temp_dir();
|
|
36
|
-
tmp.join(format!("agent-browser-{}.pid", session))
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
fn is_daemon_running() -> bool {
|
|
40
|
-
let pid_path = get_pid_path();
|
|
41
|
-
if !pid_path.exists() {
|
|
42
|
-
return false;
|
|
43
|
-
}
|
|
44
|
-
if let Ok(pid_str) = fs::read_to_string(&pid_path) {
|
|
45
|
-
if let Ok(pid) = pid_str.trim().parse::<i32>() {
|
|
46
|
-
// Check if process exists
|
|
47
|
-
unsafe {
|
|
48
|
-
return libc::kill(pid, 0) == 0;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
false
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
fn ensure_daemon() -> Result<(), String> {
|
|
56
|
-
let socket_path = get_socket_path();
|
|
57
|
-
|
|
58
|
-
if is_daemon_running() && socket_path.exists() {
|
|
59
|
-
return Ok(());
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Find daemon.js
|
|
63
|
-
let exe_path = env::current_exe().map_err(|e| e.to_string())?;
|
|
64
|
-
let exe_dir = exe_path.parent().unwrap();
|
|
65
|
-
|
|
66
|
-
let daemon_paths = [
|
|
67
|
-
exe_dir.join("daemon.js"),
|
|
68
|
-
exe_dir.join("../dist/daemon.js"),
|
|
69
|
-
PathBuf::from("dist/daemon.js"),
|
|
70
|
-
];
|
|
71
|
-
|
|
72
|
-
let daemon_path = daemon_paths
|
|
73
|
-
.iter()
|
|
74
|
-
.find(|p| p.exists())
|
|
75
|
-
.ok_or("Daemon not found. Run from project directory or ensure daemon.js is alongside binary.")?;
|
|
76
|
-
|
|
77
|
-
// Start daemon
|
|
78
|
-
let session = env::var("AGENT_BROWSER_SESSION").unwrap_or_else(|_| "default".to_string());
|
|
79
|
-
Command::new("node")
|
|
80
|
-
.arg(daemon_path)
|
|
81
|
-
.env("AGENT_BROWSER_DAEMON", "1")
|
|
82
|
-
.env("AGENT_BROWSER_SESSION", &session)
|
|
83
|
-
.stdin(Stdio::null())
|
|
84
|
-
.stdout(Stdio::null())
|
|
85
|
-
.stderr(Stdio::null())
|
|
86
|
-
.spawn()
|
|
87
|
-
.map_err(|e| format!("Failed to start daemon: {}", e))?;
|
|
88
|
-
|
|
89
|
-
// Wait for socket
|
|
90
|
-
for _ in 0..50 {
|
|
91
|
-
if socket_path.exists() {
|
|
92
|
-
return Ok(());
|
|
93
|
-
}
|
|
94
|
-
thread::sleep(Duration::from_millis(100));
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
Err("Daemon failed to start".to_string())
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
fn send_command(cmd: Value) -> Result<Response, String> {
|
|
101
|
-
let socket_path = get_socket_path();
|
|
102
|
-
let mut stream = UnixStream::connect(&socket_path)
|
|
103
|
-
.map_err(|e| format!("Failed to connect: {}", e))?;
|
|
104
|
-
|
|
105
|
-
stream.set_read_timeout(Some(Duration::from_secs(30))).ok();
|
|
106
|
-
stream.set_write_timeout(Some(Duration::from_secs(5))).ok();
|
|
107
|
-
|
|
108
|
-
let mut json_str = serde_json::to_string(&cmd).map_err(|e| e.to_string())?;
|
|
109
|
-
json_str.push('\n');
|
|
110
|
-
|
|
111
|
-
stream.write_all(json_str.as_bytes())
|
|
112
|
-
.map_err(|e| format!("Failed to send: {}", e))?;
|
|
113
|
-
|
|
114
|
-
let mut reader = BufReader::new(stream);
|
|
115
|
-
let mut response_line = String::new();
|
|
116
|
-
reader.read_line(&mut response_line)
|
|
117
|
-
.map_err(|e| format!("Failed to read: {}", e))?;
|
|
118
|
-
|
|
119
|
-
serde_json::from_str(&response_line)
|
|
120
|
-
.map_err(|e| format!("Invalid response: {}", e))
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
fn gen_id() -> String {
|
|
124
|
-
format!("r{}", std::time::SystemTime::now()
|
|
125
|
-
.duration_since(std::time::UNIX_EPOCH)
|
|
126
|
-
.unwrap()
|
|
127
|
-
.as_micros() % 1000000)
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
fn parse_command(args: &[String]) -> Option<Value> {
|
|
131
|
-
if args.is_empty() {
|
|
132
|
-
return None;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
let cmd = args[0].as_str();
|
|
136
|
-
let rest: Vec<&str> = args[1..].iter().map(|s| s.as_str()).collect();
|
|
137
|
-
let id = gen_id();
|
|
138
|
-
|
|
139
|
-
match cmd {
|
|
140
|
-
"open" | "goto" | "navigate" => {
|
|
141
|
-
let url = rest.get(0)?;
|
|
142
|
-
let url = if url.starts_with("http") {
|
|
143
|
-
url.to_string()
|
|
144
|
-
} else {
|
|
145
|
-
format!("https://{}", url)
|
|
146
|
-
};
|
|
147
|
-
Some(json!({ "id": id, "action": "navigate", "url": url }))
|
|
148
|
-
}
|
|
149
|
-
"click" => Some(json!({ "id": id, "action": "click", "selector": rest.get(0)? })),
|
|
150
|
-
"fill" => Some(json!({ "id": id, "action": "fill", "selector": rest.get(0)?, "value": rest[1..].join(" ") })),
|
|
151
|
-
"type" => Some(json!({ "id": id, "action": "type", "selector": rest.get(0)?, "text": rest[1..].join(" ") })),
|
|
152
|
-
"hover" => Some(json!({ "id": id, "action": "hover", "selector": rest.get(0)? })),
|
|
153
|
-
"snapshot" => {
|
|
154
|
-
let mut cmd = json!({ "id": id, "action": "snapshot" });
|
|
155
|
-
let obj = cmd.as_object_mut().unwrap();
|
|
156
|
-
for (i, arg) in rest.iter().enumerate() {
|
|
157
|
-
match *arg {
|
|
158
|
-
"-i" | "--interactive" => { obj.insert("interactive".to_string(), json!(true)); }
|
|
159
|
-
"-c" | "--compact" => { obj.insert("compact".to_string(), json!(true)); }
|
|
160
|
-
"-d" | "--depth" => {
|
|
161
|
-
if let Some(d) = rest.get(i + 1) {
|
|
162
|
-
if let Ok(n) = d.parse::<i32>() {
|
|
163
|
-
obj.insert("maxDepth".to_string(), json!(n));
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
"-s" | "--selector" => {
|
|
168
|
-
if let Some(s) = rest.get(i + 1) {
|
|
169
|
-
obj.insert("selector".to_string(), json!(s));
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
_ => {}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
Some(cmd)
|
|
176
|
-
}
|
|
177
|
-
"screenshot" => Some(json!({ "id": id, "action": "screenshot", "path": rest.get(0) })),
|
|
178
|
-
"close" | "quit" | "exit" => Some(json!({ "id": id, "action": "close" })),
|
|
179
|
-
"get" => match rest.get(0).map(|s| *s) {
|
|
180
|
-
Some("text") => Some(json!({ "id": id, "action": "gettext", "selector": rest.get(1)? })),
|
|
181
|
-
Some("url") => Some(json!({ "id": id, "action": "url" })),
|
|
182
|
-
Some("title") => Some(json!({ "id": id, "action": "title" })),
|
|
183
|
-
_ => None,
|
|
184
|
-
},
|
|
185
|
-
"press" => Some(json!({ "id": id, "action": "press", "key": rest.get(0)? })),
|
|
186
|
-
"wait" => {
|
|
187
|
-
if let Some(arg) = rest.get(0) {
|
|
188
|
-
if arg.parse::<u64>().is_ok() {
|
|
189
|
-
Some(json!({ "id": id, "action": "wait", "timeout": arg.parse::<u64>().unwrap() }))
|
|
190
|
-
} else {
|
|
191
|
-
Some(json!({ "id": id, "action": "wait", "selector": arg }))
|
|
192
|
-
}
|
|
193
|
-
} else {
|
|
194
|
-
None
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
"back" => Some(json!({ "id": id, "action": "back" })),
|
|
198
|
-
"forward" => Some(json!({ "id": id, "action": "forward" })),
|
|
199
|
-
"reload" => Some(json!({ "id": id, "action": "reload" })),
|
|
200
|
-
"eval" => Some(json!({ "id": id, "action": "evaluate", "script": rest.join(" ") })),
|
|
201
|
-
_ => None,
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
fn print_response(resp: &Response, json_mode: bool) {
|
|
206
|
-
if json_mode {
|
|
207
|
-
println!("{}", serde_json::to_string(resp).unwrap_or_default());
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
if !resp.success {
|
|
212
|
-
eprintln!("\x1b[31m✗ Error:\x1b[0m {}", resp.error.as_deref().unwrap_or("Unknown error"));
|
|
213
|
-
exit(1);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
if let Some(data) = &resp.data {
|
|
217
|
-
if let Some(url) = data.get("url").and_then(|v| v.as_str()) {
|
|
218
|
-
if let Some(title) = data.get("title").and_then(|v| v.as_str()) {
|
|
219
|
-
println!("\x1b[32m✓\x1b[0m \x1b[1m{}\x1b[0m", title);
|
|
220
|
-
println!("\x1b[2m {}\x1b[0m", url);
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
223
|
-
println!("{}", url);
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
226
|
-
if let Some(snapshot) = data.get("snapshot").and_then(|v| v.as_str()) {
|
|
227
|
-
println!("{}", snapshot);
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
if let Some(title) = data.get("title").and_then(|v| v.as_str()) {
|
|
231
|
-
println!("{}", title);
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
|
-
if let Some(text) = data.get("text").and_then(|v| v.as_str()) {
|
|
235
|
-
println!("{}", text);
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
if let Some(result) = data.get("result") {
|
|
239
|
-
println!("{}", serde_json::to_string_pretty(result).unwrap_or_default());
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
if data.get("closed").is_some() {
|
|
243
|
-
println!("\x1b[32m✓\x1b[0m Browser closed");
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
println!("\x1b[32m✓\x1b[0m Done");
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
fn print_help() {
|
|
251
|
-
println!(r#"
|
|
252
|
-
agent-browser - fast browser automation CLI (Rust)
|
|
253
|
-
|
|
254
|
-
Usage: agent-browser <command> [args] [--json]
|
|
255
|
-
|
|
256
|
-
Commands:
|
|
257
|
-
open <url> Navigate to URL
|
|
258
|
-
click <sel> Click element (@ref from snapshot)
|
|
259
|
-
fill <sel> <text> Fill input
|
|
260
|
-
type <sel> <text> Type text
|
|
261
|
-
hover <sel> Hover element
|
|
262
|
-
snapshot [opts] Get accessibility tree with refs
|
|
263
|
-
screenshot [path] Take screenshot
|
|
264
|
-
get text <sel> Get text content
|
|
265
|
-
get url Get current URL
|
|
266
|
-
get title Get page title
|
|
267
|
-
press <key> Press keyboard key
|
|
268
|
-
wait <ms|sel> Wait for time or element
|
|
269
|
-
eval <js> Evaluate JavaScript
|
|
270
|
-
close Close browser
|
|
271
|
-
|
|
272
|
-
Snapshot Options:
|
|
273
|
-
-i, --interactive Only interactive elements
|
|
274
|
-
-c, --compact Remove empty structural elements
|
|
275
|
-
-d, --depth <n> Limit tree depth
|
|
276
|
-
-s, --selector <sel> Scope to CSS selector
|
|
277
|
-
|
|
278
|
-
Options:
|
|
279
|
-
--json Output JSON
|
|
280
|
-
|
|
281
|
-
Examples:
|
|
282
|
-
agent-browser open example.com
|
|
283
|
-
agent-browser snapshot -i
|
|
284
|
-
agent-browser click @e2
|
|
285
|
-
"#);
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
fn main() {
|
|
289
|
-
let args: Vec<String> = env::args().skip(1).collect();
|
|
290
|
-
let json_mode = args.iter().any(|a| a == "--json");
|
|
291
|
-
let clean_args: Vec<String> = args.iter().filter(|a| !a.starts_with("--")).cloned().collect();
|
|
292
|
-
|
|
293
|
-
if clean_args.is_empty() || args.iter().any(|a| a == "--help" || a == "-h") {
|
|
294
|
-
print_help();
|
|
295
|
-
return;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
let cmd = match parse_command(&clean_args) {
|
|
299
|
-
Some(c) => c,
|
|
300
|
-
None => {
|
|
301
|
-
eprintln!("\x1b[31mUnknown command:\x1b[0m {}", clean_args.get(0).unwrap_or(&String::new()));
|
|
302
|
-
exit(1);
|
|
303
|
-
}
|
|
304
|
-
};
|
|
305
|
-
|
|
306
|
-
if let Err(e) = ensure_daemon() {
|
|
307
|
-
if json_mode {
|
|
308
|
-
println!(r#"{{"success":false,"error":"{}"}}"#, e);
|
|
309
|
-
} else {
|
|
310
|
-
eprintln!("\x1b[31m✗ Error:\x1b[0m {}", e);
|
|
311
|
-
}
|
|
312
|
-
exit(1);
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
match send_command(cmd) {
|
|
316
|
-
Ok(resp) => {
|
|
317
|
-
let success = resp.success;
|
|
318
|
-
print_response(&resp, json_mode);
|
|
319
|
-
if !success {
|
|
320
|
-
exit(1);
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
Err(e) => {
|
|
324
|
-
if json_mode {
|
|
325
|
-
println!(r#"{{"success":false,"error":"{}"}}"#, e);
|
|
326
|
-
} else {
|
|
327
|
-
eprintln!("\x1b[31m✗ Error:\x1b[0m {}", e);
|
|
328
|
-
}
|
|
329
|
-
exit(1);
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
}
|
package/docker/Dockerfile.build
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
# Multi-platform Rust cross-compilation image
|
|
2
|
-
FROM rust:1.85-bookworm
|
|
3
|
-
|
|
4
|
-
# Install cross-compilation toolchains
|
|
5
|
-
RUN apt-get update && apt-get install -y \
|
|
6
|
-
gcc-aarch64-linux-gnu \
|
|
7
|
-
gcc-x86-64-linux-gnu \
|
|
8
|
-
libc6-dev-arm64-cross \
|
|
9
|
-
libc6-dev-amd64-cross \
|
|
10
|
-
mingw-w64 \
|
|
11
|
-
&& rm -rf /var/lib/apt/lists/*
|
|
12
|
-
|
|
13
|
-
# Add Rust targets (Unix only - Windows uses Node.js fallback due to Unix socket dependency)
|
|
14
|
-
RUN rustup target add \
|
|
15
|
-
x86_64-unknown-linux-gnu \
|
|
16
|
-
aarch64-unknown-linux-gnu \
|
|
17
|
-
x86_64-apple-darwin \
|
|
18
|
-
aarch64-apple-darwin
|
|
19
|
-
|
|
20
|
-
# Install cargo-zigbuild for easier cross-compilation (especially macOS)
|
|
21
|
-
RUN curl -sSL https://ziglang.org/download/0.13.0/zig-linux-x86_64-0.13.0.tar.xz | tar -xJ -C /opt \
|
|
22
|
-
&& ln -s /opt/zig-linux-x86_64-0.13.0/zig /usr/local/bin/zig
|
|
23
|
-
RUN cargo install cargo-zigbuild
|
|
24
|
-
|
|
25
|
-
# Configure linkers for cross-compilation
|
|
26
|
-
RUN mkdir -p /.cargo
|
|
27
|
-
RUN echo '[target.aarch64-unknown-linux-gnu]\nlinker = "aarch64-linux-gnu-gcc"\n\n[target.x86_64-pc-windows-gnu]\nlinker = "x86_64-w64-mingw32-gcc"\n' > /.cargo/config.toml
|
|
28
|
-
|
|
29
|
-
WORKDIR /build
|
|
30
|
-
|
|
31
|
-
ENTRYPOINT ["/bin/bash"]
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
# Docker Compose for building agent-browser
|
|
2
|
-
# Usage: docker compose -f docker/docker-compose.yml run build-all
|
|
3
|
-
#
|
|
4
|
-
# Note: Windows is skipped because the Rust CLI uses Unix sockets.
|
|
5
|
-
# Windows users will use the Node.js fallback automatically.
|
|
6
|
-
|
|
7
|
-
services:
|
|
8
|
-
# Build for all Unix platforms
|
|
9
|
-
build-all:
|
|
10
|
-
build:
|
|
11
|
-
context: ..
|
|
12
|
-
dockerfile: docker/Dockerfile.build
|
|
13
|
-
volumes:
|
|
14
|
-
- ../cli:/build
|
|
15
|
-
- ../bin:/output
|
|
16
|
-
command: |
|
|
17
|
-
-c '
|
|
18
|
-
set -e
|
|
19
|
-
echo "Building for all platforms..."
|
|
20
|
-
|
|
21
|
-
# Linux x64
|
|
22
|
-
echo "→ Linux x64"
|
|
23
|
-
cargo zigbuild --release --target x86_64-unknown-linux-gnu
|
|
24
|
-
cp /build/target/x86_64-unknown-linux-gnu/release/agent-browser /output/agent-browser-linux-x64
|
|
25
|
-
chmod +x /output/agent-browser-linux-x64
|
|
26
|
-
|
|
27
|
-
# Linux ARM64
|
|
28
|
-
echo "→ Linux ARM64"
|
|
29
|
-
cargo zigbuild --release --target aarch64-unknown-linux-gnu
|
|
30
|
-
cp /build/target/aarch64-unknown-linux-gnu/release/agent-browser /output/agent-browser-linux-arm64
|
|
31
|
-
chmod +x /output/agent-browser-linux-arm64
|
|
32
|
-
|
|
33
|
-
# macOS x64
|
|
34
|
-
echo "→ macOS x64"
|
|
35
|
-
cargo zigbuild --release --target x86_64-apple-darwin
|
|
36
|
-
cp /build/target/x86_64-apple-darwin/release/agent-browser /output/agent-browser-darwin-x64
|
|
37
|
-
chmod +x /output/agent-browser-darwin-x64
|
|
38
|
-
|
|
39
|
-
# macOS ARM64
|
|
40
|
-
echo "→ macOS ARM64"
|
|
41
|
-
cargo zigbuild --release --target aarch64-apple-darwin
|
|
42
|
-
cp /build/target/aarch64-apple-darwin/release/agent-browser /output/agent-browser-darwin-arm64
|
|
43
|
-
chmod +x /output/agent-browser-darwin-arm64
|
|
44
|
-
|
|
45
|
-
echo ""
|
|
46
|
-
echo "✓ All platforms built successfully!"
|
|
47
|
-
echo "(Windows uses Node.js fallback - Unix sockets not supported)"
|
|
48
|
-
ls -la /output/agent-browser-*
|
|
49
|
-
'
|
|
50
|
-
|
|
51
|
-
# Build for a single target (override with TARGET env var)
|
|
52
|
-
build-single:
|
|
53
|
-
build:
|
|
54
|
-
context: ..
|
|
55
|
-
dockerfile: docker/Dockerfile.build
|
|
56
|
-
volumes:
|
|
57
|
-
- ../cli:/build
|
|
58
|
-
- ../bin:/output
|
|
59
|
-
environment:
|
|
60
|
-
- TARGET=${TARGET:-x86_64-unknown-linux-gnu}
|
|
61
|
-
- OUTPUT_NAME=${OUTPUT_NAME:-agent-browser-linux-x64}
|
|
62
|
-
command: |
|
|
63
|
-
-c '
|
|
64
|
-
cargo zigbuild --release --target $TARGET
|
|
65
|
-
cp /build/target/$TARGET/release/agent-browser* /output/$OUTPUT_NAME
|
|
66
|
-
chmod +x /output/$OUTPUT_NAME 2>/dev/null || true
|
|
67
|
-
echo "✓ Built $OUTPUT_NAME"
|
|
68
|
-
'
|