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    #[command(about = "Manage Jira inbox issues")]
143    Inbox(inbox::InboxArgs),
144
145    /// Print a shell completion script
146    ///
147    /// Emits the completion script for the chosen shell on stdout; source it
148    /// from the shell profile to get completion for kasl's commands and flags.
149    #[command(about = "Print a shell completion script (source it from your shell profile)")]
150    Completions {
151        /// Target shell
152        shell: clap_complete::Shell,
153    },
154
155    /// Carry one toast button press to the watcher (internal)
156    ///
157    /// Launched by the shortcut behind a toast's Take / Snooze / Dismiss
158    /// button, because a toast button cannot pass an argument of its own.
159    /// It posts the decision to the watcher and exits without printing.
160    /// Hidden from help and completion: the commands a person types for this
161    /// are `kasl inbox take`, `snooze` and `dismiss`, which say what they did.
162    #[command(hide = true, about = "Carry a toast button press to the watcher")]
163    ToastAction(toast_action::ToastActionArgs),
164
165    /// Database migration management utilities (debug builds only)
166    ///
167    /// Provides tools for database schema management, migration history,
168    /// and rollback operations. Available only in debug builds for safety.
169    #[cfg(debug_assertions)]
170    #[command(about = "Database migration management")]
171    Migrations(migrations::MigrationsArgs),
172}
173
174/// The main CLI structure that parses command-line arguments.
175///
176/// Uses `clap` to define the application's interface and delegates
177/// command execution to the appropriate subcommand module. The CLI
178/// requires at least one subcommand to be specified.
179///
180/// # Examples
181///
182/// ```bash
183/// # Display help
184/// kasl --help
185///
186/// # Run a specific command
187/// kasl task add --name "New task"
188/// ```
189#[derive(Debug, Parser)]
190#[command(name = "kasl", author, version, about, long_about = None)]
191#[command(arg_required_else_help(true))]
192pub struct Cli {
193    #[command(subcommand)]
194    command: Commands,
195}
196
197impl Cli {
198    /// Parses command-line arguments and executes the corresponding command.
199    ///
200    /// This is the main entry point for the CLI logic. It handles command
201    /// routing and provides centralized error handling for all commands.
202    ///
203    /// # Examples
204    ///
205    /// ```rust,no_run
206    /// use kasl::commands::Cli;
207    ///
208    /// #[tokio::main]
209    /// async fn main() -> anyhow::Result<()> {
210    ///     Cli::menu().await
211    /// }
212    /// ```
213    pub async fn menu() -> Result<()> {
214        let cli = Self::parse();
215
216        match cli.command {
217            Commands::Autostart(args) => autostart::cmd(args),
218            Commands::Setup(args) => {
219                warn_if_deprecated_alias();
220                init::cmd(args)
221            }
222            Commands::Task(args) => task::cmd(args).await,
223            Commands::End => {
224                // Manually end the current workday. A day that was never
225                // opened is a refusal, not a success: saying "ended" over an
226                // empty database taught users to trust a stamp that is not
227                // there.
228                let today = Local::now().date_naive();
229                if !Workdays::new()?.insert_end(today)? {
230                    return Err(msg_error_anyhow!(Message::WorkdayNeverStarted(today.to_string())));
231                }
232                msg_info!(Message::WorkdayEnded);
233                Ok(())
234            }
235            Commands::Sum(args) => sum::cmd(args).await,
236            Commands::Report(args) => report::cmd(args).await,
237            Commands::Export(args) => export::cmd(args).await,
238            Commands::Template(args) => template::cmd(args),
239            Commands::Tag(args) => tag::cmd(args).await,
240            Commands::SelfUpdate => {
241                warn_if_deprecated_alias();
242                update::cmd().await
243            }
244            Commands::Watch(args) => watch::cmd(args).await,
245            Commands::Pauses(args) => pauses::cmd(args).await,
246            Commands::Completions { shell } => {
247                use clap::CommandFactory;
248                clap_complete::generate(shell, &mut Self::command(), "kasl", &mut std::io::stdout());
249                Ok(())
250            }
251            Commands::Server(args) => server::cmd(args).await,
252            Commands::Inbox(args) => inbox::cmd(args).await,
253            Commands::ToastAction(args) => toast_action::cmd(args),
254
255            // Database migrations only available in debug builds
256            #[cfg(debug_assertions)]
257            Commands::Migrations(args) => migrations::cmd(args),
258        }
259    }
260}
261
262/// Old command names still accepted as aliases, with their replacements.
263///
264/// Removed in 2.0; until then the alias works and says so.
265const DEPRECATED_ALIASES: [(&str, &str); 2] = [("init", "setup"), ("update", "self-update")];
266
267/// Prints a rename notice when the command was invoked by its old name.
268///
269/// clap resolves an alias to the canonical variant without recording which
270/// spelling was typed, so the first non-flag argument is what tells them
271/// apart.
272fn warn_if_deprecated_alias() {
273    let Some(typed) = std::env::args().nth(1) else {
274        return;
275    };
276    if let Some((old, new)) = DEPRECATED_ALIASES.iter().find(|(old, _)| *old == typed) {
277        msg_warning!(Message::DeprecatedCommand(old, new));
278    }
279}