Skip to main content

chub_cli/commands/
pin.rs

1use clap::{Args, Subcommand};
2use owo_colors::OwoColorize;
3
4use chub_core::team::pins;
5
6use crate::output;
7
8#[derive(Args)]
9pub struct PinArgs {
10    #[command(subcommand)]
11    command: PinCommand,
12}
13
14#[derive(Subcommand)]
15pub enum PinCommand {
16    /// Pin a doc to the project
17    Add(PinAddArgs),
18    /// Remove a pinned doc
19    Remove(PinRemoveArgs),
20    /// List all pinned docs
21    List,
22    /// Fetch all pinned docs at once
23    Get(PinGetArgs),
24}
25
26#[derive(Args)]
27pub struct PinAddArgs {
28    /// Entry ID (e.g. "openai/chat")
29    id: String,
30    /// Language variant
31    #[arg(long)]
32    lang: Option<String>,
33    /// Specific version to lock
34    #[arg(long)]
35    version: Option<String>,
36    /// Reason for pinning
37    #[arg(long)]
38    reason: Option<String>,
39    /// Source name for private registry
40    #[arg(long)]
41    source: Option<String>,
42}
43
44#[derive(Args)]
45pub struct PinRemoveArgs {
46    /// Entry ID to unpin
47    id: String,
48}
49
50#[derive(Args)]
51pub struct PinGetArgs {
52    /// Language variant (overrides pin lang)
53    #[arg(long)]
54    lang: Option<String>,
55}
56
57pub fn run(args: PinArgs, json: bool) {
58    match args.command {
59        PinCommand::Add(add_args) => run_add(add_args, json),
60        PinCommand::Remove(rm_args) => run_remove(rm_args, json),
61        PinCommand::List => run_list(json),
62        PinCommand::Get(_) => {
63            if json {
64                println!(
65                    "{}",
66                    serde_json::json!({"info": "Use `chub get --pinned` to fetch all pinned docs."})
67                );
68            } else {
69                eprintln!(
70                    "Use {} to fetch all pinned docs.",
71                    "chub get --pinned".bold()
72                );
73            }
74        }
75    }
76}
77
78fn run_add(args: PinAddArgs, json: bool) {
79    match pins::add_pin(
80        &args.id,
81        args.lang.clone(),
82        args.version.clone(),
83        args.reason.clone(),
84        args.source.clone(),
85    ) {
86        Ok(()) => {
87            if json {
88                println!(
89                    "{}",
90                    serde_json::json!({
91                        "status": "pinned",
92                        "id": args.id,
93                        "lang": args.lang,
94                        "version": args.version,
95                    })
96                );
97            } else {
98                let mut msg = format!("Pinned {}", args.id.bold());
99                if let Some(ref lang) = args.lang {
100                    msg.push_str(&format!(" ({})", lang));
101                }
102                if let Some(ref ver) = args.version {
103                    msg.push_str(&format!(" v{}", ver));
104                }
105                output::success(&msg);
106            }
107        }
108        Err(e) => {
109            output::error(&e.to_string(), json);
110            std::process::exit(1);
111        }
112    }
113}
114
115fn run_remove(args: PinRemoveArgs, json: bool) {
116    match pins::remove_pin(&args.id) {
117        Ok(true) => {
118            if json {
119                println!(
120                    "{}",
121                    serde_json::json!({"status": "unpinned", "id": args.id})
122                );
123            } else {
124                output::success(&format!("Unpinned {}", args.id.bold()));
125            }
126        }
127        Ok(false) => {
128            if json {
129                println!(
130                    "{}",
131                    serde_json::json!({"status": "not_found", "id": args.id})
132                );
133            } else {
134                eprintln!("No pin found for {}.", args.id.bold());
135            }
136        }
137        Err(e) => {
138            output::error(&e.to_string(), json);
139            std::process::exit(1);
140        }
141    }
142}
143
144fn run_list(json: bool) {
145    let pins = pins::list_pins();
146
147    if json {
148        println!(
149            "{}",
150            serde_json::to_string_pretty(&serde_json::json!({
151                "pins": pins,
152                "total": pins.len(),
153            }))
154            .unwrap_or_default()
155        );
156    } else {
157        if pins.is_empty() {
158            eprintln!(
159                "{}",
160                "No pins configured. Use `chub pin add <id>` to pin a doc.".dimmed()
161            );
162            return;
163        }
164        eprintln!("{}", format!("{} pinned docs:\n", pins.len()).bold());
165        for pin in &pins {
166            let mut line = format!("  {}", pin.id.bold());
167            if let Some(ref lang) = pin.lang {
168                line.push_str(&format!("  {}", lang.cyan()));
169            }
170            if let Some(ref ver) = pin.version {
171                line.push_str(&format!("  v{}", ver));
172            }
173            if let Some(ref src) = pin.source {
174                line.push_str(&format!("  [{}]", src.dimmed()));
175            }
176            eprintln!("{}", line);
177            if let Some(ref reason) = pin.reason {
178                eprintln!("       {}", reason.dimmed());
179            }
180        }
181    }
182}