1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
//! Clap-derived CLI schema — the structure that `clap::Parser::parse`
//! fills from `std::env::args`.
use crate::Mode;
use anyhow::Result;
use clap::{Parser, Subcommand};
use std::path::PathBuf;
#[derive(Parser, Debug, Clone)]
#[command(
name = "burn",
version,
about = "Sandboxed JavaScript runtime",
long_about = "Execute JavaScript in the Afterburner sandbox. \
Reads .js files, evaluates inline code, pipes UDFs through stdin."
)]
pub struct Cli {
#[command(subcommand)]
pub command: Option<Cmd>,
/// Positional fallback — when no subcommand is given but a path is,
/// this is treated as `burn run <path>`. Matches the user expectation
/// of `burn ./script.js` working with zero ceremony.
#[arg(value_name = "FILE")]
pub file: Option<PathBuf>,
/// Eval inline source (when not using a subcommand).
#[arg(short = 'e', long = "eval", value_name = "CODE", global = true)]
pub eval_code: Option<String>,
/// Engine mode (`adaptive`, `wasm`, `native`). Default: adaptive.
#[arg(long, value_name = "MODE", global = true)]
pub mode: Option<String>,
/// Per-call fuel budget (backend-specific instruction count).
#[arg(long, value_name = "N", global = true)]
pub fuel: Option<u64>,
/// Per-call linear memory cap (bytes).
#[arg(long, value_name = "BYTES", global = true)]
pub memory: Option<usize>,
/// Per-call wall-clock cap (milliseconds).
#[arg(long = "timeout", value_name = "MS", global = true)]
pub timeout_ms: Option<u64>,
/// Grant outbound HTTP access. Values: `*` = any host;
/// `api.example.com,*.trusted.io` = comma-separated allow-list with
/// optional wildcard subdomains. Without this flag all HTTP is
/// denied (`PermissionDenied`).
#[arg(long = "allow-net", value_name = "HOSTS", global = true)]
pub allow_net: Option<String>,
/// Grant read+write filesystem access. Values: `*` = entire FS;
/// `/var/data,/tmp/workspace` = comma-separated root allow-list.
#[arg(long = "allow-fs", value_name = "PATHS", global = true)]
pub allow_fs: Option<String>,
/// Grant env-var read access. Values: `*` = all env; `HOME,PATH` =
/// comma-separated name allow-list.
#[arg(long = "allow-env", value_name = "VARS", global = true)]
pub allow_env: Option<String>,
/// Shortcut: grant all capabilities (net, fs, env). Use with care.
#[arg(long = "allow-all", short = 'A', global = true)]
pub allow_all: bool,
/// Seal the sandbox (empty capabilities) — flip the CLI's open-by-default.
/// Combine with `--allow-*` flags to hand-pick grants.
#[arg(long = "sandbox", global = true)]
pub sandbox: bool,
/// Suppress the first-run open-capabilities banner and other
/// non-essential stderr notices. `BURN_QUIET=1` in the environment
/// has the same effect.
#[arg(long = "quiet", short = 'q', global = true)]
pub quiet: bool,
/// **Internal — set only by `worker_threads`.** Marks this `burn`
/// invocation as a worker child: read the init frame off stdin,
/// expose `parentPort`, and pump frames over stdin/stdout per the
/// daemon_workers protocol. Hidden from `--help` to discourage
/// human use; running `burn --internal-worker foo.js` by hand
/// without an init frame on stdin will hang.
#[arg(long = "internal-worker", hide = true, global = true)]
pub internal_worker: bool,
/// **Internal — set only by `worker_threads`.** The monotonic
/// `threadId` the parent assigned to this worker. Defaults to 0
/// (which only the parent process sees) outside worker mode.
#[arg(
long = "worker-thread-id",
value_name = "ID",
hide = true,
global = true
)]
pub worker_thread_id: Option<i32>,
/// Positional arguments after the script path — passed through as
/// `process.argv[2..]`. Only meaningful for the top-level
/// `burn FILE arg1 arg2…` shape; each subcommand has its own
/// `rest_args` when it accepts trailing args.
#[arg(
trailing_var_arg = true,
allow_hyphen_values = true,
value_name = "ARGS"
)]
pub rest_args: Vec<String>,
}
#[derive(Subcommand, Debug, Clone)]
pub enum Cmd {
/// Execute a JavaScript file.
Run {
#[arg(value_name = "FILE")]
file: PathBuf,
/// Arguments passed through as `process.argv[2..]`.
#[arg(
trailing_var_arg = true,
allow_hyphen_values = true,
value_name = "ARGS"
)]
rest_args: Vec<String>,
},
/// Evaluate an inline JavaScript snippet.
Eval {
#[arg(value_name = "CODE")]
code: String,
/// Arguments passed through as `process.argv[2..]`.
#[arg(
trailing_var_arg = true,
allow_hyphen_values = true,
value_name = "ARGS"
)]
rest_args: Vec<String>,
},
/// UDF mode — reads JSON from stdin, feeds as `data` to the script,
/// writes the script's return value as JSON to stdout.
Thrust {
#[arg(value_name = "FILE")]
file: PathBuf,
},
/// Parse + compile a script without executing it. Exit code 0 on
/// success, 1 on syntax or semantic errors.
Check {
#[arg(value_name = "FILE")]
file: PathBuf,
},
/// Measure throughput + p50/p99 latency by running the script N
/// times. Reports to stderr; script output is suppressed.
Bench {
#[arg(value_name = "FILE")]
file: PathBuf,
/// Total iterations to submit.
#[arg(long, default_value_t = 10_000)]
iters: usize,
/// Workers for the threaded path. `1` uses the single-threaded
/// BurnCache. Higher values use ThrustEngine.
#[arg(long, default_value_t = 1)]
workers: usize,
},
/// Interactive REPL. Each line becomes a fresh script (no state
/// shared across lines — matches the fresh-per-call invariant).
Repl,
/// Print the build version + enabled features.
Version,
}
pub fn parse_mode(s: &str) -> Result<Mode> {
Ok(match s.to_ascii_lowercase().as_str() {
"native" => Mode::Native,
#[cfg(feature = "wasm")]
"wasm" => Mode::Wasm,
#[cfg(feature = "adaptive")]
"adaptive" => Mode::Adaptive,
other => anyhow::bail!("unknown --mode '{other}'; expected one of: native, wasm, adaptive"),
})
}