Skip to main content

kasl/commands/
mod.rs

1//! Command-line interface commands for kasl application.
2//!
3//! Contains all CLI command implementations for task management, activity monitoring,
4//! reporting, and system configuration.
5//!
6//! ## Usage
7//!
8//! ```bash
9//! kasl watch                    # Start activity monitoring
10//! kasl task add --name "Review code" # Create a new task
11//! kasl report                   # Generate today's report
12//! kasl export tasks --format csv # Export tasks to CSV
13//! ```
14
15pub mod autostart;
16pub mod export;
17pub mod inbox;
18pub mod init;
19pub mod migrations;
20pub mod pauses;
21pub mod report;
22pub mod server;
23pub mod sum;
24pub mod tag;
25pub mod task;
26pub mod template;
27pub mod toast_action;
28pub mod update;
29pub mod watch;
30
31use crate::{db::workdays::Workdays, libs::messages::types::Message, msg_error_anyhow, msg_info, msg_warning};
32use anyhow::Result;
33use chrono::Local;
34use clap::{Parser, Subcommand};
35
36/// Defines the main subcommands that the application can execute.
37#[derive(Debug, Subcommand)]
38enum Commands {
39    /// Manage autostart configuration for system boot
40    ///
41    /// Controls whether kasl automatically starts monitoring when the system boots.
42    /// Supports both system-level and user-level autostart on Windows.
43    #[command(about = "Manage autostart on system boot")]
44    Autostart(autostart::AutostartArgs),
45
46    /// Set up application configuration interactively
47    ///
48    /// Guides the user through setting up API credentials, monitor settings,
49    /// and other configuration options required for kasl to function properly.
50    ///
51    /// `init` stays as a deprecated alias until 2.0. An alias rather than a
52    /// hidden variant: clap_complete emits hidden subcommands into the
53    /// completion scripts, so Tab would keep teaching the old spelling.
54    #[command(about = "Set up configuration", alias = "init")]
55    Setup(init::SetupArgs),
56
57    /// Comprehensive task management command
58    ///
59    /// Handles all task-related operations including creation, editing, deletion,
60    /// viewing, and integration with external services like GitLab and Jira.
61    #[command(about = "Create task")]
62    Task(task::TaskArgs),
63
64    /// Manually end the current workday
65    ///
66    /// Records the end timestamp for today's work session. Typically used
67    /// when the automatic monitoring needs to be manually finalized.
68    #[command(about = "Write end timestamp to database")]
69    End,
70
71    /// Display monthly working hours summary
72    ///
73    /// Shows a comprehensive overview of work hours, productivity metrics,
74    /// and daily breakdowns for the current month.
75    #[command(about = "Get summary")]
76    Sum(sum::SumArgs),
77
78    /// Update kasl itself to the latest release
79    ///
80    /// Checks GitHub releases for newer versions and automatically downloads
81    /// and installs updates if available.
82    ///
83    /// `update` stays as a deprecated alias until 2.0; it read as "update my
84    /// data", which is what every other command does.
85    #[command(name = "self-update", about = "Update kasl itself to the latest release", alias = "update")]
86    SelfUpdate,
87
88    /// Generate and optionally submit work reports
89    ///
90    /// Creates detailed daily reports with work intervals, tasks, and productivity
91    /// metrics. Can automatically submit reports to configured APIs.
92    #[command(about = "Prepare a report")]
93    Report(report::ReportArgs),
94
95    /// Export application data to external formats
96    ///
97    /// Supports exporting tasks, reports, and summaries to CSV, JSON, and Excel
98    /// formats for external analysis or backup purposes.
99    #[command(about = "Export data to various formats")]
100    Export(export::ExportArgs),
101
102    /// Manage reusable task templates
103    ///
104    /// Create, edit, and use templates for frequently created tasks to
105    /// streamline task creation workflow.
106    #[command(about = "Manage task templates")]
107    Template(template::TemplateArgs),
108
109    /// Organize tasks with custom tags
110    ///
111    /// Create and manage tags to categorize and filter tasks by project,
112    /// priority, or any custom criteria.
113    #[command(about = "Manage task tags")]
114    Tag(tag::TagArgs),
115
116    /// Background activity monitoring daemon
117    ///
118    /// Monitors user input activity to automatically detect work sessions,
119    /// breaks, and workday boundaries. Can run as a background service.
120    #[command(about = "Watch user activity in the background to record pauses")]
121    Watch(watch::WatchArgs),
122
123    /// View recorded pauses and record ones the monitor missed
124    ///
125    /// Lists detected pauses for a date, and lets the user add an absence the
126    /// activity monitor did not catch or remove one recorded by mistake.
127    #[command(about = "View pauses and record ones the monitor missed")]
128    Pauses(pauses::PausesArgs),
129
130    /// Connection to the team's kasl-server
131    ///
132    /// Connects this machine to a kasl-server with an agent token issued by
133    /// an administrator, shows where the connection stands, and forgets it
134    /// again. The token lives in the OS keyring, never in the config file.
135    #[command(about = "Manage the connection to a kasl-server")]
136    Server(server::ServerArgs),
137
138    /// Jira inbox of assigned open issues
139    ///
140    /// Syncs assigned unresolved Jira issues into a local table, lists them
141    /// by priority, and supports pin / dismiss / open / import into tasks,
142    /// and starting, closing and commenting on issues in Jira.
143    // Boxed: a filter set on every subcommand makes these the largest
144    // arguments by far, and every other variant would carry their size.
145    #[command(about = "Manage Jira inbox issues")]
146    Inbox(Box<inbox::InboxArgs>),
147
148    /// Print a shell completion script
149    ///
150    /// Emits the completion script for the chosen shell on stdout; source it
151    /// from the shell profile to get completion for kasl's commands and flags.
152    #[command(about = "Print a shell completion script (source it from your shell profile)")]
153    Completions {
154        /// Target shell
155        shell: clap_complete::Shell,
156    },
157
158    /// Carry one toast button press to the watcher (internal)
159    ///
160    /// Launched by the shortcut behind a toast's Take / Snooze / Dismiss
161    /// button, because a toast button cannot pass an argument of its own.
162    /// It posts the decision to the watcher and exits without printing.
163    /// Hidden from help and completion: the commands a person types for this
164    /// are `kasl inbox take`, `snooze` and `dismiss`, which say what they did.
165    #[command(hide = true, about = "Carry a toast button press to the watcher")]
166    ToastAction(toast_action::ToastActionArgs),
167
168    /// Database migration management utilities (debug builds only)
169    ///
170    /// Provides tools for database schema management, migration history,
171    /// and rollback operations. Available only in debug builds for safety.
172    #[cfg(debug_assertions)]
173    #[command(about = "Database migration management")]
174    Migrations(migrations::MigrationsArgs),
175}
176
177/// The main CLI structure that parses command-line arguments.
178///
179/// Uses `clap` to define the application's interface and delegates
180/// command execution to the appropriate subcommand module. The CLI
181/// requires at least one subcommand to be specified.
182///
183/// # Examples
184///
185/// ```bash
186/// # Display help
187/// kasl --help
188///
189/// # Run a specific command
190/// kasl task add --name "New task"
191/// ```
192#[derive(Debug, Parser)]
193#[command(name = "kasl", author, version, about, long_about = None)]
194#[command(arg_required_else_help(true))]
195pub struct Cli {
196    #[command(subcommand)]
197    command: Commands,
198}
199
200impl Cli {
201    /// Parses command-line arguments and executes the corresponding command.
202    ///
203    /// This is the main entry point for the CLI logic. It handles command
204    /// routing and provides centralized error handling for all commands.
205    ///
206    /// # Examples
207    ///
208    /// ```rust,no_run
209    /// use kasl::commands::Cli;
210    ///
211    /// #[tokio::main]
212    /// async fn main() -> anyhow::Result<()> {
213    ///     Cli::menu().await
214    /// }
215    /// ```
216    pub async fn menu() -> Result<()> {
217        let cli = Self::parse();
218
219        match cli.command {
220            Commands::Autostart(args) => autostart::cmd(args),
221            Commands::Setup(args) => {
222                warn_if_deprecated_alias();
223                init::cmd(args)
224            }
225            Commands::Task(args) => task::cmd(args).await,
226            Commands::End => {
227                // Manually end the current workday. A day that was never
228                // opened is a refusal, not a success: saying "ended" over an
229                // empty database taught users to trust a stamp that is not
230                // there.
231                let today = Local::now().date_naive();
232                if !Workdays::new()?.insert_end(today)? {
233                    return Err(msg_error_anyhow!(Message::WorkdayNeverStarted(today.to_string())));
234                }
235                msg_info!(Message::WorkdayEnded);
236                Ok(())
237            }
238            Commands::Sum(args) => sum::cmd(args).await,
239            Commands::Report(args) => report::cmd(args).await,
240            Commands::Export(args) => export::cmd(args).await,
241            Commands::Template(args) => template::cmd(args),
242            Commands::Tag(args) => tag::cmd(args).await,
243            Commands::SelfUpdate => {
244                warn_if_deprecated_alias();
245                update::cmd().await
246            }
247            Commands::Watch(args) => watch::cmd(args).await,
248            Commands::Pauses(args) => pauses::cmd(args).await,
249            Commands::Completions { shell } => {
250                use clap::CommandFactory;
251                clap_complete::generate(shell, &mut Self::command(), "kasl", &mut std::io::stdout());
252                Ok(())
253            }
254            Commands::Server(args) => server::cmd(args).await,
255            Commands::Inbox(args) => inbox::cmd(*args).await,
256            Commands::ToastAction(args) => toast_action::cmd(args).await,
257
258            // Database migrations only available in debug builds
259            #[cfg(debug_assertions)]
260            Commands::Migrations(args) => migrations::cmd(args),
261        }
262    }
263}
264
265/// Old command names still accepted as aliases, with their replacements.
266///
267/// Removed in 2.0; until then the alias works and says so.
268const DEPRECATED_ALIASES: [(&str, &str); 2] = [("init", "setup"), ("update", "self-update")];
269
270/// Prints a rename notice when the command was invoked by its old name.
271///
272/// clap resolves an alias to the canonical variant without recording which
273/// spelling was typed, so the first non-flag argument is what tells them
274/// apart.
275fn warn_if_deprecated_alias() {
276    let Some(typed) = std::env::args().nth(1) else {
277        return;
278    };
279    if let Some((old, new)) = DEPRECATED_ALIASES.iter().find(|(old, _)| *old == typed) {
280        msg_warning!(Message::DeprecatedCommand(old, new));
281    }
282}