mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-07-31 01:36:15 -04:00
agent: allow paths in --save option
This commit is contained in:
@@ -294,12 +294,12 @@ pub fn init(allocator: std.mem.Allocator, app: *App, opts: Config.Agent) !*Agent
|
||||
});
|
||||
return error.ConflictingFlags;
|
||||
}
|
||||
save.validateFilename(save_path) catch {
|
||||
if (save_path.len == 0) {
|
||||
log.fatal(.app, "invalid --save filename", .{
|
||||
.hint = "--save takes a local file name, not a path (e.g. script.js)",
|
||||
.hint = "--save needs a non-empty file name",
|
||||
});
|
||||
return error.InvalidFilename;
|
||||
};
|
||||
}
|
||||
}
|
||||
if (opts.no_llm and opts.provider != null) {
|
||||
log.warn(.app, "ignoring --provider", .{ .reason = "--no-llm takes precedence" });
|
||||
@@ -1013,7 +1013,6 @@ fn handleSave(self: *Agent, arena: std.mem.Allocator, rest: []const u8) void {
|
||||
const msg: []const u8 = switch (err) {
|
||||
error.UnterminatedQuote => "unterminated filename quote",
|
||||
error.EmptyFilename => "filename cannot be empty",
|
||||
error.InvalidFilename => "filename must be a local file name, not a path",
|
||||
error.OutOfMemory => "out of memory",
|
||||
};
|
||||
self.terminal.printError("{s}", .{msg});
|
||||
|
||||
@@ -52,7 +52,7 @@ pub fn parseCommand(arena: std.mem.Allocator, rest: []const u8) !Command {
|
||||
name = trimmed[0..tok_end];
|
||||
after = trimmed[tok_end..];
|
||||
}
|
||||
try validateFilename(name);
|
||||
if (name.len == 0) return error.EmptyFilename;
|
||||
if (!std.mem.endsWith(u8, name, ".js")) {
|
||||
name = try std.mem.concat(arena, u8, &.{ name, ".js" });
|
||||
}
|
||||
@@ -60,14 +60,6 @@ pub fn parseCommand(arena: std.mem.Allocator, rest: []const u8) !Command {
|
||||
return .{ .filename = name, .prompt = if (prompt.len == 0) null else prompt };
|
||||
}
|
||||
|
||||
pub fn validateFilename(name: []const u8) !void {
|
||||
if (name.len == 0) return error.EmptyFilename;
|
||||
if (std.fs.path.isAbsolute(name)) return error.InvalidFilename;
|
||||
if (std.mem.indexOfScalar(u8, name, '/') != null) return error.InvalidFilename;
|
||||
if (std.mem.indexOfScalar(u8, name, '\\') != null) return error.InvalidFilename;
|
||||
if (std.mem.eql(u8, name, ".") or std.mem.eql(u8, name, "..")) return error.InvalidFilename;
|
||||
}
|
||||
|
||||
pub fn randomFilename(arena: std.mem.Allocator) ![]const u8 {
|
||||
for (0..100) |_| {
|
||||
const n = std.crypto.random.int(u64);
|
||||
@@ -175,12 +167,13 @@ test "parseCommand: empty is all null" {
|
||||
try std.testing.expect(r.prompt == null);
|
||||
}
|
||||
|
||||
test "parseCommand: rejects path-like filenames" {
|
||||
test "parseCommand: accepts path-like filenames" {
|
||||
var arena: std.heap.ArenaAllocator = .init(std.testing.allocator);
|
||||
defer arena.deinit();
|
||||
const aa = arena.allocator();
|
||||
try std.testing.expectError(error.InvalidFilename, parseCommand(aa, "../evil.js"));
|
||||
try std.testing.expectError(error.InvalidFilename, parseCommand(aa, "/tmp/x.js"));
|
||||
try std.testing.expectEqualStrings("../evil.js", (try parseCommand(aa, "../evil.js")).filename.?);
|
||||
try std.testing.expectEqualStrings("/tmp/x.js", (try parseCommand(aa, "/tmp/x.js")).filename.?);
|
||||
try std.testing.expectEqualStrings("out/run.js", (try parseCommand(aa, "out/run")).filename.?);
|
||||
try std.testing.expectError(error.UnterminatedQuote, parseCommand(aa, "\"unclosed.js"));
|
||||
}
|
||||
|
||||
|
||||
@@ -208,8 +208,8 @@
|
||||
\\--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. PATH must be a
|
||||
\\ local file name, not a path. Requires --task.
|
||||
\\ calls). Overwrites PATH if it exists. Requires
|
||||
\\ --task.
|
||||
\\
|
||||
\\-a, --attach <PATH> Feed a local file to the model alongside --task.
|
||||
\\ Repeatable, one file per flag. Text files are
|
||||
|
||||
Reference in New Issue
Block a user