Skip to main content

agent_first_http/cli/cmd/
profile.rs

1//! `afhttp profile` subcommand. Local profile lifecycle.
2
3use std::path::PathBuf;
4
5use crate::cli::output;
6use crate::sdk::profile;
7use crate::shared::error::Error;
8use crate::shared::time::parse_duration;
9
10#[derive(Debug)]
11pub struct Args {
12    pub sub: ProfileSub,
13}
14
15#[derive(Debug)]
16pub enum ProfileSub {
17    List(ListArgs),
18    Info(InfoArgs),
19    LockStatus(InfoArgs),
20    Downloads(InfoArgs),
21    Delete(DeleteArgs),
22    Prune(PruneArgs),
23    Cookies(InfoArgs),
24}
25
26#[derive(Debug)]
27pub struct ListArgs {
28    pub profile_root: Option<PathBuf>,
29}
30
31#[derive(Debug)]
32pub struct InfoArgs {
33    pub name: String,
34    pub backend: Option<String>,
35    pub profile_root: Option<PathBuf>,
36}
37
38#[derive(Debug)]
39pub struct DeleteArgs {
40    pub name: String,
41    pub backend: Option<String>,
42    pub confirm: String,
43    pub profile_root: Option<PathBuf>,
44}
45
46#[derive(Debug)]
47pub struct PruneArgs {
48    pub older_than: String,
49    pub dry_run: bool,
50    pub profile_root: Option<PathBuf>,
51}
52
53pub async fn run(args: Args) -> Result<(), Error> {
54    match args.sub {
55        ProfileSub::List(a) => {
56            let root = profile_root_for_output(a.profile_root.as_deref());
57            let entries = profile::list(a.profile_root.as_deref())?;
58            output::emit(
59                "profile_list",
60                &serde_json::json!({
61                    "profile_root": root.display().to_string(),
62                    "profiles": entries,
63                }),
64            )
65        }
66        ProfileSub::Info(a) => {
67            let entry = profile::info(&a.name, a.backend.as_deref(), a.profile_root.as_deref())?;
68            output::emit("profile_info", &entry)
69        }
70        ProfileSub::LockStatus(a) => {
71            let status =
72                profile::lock_status(&a.name, a.backend.as_deref(), a.profile_root.as_deref())?;
73            output::emit("profile_lock_status", &status)
74        }
75        ProfileSub::Downloads(a) => {
76            let entry = profile::info(&a.name, a.backend.as_deref(), a.profile_root.as_deref())?;
77            let download_dir = entry.path.join("downloads");
78            let download_dir = download_dir.canonicalize().unwrap_or(download_dir);
79            let downloads =
80                profile::downloads(&a.name, a.backend.as_deref(), a.profile_root.as_deref())?;
81            output::emit(
82                "profile_downloads",
83                &serde_json::json!({
84                    "backend": entry.backend,
85                    "name": a.name,
86                    "download_dir": download_dir.display().to_string(),
87                    "downloads": downloads,
88                }),
89            )
90        }
91        ProfileSub::Delete(a) => {
92            let entry = profile::info(&a.name, a.backend.as_deref(), a.profile_root.as_deref())?;
93            profile::delete(
94                &a.name,
95                &a.confirm,
96                a.backend.as_deref(),
97                a.profile_root.as_deref(),
98            )?;
99            output::emit(
100                "profile_delete",
101                &serde_json::json!({"backend": entry.backend, "name": a.name, "deleted": true}),
102            )
103        }
104        ProfileSub::Prune(a) => {
105            let root = profile_root_for_output(a.profile_root.as_deref());
106            let older_than = parse_duration(&a.older_than)?;
107            let removed = profile::prune(older_than, a.dry_run, a.profile_root.as_deref())?;
108            output::emit(
109                "profile_prune",
110                &serde_json::json!({
111                    "profile_root": root.display().to_string(),
112                    "dry_run": a.dry_run,
113                    "profiles": removed,
114                }),
115            )
116        }
117        ProfileSub::Cookies(a) => {
118            let entry = profile::info(&a.name, a.backend.as_deref(), a.profile_root.as_deref())?;
119            let jar_path = entry.path.join("cookies.jar.json");
120            let jar = crate::sdk::profile::cookie_jar::CookieJar::load(&jar_path)?;
121            let cookies = jar.cookies_redacted();
122            output::emit(
123                "profile_cookies",
124                &serde_json::json!({
125                    "backend": entry.backend,
126                    "name": a.name,
127                    "jar_path": jar_path.display().to_string(),
128                    "count": cookies.len(),
129                    "cookies": cookies,
130                }),
131            )
132        }
133    }
134}
135
136fn profile_root_for_output(root: Option<&std::path::Path>) -> PathBuf {
137    root.map(PathBuf::from)
138        .unwrap_or_else(profile::paths::default_root)
139}