mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-07-30 17:25:58 -04:00
cli: add run command for script replay
`lightpanda agent <script>` replays a saved script token-free (no LLM), but running it under `agent` reads wrong. Add `run` as a dedicated, script-only command: `lightpanda run <script>`. `run` exposes only a required [SCRIPT] positional plus the common options; its LLM-session flags (--task/--save/--list-models/REPL) are absent, so they're rejected at parse time. `parseArgs` normalizes a `run` invocation to an `.agent` command, reusing the existing token-free replay path unchanged, so nothing downstream needs a `.run` case. `agent <script>` keeps working. Telemetry records `run` as its own mode code (R) via a new Config.command field, so its adoption is measurable separately from agent script replay (AR).
This commit is contained in:
@@ -78,7 +78,7 @@ pub fn init(allocator: Allocator, config: *const Config) !*App {
|
||||
|
||||
app.app_dir_path = getAndMakeAppDir(allocator);
|
||||
|
||||
app.telemetry = try Telemetry.init(app, config.mode, config.interactive());
|
||||
app.telemetry = try Telemetry.init(app, config.command, config.interactive());
|
||||
errdefer app.telemetry.deinit(allocator);
|
||||
|
||||
app.arena_pool = ArenaPool.init(allocator, .{});
|
||||
|
||||
@@ -263,6 +263,13 @@ const Commands = cli.Builder(.{
|
||||
},
|
||||
.shared_options = CommonOptions,
|
||||
},
|
||||
.{
|
||||
// Normalized to `.agent` in `parseArgs`; intentionally no LLM options.
|
||||
.name = "run",
|
||||
.positional = .{ .name = "script_file", .type = ?[:0]const u8 },
|
||||
.options = .{},
|
||||
.shared_options = CommonOptions,
|
||||
},
|
||||
.{ .name = "version", .options = .{
|
||||
.{ .name = "check", .type = bool },
|
||||
} },
|
||||
@@ -273,6 +280,9 @@ pub const Mode = Commands.Union;
|
||||
pub const Agent = @FieldType(Mode, "agent");
|
||||
|
||||
mode: Mode,
|
||||
// The command as typed. Mirrors `mode`, except `run` normalizes to `.agent`
|
||||
// for execution while this keeps `.run` for telemetry.
|
||||
command: RunMode,
|
||||
exec_name: []const u8,
|
||||
http_headers: HttpHeaders,
|
||||
|
||||
@@ -287,6 +297,7 @@ fn modeNeedsHttp(mode: Mode) bool {
|
||||
pub fn init(allocator: Allocator, exec_name: []const u8, mode: Mode) !Config {
|
||||
var config = Config{
|
||||
.mode = mode,
|
||||
.command = std.meta.activeTag(mode),
|
||||
.exec_name = exec_name,
|
||||
.http_headers = undefined,
|
||||
};
|
||||
@@ -713,7 +724,7 @@ pub fn printUsageAndExit(self: *const Config, help_for: RunMode, success: bool)
|
||||
, .{Help.general});
|
||||
std.debug.print(template, .{exec_name});
|
||||
},
|
||||
inline .fetch, .serve, .mcp, .agent => |tag| {
|
||||
inline .fetch, .serve, .mcp, .agent, .run => |tag| {
|
||||
const template = comptimePrint(
|
||||
\\{s}
|
||||
\\
|
||||
@@ -735,11 +746,28 @@ pub fn printUsageAndExit(self: *const Config, help_for: RunMode, success: bool)
|
||||
}
|
||||
|
||||
pub fn parseArgs(allocator: Allocator) !Config {
|
||||
const exec_name, const command = try Commands.parse(allocator);
|
||||
const exec_name, var command = try Commands.parse(allocator);
|
||||
if (command == .serve and command.serve.timeout != null) {
|
||||
log.warn(.app, "--timeout is deprecated", .{});
|
||||
}
|
||||
return .init(allocator, exec_name, command);
|
||||
const invoked = std.meta.activeTag(command);
|
||||
// Rewrite `run` to `.agent` so nothing downstream needs a `.run` case.
|
||||
if (command == .run) {
|
||||
const run = command.run;
|
||||
if (run.script_file == null) {
|
||||
log.fatal(.app, "missing script file", .{ .hint = "usage: lightpanda run <script.js>" });
|
||||
return error.MissingArgument;
|
||||
}
|
||||
// run's fields are a strict subset of Agent's (compile error otherwise).
|
||||
var agent_opts: Agent = .{};
|
||||
inline for (@typeInfo(@TypeOf(run)).@"struct".fields) |f| {
|
||||
@field(agent_opts, f.name) = @field(run, f.name);
|
||||
}
|
||||
command = .{ .agent = agent_opts };
|
||||
}
|
||||
var config = try Config.init(allocator, exec_name, command);
|
||||
config.command = invoked;
|
||||
return config;
|
||||
}
|
||||
|
||||
pub fn validateUserAgent(ua: []const u8) !void {
|
||||
|
||||
29
src/help.zon
29
src/help.zon
@@ -8,6 +8,7 @@
|
||||
\\ fetch fetches the specified URL
|
||||
\\ help displays this message
|
||||
\\ mcp starts an MCP (Model Context Protocol) server over stdio
|
||||
\\ run runs a saved script (no LLM), then exits
|
||||
\\ serve starts a WebSocket CDP server
|
||||
\\ version displays the version of {0s}
|
||||
\\
|
||||
@@ -143,14 +144,15 @@
|
||||
\\ {0s} agent --provider huggingface (HF serverless router, HF_TOKEN)
|
||||
\\ {0s} agent --provider ollama --model qwen3.5:latest
|
||||
\\ {0s} agent --no-llm (basic slash-command-only REPL)
|
||||
\\ {0s} agent script.js (run a saved script, then exit)
|
||||
\\ {0s} run script.js (replay a saved script; see `run`)
|
||||
\\ {0s} agent --task "..." --save out.js (synthesize a replayable script)
|
||||
\\
|
||||
\\Arguments:
|
||||
\\[SCRIPT]
|
||||
\\ Optional path to a .js script. Runs the script (no LLM calls) and
|
||||
\\ exits. With no script and no --task, the REPL starts; from there
|
||||
\\ /load runs a script and /save exports the session to a file.
|
||||
\\ exits; `{0s} run SCRIPT` is the preferred spelling. With no script
|
||||
\\ and no --task, the REPL starts; from there /load runs a script and
|
||||
\\ /save exports the session to a file.
|
||||
\\ Caution: .js files can contain evaluate(...) calls that run
|
||||
\\ arbitrary JavaScript in the page. Only run scripts you trust, the
|
||||
\\ same way you would a shell script.
|
||||
@@ -209,7 +211,7 @@
|
||||
\\ --save <PATH>
|
||||
\\ Synthesize a replayable .js script from the --task run and write
|
||||
\\ it to PATH, instead of printing the answer. Replay it later with
|
||||
\\ `agent PATH` (no LLM calls). Overwrites PATH if it exists.
|
||||
\\ `run PATH` (no LLM calls). Overwrites PATH if it exists.
|
||||
\\ Requires --task.
|
||||
\\ --system-prompt <STRING>
|
||||
\\ Override the default system prompt.
|
||||
@@ -237,6 +239,25 @@
|
||||
\\MISTRAL_API_KEY. The local servers (Ollama, llama.cpp) do not require an
|
||||
\\API key.
|
||||
,
|
||||
.run =
|
||||
\\run command
|
||||
\\Runs a saved script, then exits. No LLM calls, no API key needed.
|
||||
\\
|
||||
\\Usage:
|
||||
\\ {0s} run <SCRIPT> [COMMON_OPTIONS]
|
||||
\\
|
||||
\\Examples:
|
||||
\\ {0s} run script.js (replay a saved script)
|
||||
\\
|
||||
\\Arguments:
|
||||
\\<SCRIPT>
|
||||
\\ Path to a .js script to run, then exit. Produce one with
|
||||
\\ `{0s} agent --task "..." --save script.js`, or `/save` from the
|
||||
\\ agent REPL.
|
||||
\\ Caution: .js files can contain evaluate(...) calls that run
|
||||
\\ arbitrary JavaScript in the page. Only run scripts you trust, the
|
||||
\\ same way you would a shell script.
|
||||
,
|
||||
.version =
|
||||
\\usage: {0s} version [OPTIONS]
|
||||
\\
|
||||
|
||||
@@ -82,6 +82,7 @@ pub fn init(self: *LightPanda, app: *App, iid: ?[36]u8, run_mode: Config.RunMode
|
||||
.fetch => "F",
|
||||
.serve => "S",
|
||||
.agent => if (interactive == false) "AR" else "A",
|
||||
.run => "R",
|
||||
.mcp => "M",
|
||||
.version => "V",
|
||||
.help => "H",
|
||||
|
||||
Reference in New Issue
Block a user