1pub mod commands;
2pub mod debug_agent;
3pub mod discord;
4pub mod docs;
5pub mod echo;
6pub mod gcal;
7pub mod help_agent;
8pub mod lifecycle;
9pub mod man;
10pub mod onepass;
11pub mod permissions;
12pub mod service;
13pub mod service_discord;
14pub mod service_gcal;
15pub mod service_list;
16pub mod service_onepass;
17pub mod service_slack;
18pub mod service_spotify;
19pub mod service_status;
20pub mod service_telegram;
21pub mod service_ymusic;
22pub mod signing;
23pub mod slack;
24pub mod spotify;
25pub mod telegram;
26pub mod ymusic;
27
28use clap::{Parser, Subcommand};
29
30use zad::error::Result;
31
32pub(crate) trait DialoguerExt<T> {
37 fn into_zad(self) -> Result<T>;
38}
39
40impl<T> DialoguerExt<T> for std::result::Result<T, dialoguer::Error> {
41 fn into_zad(self) -> Result<T> {
42 self.map_err(|e| zad::ZadError::Prompt(e.to_string()))
43 }
44}
45
46#[derive(Debug, Parser)]
47#[command(
48 name = "zad",
49 version,
50 about = "Connect AI agents to external services via scoped service configs.",
51 disable_help_subcommand = true,
52 propagate_version = true
53)]
54pub struct Cli {
55 #[arg(long, global = true)]
57 pub debug: bool,
58
59 #[arg(long, global = true)]
63 pub help_agent: bool,
64
65 #[arg(long, global = true)]
68 pub debug_agent: bool,
69
70 #[arg(long, global = true)]
74 pub wait: bool,
75
76 #[command(subcommand)]
77 pub command: Option<Command>,
78}
79
80#[derive(Debug, Subcommand)]
81pub enum Command {
82 Service(service::ServiceArgs),
84 #[command(name = "1pass")]
86 OnePass(onepass::OnePassArgs),
87 Discord(discord::DiscordArgs),
89 Gcal(gcal::GcalArgs),
91 Slack(slack::SlackArgs),
93 Spotify(spotify::SpotifyArgs),
95 Telegram(telegram::TelegramArgs),
97 Ymusic(ymusic::YmusicArgs),
99 Signing(signing::SigningArgs),
101 Commands(commands::CommandsArgs),
103 Docs(docs::DocsArgs),
105 Man(man::ManArgs),
107}
108
109fn rate_limit_service_for(cmd: &Command) -> Option<&'static str> {
113 match cmd {
114 Command::Discord(_) => Some("discord"),
115 Command::Gcal(_) => Some("gcal"),
116 Command::Slack(_) => Some("slack"),
117 Command::Spotify(_) => Some("spotify"),
118 Command::Telegram(_) => Some("telegram"),
119 Command::Ymusic(_) => Some("ymusic"),
120 Command::OnePass(_)
124 | Command::Service(_)
125 | Command::Signing(_)
126 | Command::Commands(_)
127 | Command::Docs(_)
128 | Command::Man(_) => None,
129 }
130}
131
132pub async fn run() -> Result<()> {
133 let cli = Cli::parse();
134 zad::logging::init(cli.debug);
135
136 if cli.help_agent {
137 print!("{}", help_agent::render());
138 return Ok(());
139 }
140
141 if cli.debug_agent {
142 print!("{}", debug_agent::render());
143 return Ok(());
144 }
145
146 if let Some(cmd) = cli.command.as_ref()
147 && let Some(svc) = rate_limit_service_for(cmd)
148 {
149 zad::rate_limit::precall_check(svc, cli.wait).await?;
150 }
151
152 match cli.command {
153 Some(Command::Service(args)) => service::run(args).await,
154 Some(Command::OnePass(args)) => onepass::run(args).await,
155 Some(Command::Discord(args)) => discord::run(args).await,
156 Some(Command::Gcal(args)) => gcal::run(args).await,
157 Some(Command::Slack(args)) => slack::run(args).await,
158 Some(Command::Spotify(args)) => spotify::run(args).await,
159 Some(Command::Telegram(args)) => telegram::run(args).await,
160 Some(Command::Ymusic(args)) => ymusic::run(args).await,
161 Some(Command::Signing(args)) => signing::run(args),
162 Some(Command::Commands(args)) => commands::run(args),
163 Some(Command::Docs(args)) => docs::run(args),
164 Some(Command::Man(args)) => man::run(args),
165 None => {
166 println!("zad {}", zad::version());
167 println!("Run `zad --help` for usage.");
168 Ok(())
169 }
170 }
171}