feat: eval --file, tab-list URL truncation, skill doc fixes (issue #2/#3 + Hermes)

- `eval --file <path>`: read JS from a file, sent verbatim — avoids shell-mangling
  of non-ASCII identifiers/strings (Chinese), quotes, and large scripts (issue #3).
- `tab list`: truncate multi-KB URLs (JWT/OTP login links) middle-out with a char
  count so the list stays readable (issue #3).
- skill: fix the snapshot example to match real output
  (`- role "name" [ref=eN]`, not `@e1 [role]`); document that eval runs in the
  page MAIN world with persistent state (top-level `const` collides — use IIFE /
  window / unique names) and to prefer --file/--stdin/-b for non-ASCII or big JS.
This commit is contained in:
leeguooooo
2026-06-11 22:22:07 +09:00
parent abb65c632b
commit 123510db2b
3 changed files with 56 additions and 15 deletions
+20 -6
View File
@@ -841,17 +841,31 @@ fn parse_command_inner(args: &[String], flags: &Flags) -> Result<Value, ParseErr
// === Eval ===
"eval" => {
// Check for flags: -b/--base64 or --stdin
let (is_base64, is_stdin, script_parts): (bool, bool, &[&str]) =
// Check for flags: -b/--base64, --stdin, or --file <path>
let (is_base64, is_stdin, is_file, script_parts): (bool, bool, bool, &[&str]) =
if rest.first() == Some(&"-b") || rest.first() == Some(&"--base64") {
(true, false, &rest[1..])
(true, false, false, &rest[1..])
} else if rest.first() == Some(&"--stdin") {
(false, true, &rest[1..])
(false, true, false, &rest[1..])
} else if rest.first() == Some(&"--file") {
(false, false, true, &rest[1..])
} else {
(false, false, rest.as_slice())
(false, false, false, rest.as_slice())
};
let script = if is_stdin {
let script = if is_file {
// Read the script from a file. Avoids shell-mangling of inline JS
// (non-ASCII identifiers/strings, quotes, large scripts) — the file
// is read as UTF-8 and sent verbatim.
let path = script_parts.first().ok_or(ParseError::InvalidValue {
message: "eval --file requires a path".to_string(),
usage: "eval --file <path>",
})?;
std::fs::read_to_string(path).map_err(|e| ParseError::InvalidValue {
message: format!("eval --file: cannot read {path}: {e}"),
usage: "eval --file <path>",
})?
} else if is_stdin {
// Read script from stdin
let stdin = io::stdin();
let lines: Vec<String> = stdin
+17
View File
@@ -130,6 +130,20 @@ fn format_stream_status_text(action: Option<&str>, data: &serde_json::Value) ->
}
}
/// Shorten an over-long string by keeping its head and tail and eliding the
/// middle, with a char count. Used so multi-KB URLs (JWT/OTP login links) don't
/// flood `tab list`.
fn truncate_middle(s: &str, max: usize) -> String {
let n = s.chars().count();
if n <= max {
return s.to_string();
}
let keep = max.saturating_sub(1) / 2;
let head: String = s.chars().take(keep).collect();
let tail: String = s.chars().skip(n - keep).collect();
format!("{head}{tail} [{n} chars]")
}
pub fn print_response_with_opts(resp: &Response, action: Option<&str>, opts: &OutputOptions) {
if opts.json {
if opts.content_boundaries {
@@ -422,6 +436,9 @@ pub fn print_response_with_opts(resp: &Response, action: Option<&str>, opts: &Ou
.and_then(|v| v.as_str())
.unwrap_or("Untitled");
let url = tab.get("url").and_then(|v| v.as_str()).unwrap_or("");
// Truncate very long URLs (e.g. multi-KB JWT/OTP login links) so
// the list stays readable instead of flooding the terminal.
let url = truncate_middle(url, 120);
let active = tab.get("active").and_then(|v| v.as_bool()).unwrap_or(false);
let marker = if active {
color::cyan("")