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