1use anyhow::Result;
2use clap::{Parser, Subcommand};
3
4use crate::duration::parse_idle;
5use crate::paths::Context;
6use crate::{attach, doctor, list, notify, resume, rm, server, service, setup, stats, tui};
7
8#[derive(Debug, Parser)]
10#[command(name = "agent-berth", version, about, propagate_version = true)]
11struct Cli {
12 #[command(subcommand)]
13 command: Option<Command>,
14}
15
16#[derive(Debug, Subcommand)]
17enum Command {
18 Tui {
20 #[arg(long)]
23 tmux: bool,
24 },
25 Server,
27 Setup {
29 #[arg(long)]
31 no_service: bool,
32 },
33 Teardown,
35 Service {
37 #[command(subcommand)]
38 action: ServiceAction,
39 },
40 List {
42 #[arg(long)]
44 json: bool,
45 #[arg(long)]
47 resumable: bool,
48 #[arg(
50 long,
51 value_name = "DURATION",
52 requires = "resumable",
53 num_args = 0..=1,
54 default_missing_value = "20m"
55 )]
56 idle: Option<String>,
57 #[arg(long, requires = "resumable")]
59 here: bool,
60 },
61 Stats {
63 #[arg(long)]
65 json: bool,
66 },
67 Notify {
69 #[arg(long)]
70 provider: String,
71 },
72 Attach {
74 query: Option<String>,
76 #[arg(short, long)]
78 preview: bool,
79 #[arg(short, long)]
81 session: bool,
82 #[arg(long)]
84 dry_run: bool,
85 },
86 Doctor,
88 Resume {
90 pattern: Option<String>,
92 #[arg(
94 long,
95 value_name = "DURATION",
96 num_args = 0..=1,
97 default_missing_value = "20m"
98 )]
99 idle: Option<String>,
100 #[arg(long)]
102 here: bool,
103 #[arg(long)]
105 dry_run: bool,
106 },
107 #[command(alias = "remove")]
109 Rm {
110 patterns: Vec<String>,
112 },
113}
114
115#[derive(Debug, Subcommand)]
116enum ServiceAction {
117 Start,
119 Stop,
121 Restart,
123}
124
125pub fn run() -> Result<()> {
126 let cli = Cli::parse();
127 let ctx = Context::from_env()?;
128 let Some(command) = cli.command else {
129 if !std::io::IsTerminal::is_terminal(&std::io::stdout()) {
130 return list::run(&ctx, false, false, None, false);
131 }
132 return tui::run(&ctx);
133 };
134 match command {
135 Command::Tui { tmux } => {
136 if tmux {
137 return tui::run_tmux(&ctx);
138 }
139 if !std::io::IsTerminal::is_terminal(&std::io::stdout()) {
140 anyhow::bail!("tui requires a terminal");
141 }
142 tui::run(&ctx)
143 }
144 Command::Server => server::run(&ctx),
145 Command::Setup { no_service } => setup::setup(&ctx, no_service),
146 Command::Teardown => setup::teardown(&ctx),
147 Command::Service { action } => match action {
148 ServiceAction::Start => service::start(&ctx),
149 ServiceAction::Stop => service::stop(&ctx),
150 ServiceAction::Restart => service::restart(&ctx),
151 },
152 Command::List {
153 json,
154 resumable,
155 idle,
156 here,
157 } => {
158 let idle = if resumable {
159 parse_idle(idle.as_deref())?
160 } else {
161 None
162 };
163 list::run(&ctx, json, resumable, idle, here)
164 }
165 Command::Stats { json } => stats::run(&ctx, json),
166 Command::Notify { provider } => notify::run(&ctx, provider),
167 Command::Attach {
168 query,
169 preview,
170 session,
171 dry_run,
172 } => attach::run(&ctx, query, preview, session, dry_run),
173 Command::Doctor => doctor::run(&ctx),
174 Command::Resume {
175 pattern,
176 idle,
177 here,
178 dry_run,
179 } => resume::run(&ctx, parse_idle(idle.as_deref())?, pattern, here, dry_run),
180 Command::Rm { patterns } => rm::run(&ctx, patterns),
181 }
182}