Skip to main content

chub_cli/commands/
bundle.rs

1use std::path::PathBuf;
2
3use clap::{Args, Subcommand};
4use owo_colors::OwoColorize;
5
6use chub_core::team::bundles;
7
8use crate::output;
9
10#[derive(Args)]
11pub struct BundleArgs {
12    #[command(subcommand)]
13    command: BundleCommand,
14}
15
16#[derive(Subcommand)]
17pub enum BundleCommand {
18    /// Create a new bundle
19    Create(BundleCreateArgs),
20    /// Install a bundle (pin all its entries)
21    Install(BundleInstallArgs),
22    /// List available bundles
23    List,
24}
25
26#[derive(Args)]
27pub struct BundleCreateArgs {
28    /// Bundle name
29    name: String,
30    /// Description
31    #[arg(long)]
32    description: Option<String>,
33    /// Author name
34    #[arg(long)]
35    author: Option<String>,
36    /// Entry IDs (comma-separated)
37    #[arg(long)]
38    entries: String,
39    /// Notes
40    #[arg(long)]
41    notes: Option<String>,
42}
43
44#[derive(Args)]
45pub struct BundleInstallArgs {
46    /// Bundle name or path to bundle YAML
47    name: String,
48}
49
50pub fn run(args: BundleArgs, json: bool) {
51    match args.command {
52        BundleCommand::Create(create_args) => run_create(create_args, json),
53        BundleCommand::Install(install_args) => run_install(install_args, json),
54        BundleCommand::List => run_list(json),
55    }
56}
57
58fn run_create(args: BundleCreateArgs, json: bool) {
59    let entries: Vec<String> = args
60        .entries
61        .split(',')
62        .map(|s| s.trim().to_string())
63        .filter(|s| !s.is_empty())
64        .collect();
65
66    match bundles::create_bundle(
67        &args.name,
68        args.description.as_deref(),
69        args.author.as_deref(),
70        entries,
71        args.notes.as_deref(),
72    ) {
73        Ok(path) => {
74            if json {
75                println!(
76                    "{}",
77                    serde_json::json!({
78                        "status": "created",
79                        "path": path.display().to_string(),
80                    })
81                );
82            } else {
83                output::success(&format!("Bundle created: {}", path.display()));
84            }
85        }
86        Err(e) => {
87            output::error(&e.to_string(), json);
88            std::process::exit(1);
89        }
90    }
91}
92
93fn run_install(args: BundleInstallArgs, json: bool) {
94    // Try as path first, then as name
95    let bundle = if PathBuf::from(&args.name).exists() {
96        bundles::load_bundle(&PathBuf::from(&args.name))
97    } else {
98        bundles::load_bundle_by_name(&args.name)
99    };
100
101    match bundle {
102        Ok(b) => {
103            let name = b.name.clone();
104            match bundles::install_bundle(&b) {
105                Ok(pinned) => {
106                    if json {
107                        println!(
108                            "{}",
109                            serde_json::json!({
110                                "status": "installed",
111                                "bundle": name,
112                                "pinned": pinned,
113                            })
114                        );
115                    } else {
116                        output::success(&format!(
117                            "Installed bundle \"{}\": pinned {} entries.",
118                            name.bold(),
119                            pinned.len()
120                        ));
121                        for id in &pinned {
122                            eprintln!("  + {}", id);
123                        }
124                    }
125                }
126                Err(e) => {
127                    output::error(&e.to_string(), json);
128                    std::process::exit(1);
129                }
130            }
131        }
132        Err(e) => {
133            output::error(&e.to_string(), json);
134            std::process::exit(1);
135        }
136    }
137}
138
139fn run_list(json: bool) {
140    let bundles_list = bundles::list_bundles();
141
142    if json {
143        println!(
144            "{}",
145            serde_json::to_string_pretty(&serde_json::json!({
146                "bundles": bundles_list,
147                "total": bundles_list.len(),
148            }))
149            .unwrap_or_default()
150        );
151    } else {
152        if bundles_list.is_empty() {
153            eprintln!("{}", "No bundles found in .chub/bundles/".dimmed());
154            return;
155        }
156        eprintln!("{}", format!("{} bundles:\n", bundles_list.len()).bold());
157        for b in &bundles_list {
158            eprintln!("  {}", b.name.bold());
159            if let Some(ref desc) = b.description {
160                eprintln!("    {}", desc.dimmed());
161            }
162            eprintln!("    Entries: {}", b.entries.join(", "));
163        }
164    }
165}