agent_first_http/cli/cmd/
profile.rs1use std::path::PathBuf;
4
5use clap::{Args as ClapArgs, Subcommand};
6
7use crate::cli::output;
8use crate::sdk::profile;
9use crate::shared::error::Error;
10use crate::shared::time::parse_duration;
11
12#[derive(ClapArgs, Debug)]
13pub struct Args {
14 #[command(subcommand)]
15 pub sub: ProfileSub,
16}
17
18#[derive(Subcommand, Debug)]
19#[command(disable_help_subcommand = true)]
20pub enum ProfileSub {
21 List(ListArgs),
23 Info(InfoArgs),
25 LockStatus(InfoArgs),
27 Downloads(InfoArgs),
29 Delete(DeleteArgs),
31 Prune(PruneArgs),
33 Cookies(InfoArgs),
35}
36
37#[derive(ClapArgs, Debug)]
38pub struct ListArgs {
39 #[arg(long)]
41 pub profile_root: Option<PathBuf>,
42}
43
44#[derive(ClapArgs, Debug)]
45pub struct InfoArgs {
46 pub name: String,
48 #[arg(long)]
53 pub backend: Option<String>,
54 #[arg(long)]
56 pub profile_root: Option<PathBuf>,
57}
58
59#[derive(ClapArgs, Debug)]
60pub struct DeleteArgs {
61 pub name: String,
63 #[arg(long)]
68 pub backend: Option<String>,
69 #[arg(long)]
71 pub confirm: String,
72 #[arg(long)]
74 pub profile_root: Option<PathBuf>,
75}
76
77#[derive(ClapArgs, Debug)]
78pub struct PruneArgs {
79 #[arg(long)]
81 pub older_than: String,
82 #[arg(long, default_value_t = false)]
84 pub dry_run: bool,
85 #[arg(long)]
87 pub profile_root: Option<PathBuf>,
88}
89
90pub async fn run(args: Args) -> Result<(), Error> {
91 match args.sub {
92 ProfileSub::List(a) => {
93 let root = profile_root_for_output(a.profile_root.as_deref());
94 let entries = profile::list(a.profile_root.as_deref())?;
95 output::emit(
96 "profile_list",
97 &serde_json::json!({
98 "profile_root": root.display().to_string(),
99 "profiles": entries,
100 }),
101 )
102 }
103 ProfileSub::Info(a) => {
104 let entry = profile::info(&a.name, a.backend.as_deref(), a.profile_root.as_deref())?;
105 output::emit("profile_info", &entry)
106 }
107 ProfileSub::LockStatus(a) => {
108 let status =
109 profile::lock_status(&a.name, a.backend.as_deref(), a.profile_root.as_deref())?;
110 output::emit("profile_lock_status", &status)
111 }
112 ProfileSub::Downloads(a) => {
113 let entry = profile::info(&a.name, a.backend.as_deref(), a.profile_root.as_deref())?;
114 let download_dir = entry.path.join("downloads");
115 let download_dir = download_dir.canonicalize().unwrap_or(download_dir);
116 let downloads =
117 profile::downloads(&a.name, a.backend.as_deref(), a.profile_root.as_deref())?;
118 output::emit(
119 "profile_downloads",
120 &serde_json::json!({
121 "backend": entry.backend,
122 "name": a.name,
123 "download_dir": download_dir.display().to_string(),
124 "downloads": downloads,
125 }),
126 )
127 }
128 ProfileSub::Delete(a) => {
129 let entry = profile::info(&a.name, a.backend.as_deref(), a.profile_root.as_deref())?;
130 profile::delete(
131 &a.name,
132 &a.confirm,
133 a.backend.as_deref(),
134 a.profile_root.as_deref(),
135 )?;
136 output::emit(
137 "profile_delete",
138 &serde_json::json!({"backend": entry.backend, "name": a.name, "deleted": true}),
139 )
140 }
141 ProfileSub::Prune(a) => {
142 let root = profile_root_for_output(a.profile_root.as_deref());
143 let older_than = parse_duration(&a.older_than)?;
144 let removed = profile::prune(older_than, a.dry_run, a.profile_root.as_deref())?;
145 output::emit(
146 "profile_prune",
147 &serde_json::json!({
148 "profile_root": root.display().to_string(),
149 "dry_run": a.dry_run,
150 "profiles": removed,
151 }),
152 )
153 }
154 ProfileSub::Cookies(a) => {
155 let entry = profile::info(&a.name, a.backend.as_deref(), a.profile_root.as_deref())?;
156 let jar_path = entry.path.join("cookies.jar.json");
157 let jar = crate::sdk::profile::cookie_jar::CookieJar::load(&jar_path)?;
158 let cookies = jar.cookies_redacted();
159 output::emit(
160 "profile_cookies",
161 &serde_json::json!({
162 "backend": entry.backend,
163 "name": a.name,
164 "jar_path": jar_path.display().to_string(),
165 "count": cookies.len(),
166 "cookies": cookies,
167 }),
168 )
169 }
170 }
171}
172
173fn profile_root_for_output(root: Option<&std::path::Path>) -> PathBuf {
174 root.map(PathBuf::from)
175 .unwrap_or_else(profile::paths::default_root)
176}