feat(cookies): cross-profile cookies export / cookies transfer
Transferring a logged-in session between Chrome profiles previously needed an ad-hoc external script to decrypt the source profile's cookie store. Make it first-class: - `cookies export --from <profile> [--domain <d>[,<d>]]` decrypts another profile's on-disk cookies and prints CDP-shaped JSON for `cookies set --curl`. - `cookies transfer --from <profile> [--domain <d>]` exports + injects into the connected browser in one shot (reuses the cookies_set path). Source profile is resolved by directory name, display name, or "auto". The store is copied to a temp file (immune to a running Chrome's lock/WAL), read via sqlite3, and values are decrypted (macOS v10: AES-128-CBC, key from the shared 'Chrome Safe Storage' Keychain entry). httpOnly/secure/per-domain auth cookies round-trip intact; SameSite=None without Secure is downgraded so CDP accepts it. macOS only for now (clear error elsewhere).
This commit is contained in:
@@ -1289,6 +1289,51 @@ fn parse_command_inner(args: &[String], flags: &Flags) -> Result<Value, ParseErr
|
||||
"cookies" => {
|
||||
let op = rest.first().unwrap_or(&"get");
|
||||
match *op {
|
||||
"transfer" => {
|
||||
// Copy a logged-in session between Chrome profiles: decrypt
|
||||
// the SOURCE profile's on-disk cookie store and inject the
|
||||
// cookies into the active (connected) session — no CDP access
|
||||
// to the source, no Chrome restart.
|
||||
// cookies transfer --from <profile> [--domain <d>[,<d>]]
|
||||
// `--from` wins; otherwise the global `--profile` is used.
|
||||
let from = rest
|
||||
.iter()
|
||||
.position(|a| *a == "--from")
|
||||
.and_then(|i| rest.get(i + 1).copied())
|
||||
.or(flags.profile.as_deref());
|
||||
let from = from.ok_or_else(|| ParseError::MissingArguments {
|
||||
context: "cookies transfer".to_string(),
|
||||
usage: "cookies transfer --from <profile> [--domain <domain>[,<domain>]]",
|
||||
})?;
|
||||
let domain = rest
|
||||
.iter()
|
||||
.position(|a| *a == "--domain")
|
||||
.and_then(|i| rest.get(i + 1).copied());
|
||||
let cookies =
|
||||
crate::cookie_export::export_cookies(from, domain).map_err(|e| {
|
||||
ParseError::InvalidValue {
|
||||
message: format!("cookies transfer: {}", e),
|
||||
usage: "cookies transfer --from <profile> [--domain <domain>]",
|
||||
}
|
||||
})?;
|
||||
if cookies.is_empty() {
|
||||
return Err(ParseError::InvalidValue {
|
||||
message: format!(
|
||||
"cookies transfer: no cookies found in profile \"{}\"{}",
|
||||
from,
|
||||
domain
|
||||
.map(|d| format!(" for domain {}", d))
|
||||
.unwrap_or_default()
|
||||
),
|
||||
usage: "cookies transfer --from <profile> [--domain <domain>]",
|
||||
});
|
||||
}
|
||||
return Ok(json!({
|
||||
"id": id,
|
||||
"action": "cookies_set",
|
||||
"cookies": cookies,
|
||||
}));
|
||||
}
|
||||
"set" => {
|
||||
// --curl <file> mode: import cookies from a JSON array,
|
||||
// raw cURL dump, or bare Cookie header. Scoped to the
|
||||
|
||||
Reference in New Issue
Block a user