use chrono::NaiveDate;
use clap::{Parser, Subcommand};
fn parse_date(s: &str) -> Result<String, String> {
NaiveDate::parse_from_str(s, "%Y-%m-%d")
.map_err(|_| format!("Invalid date format: '{s}' (expected YYYY-MM-DD)"))?;
Ok(s.to_string())
}
#[derive(Parser)]
#[command(
name = "tgltrk",
about = "Toggl Track CLI",
version,
after_help = "AI agents: run `tgltrk --help-skill` for structured usage instructions."
)]
pub struct Cli {
#[command(subcommand)]
pub command: Option<Command>,
#[arg(long, global = true)]
pub json: bool,
#[arg(long, global = true)]
pub workspace: Option<i64>,
#[arg(long = "help-skill", visible_aliases = ["export-skill", "skill"])]
pub help_skill: bool,
}
#[derive(Subcommand)]
pub enum Command {
Auth {
#[command(subcommand)]
action: AuthAction,
},
Me,
Timer {
#[command(subcommand)]
action: TimerAction,
},
Entries {
#[command(subcommand)]
action: EntriesAction,
},
Projects {
#[command(subcommand)]
action: ProjectsAction,
},
Tags {
#[command(subcommand)]
action: TagsAction,
},
Clients {
#[command(subcommand)]
action: ClientsAction,
},
Workspaces {
#[command(subcommand)]
action: WorkspacesAction,
},
Cache {
#[command(subcommand)]
action: CacheAction,
},
}
#[derive(Subcommand)]
pub enum AuthAction {
Login {
token: Option<String>,
},
Clear,
Status,
}
#[derive(Subcommand)]
pub enum TimerAction {
Current,
Start {
#[arg(short, long)]
description: Option<String>,
#[arg(short, long)]
project: Option<i64>,
#[arg(long)]
task: Option<i64>,
#[arg(short, long, value_delimiter = ',')]
tags: Option<Vec<String>>,
#[arg(short, long)]
billable: bool,
},
Stop,
}
#[derive(Subcommand)]
pub enum EntriesAction {
List {
#[arg(long, value_parser = parse_date)]
since: Option<String>,
#[arg(long, value_parser = parse_date)]
until: Option<String>,
#[arg(short = 'n', long)]
count: Option<usize>,
},
Get {
id: i64,
},
Create {
#[arg(short, long)]
description: Option<String>,
#[arg(short, long)]
project: Option<i64>,
#[arg(long)]
task: Option<i64>,
#[arg(short, long, value_delimiter = ',')]
tags: Option<Vec<String>>,
#[arg(short, long)]
billable: bool,
#[arg(long)]
start: String,
#[arg(long)]
stop: Option<String>,
#[arg(long)]
duration: Option<String>,
},
Edit {
id: i64,
#[arg(short, long)]
description: Option<String>,
#[arg(short, long)]
project: Option<i64>,
#[arg(short, long, value_delimiter = ',')]
tags: Option<Vec<String>>,
#[arg(short, long)]
billable: Option<bool>,
#[arg(long)]
start: Option<String>,
#[arg(long)]
stop: Option<String>,
#[arg(long)]
duration: Option<String>,
},
Delete {
id: i64,
},
Continue {
id: i64,
},
}
#[derive(Subcommand)]
pub enum ProjectsAction {
List,
Get {
id: i64,
},
Create {
name: String,
#[arg(long)]
client: Option<i64>,
},
Update {
id: i64,
#[arg(long)]
name: Option<String>,
#[arg(long)]
client: Option<i64>,
},
Delete {
id: i64,
},
}
#[derive(Subcommand)]
pub enum TagsAction {
List,
Create {
name: String,
},
Update {
id: i64,
#[arg(long)]
name: String,
},
Delete {
id: i64,
},
}
#[derive(Subcommand)]
pub enum ClientsAction {
List,
Get {
id: i64,
},
Create {
name: String,
},
Update {
id: i64,
#[arg(long)]
name: String,
},
Delete {
id: i64,
},
}
#[derive(Subcommand)]
pub enum WorkspacesAction {
List,
Get {
id: i64,
},
}
#[derive(Subcommand)]
pub enum CacheAction {
Clear,
Status,
}