feat(cli): scaffold InitCli subcommand and clap wiring

This commit is contained in:
Jeremiah Russell
2025-10-21 11:15:11 +01:00
committed by Jeremiah Russell
parent f02ae0ae10
commit fd70ef9511
4 changed files with 511 additions and 3 deletions

299
Cargo.lock generated
View File

@@ -101,6 +101,37 @@ dependencies = [
"serde_json", "serde_json",
] ]
[[package]]
name = "assert_cmd"
version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bd389a4b2970a01282ee455294913c0a43724daedcd1a24c3eb0ec1c1320b66"
dependencies = [
"anstyle",
"bstr",
"doc-comment",
"libc",
"predicates",
"predicates-core",
"predicates-tree",
"wait-timeout",
]
[[package]]
name = "assert_fs"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a652f6cb1f516886fcfee5e7a5c078b9ade62cfcb889524efe5a64d682dd27a9"
dependencies = [
"anstyle",
"doc-comment",
"globwalk",
"predicates",
"predicates-core",
"predicates-tree",
"tempfile",
]
[[package]] [[package]]
name = "async-lock" name = "async-lock"
version = "3.4.1" version = "3.4.1"
@@ -203,6 +234,17 @@ dependencies = [
"generic-array", "generic-array",
] ]
[[package]]
name = "bstr"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4"
dependencies = [
"memchr",
"regex-automata",
"serde",
]
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.19.0" version = "3.19.0"
@@ -327,6 +369,19 @@ dependencies = [
"winnow", "winnow",
] ]
[[package]]
name = "console"
version = "0.15.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8"
dependencies = [
"encode_unicode",
"libc",
"once_cell",
"unicode-width",
"windows-sys 0.59.0",
]
[[package]] [[package]]
name = "core-foundation" name = "core-foundation"
version = "0.10.1" version = "0.10.1"
@@ -361,6 +416,25 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "crossbeam-deque"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
"crossbeam-utils",
]
[[package]] [[package]]
name = "crossbeam-utils" name = "crossbeam-utils"
version = "0.8.21" version = "0.8.21"
@@ -381,23 +455,28 @@ dependencies = [
name = "cull-gmail" name = "cull-gmail"
version = "0.0.11" version = "0.0.11"
dependencies = [ dependencies = [
"assert_cmd",
"assert_fs",
"base64", "base64",
"chrono", "chrono",
"clap", "clap",
"clap-verbosity-flag", "clap-verbosity-flag",
"config", "config",
"dialoguer",
"env_logger", "env_logger",
"flate2", "flate2",
"futures", "futures",
"google-gmail1", "google-gmail1",
"httpmock", "httpmock",
"indicatif",
"lazy-regex", "lazy-regex",
"log", "log",
"predicates",
"serde", "serde",
"serde_json", "serde_json",
"temp-env", "temp-env",
"tempfile", "tempfile",
"thiserror", "thiserror 2.0.17",
"tokio", "tokio",
"tokio-test", "tokio-test",
"toml", "toml",
@@ -448,6 +527,25 @@ dependencies = [
"serde_core", "serde_core",
] ]
[[package]]
name = "dialoguer"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de"
dependencies = [
"console",
"shell-words",
"tempfile",
"thiserror 1.0.69",
"zeroize",
]
[[package]]
name = "difflib"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
[[package]] [[package]]
name = "digest" name = "digest"
version = "0.10.7" version = "0.10.7"
@@ -469,6 +567,12 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "doc-comment"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
[[package]] [[package]]
name = "dyn-clone" name = "dyn-clone"
version = "1.0.20" version = "1.0.20"
@@ -481,6 +585,12 @@ version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "encode_unicode"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
[[package]] [[package]]
name = "env_filter" name = "env_filter"
version = "0.1.3" version = "0.1.3"
@@ -563,6 +673,15 @@ dependencies = [
"miniz_oxide", "miniz_oxide",
] ]
[[package]]
name = "float-cmp"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8"
dependencies = [
"num-traits",
]
[[package]] [[package]]
name = "fnv" name = "fnv"
version = "1.0.7" version = "1.0.7"
@@ -712,6 +831,30 @@ version = "0.32.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
[[package]]
name = "globset"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eab69130804d941f8075cfd713bf8848a2c3b3f201a9457a11e6f87e1ab62305"
dependencies = [
"aho-corasick",
"bstr",
"log",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "globwalk"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757"
dependencies = [
"bitflags",
"ignore",
"walkdir",
]
[[package]] [[package]]
name = "google-apis-common" name = "google-apis-common"
version = "7.0.0" version = "7.0.0"
@@ -896,7 +1039,7 @@ dependencies = [
"similar", "similar",
"stringmetrics", "stringmetrics",
"tabwriter", "tabwriter",
"thiserror", "thiserror 2.0.17",
"tokio", "tokio",
"tracing", "tracing",
"url", "url",
@@ -1100,6 +1243,22 @@ dependencies = [
"icu_properties", "icu_properties",
] ]
[[package]]
name = "ignore"
version = "0.4.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81776e6f9464432afcc28d03e52eb101c93b6f0566f52aef2427663e700f0403"
dependencies = [
"crossbeam-deque",
"globset",
"log",
"memchr",
"regex-automata",
"same-file",
"walkdir",
"winapi-util",
]
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "1.9.3" version = "1.9.3"
@@ -1123,6 +1282,19 @@ dependencies = [
"serde_core", "serde_core",
] ]
[[package]]
name = "indicatif"
version = "0.17.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235"
dependencies = [
"console",
"number_prefix",
"portable-atomic",
"unicode-width",
"web-time",
]
[[package]] [[package]]
name = "io-uring" name = "io-uring"
version = "0.7.10" version = "0.7.10"
@@ -1278,6 +1450,12 @@ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
[[package]]
name = "normalize-line-endings"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
[[package]] [[package]]
name = "num-conv" name = "num-conv"
version = "0.1.0" version = "0.1.0"
@@ -1302,6 +1480,12 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "number_prefix"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
[[package]] [[package]]
name = "object" name = "object"
version = "0.37.3" version = "0.37.3"
@@ -1421,6 +1605,36 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
name = "predicates"
version = "3.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573"
dependencies = [
"anstyle",
"difflib",
"float-cmp",
"normalize-line-endings",
"predicates-core",
"regex",
]
[[package]]
name = "predicates-core"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa"
[[package]]
name = "predicates-tree"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c"
dependencies = [
"predicates-core",
"termtree",
]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.101" version = "1.0.101"
@@ -1603,6 +1817,15 @@ version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]] [[package]]
name = "schannel" name = "schannel"
version = "0.1.28" version = "0.1.28"
@@ -1776,6 +1999,12 @@ dependencies = [
"digest", "digest",
] ]
[[package]]
name = "shell-words"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
[[package]] [[package]]
name = "shlex" name = "shlex"
version = "1.3.0" version = "1.3.0"
@@ -1902,13 +2131,39 @@ dependencies = [
"windows-sys 0.61.1", "windows-sys 0.61.1",
] ]
[[package]]
name = "termtree"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683"
[[package]]
name = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl 1.0.69",
]
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "2.0.17" version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl 2.0.17",
]
[[package]]
name = "thiserror-impl"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn",
] ]
[[package]] [[package]]
@@ -2186,6 +2441,25 @@ version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "wait-timeout"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11"
dependencies = [
"libc",
]
[[package]]
name = "walkdir"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
"same-file",
"winapi-util",
]
[[package]] [[package]]
name = "want" name = "want"
version = "0.3.1" version = "0.3.1"
@@ -2269,6 +2543,25 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "web-time"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "winapi-util"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys 0.61.1",
]
[[package]] [[package]]
name = "windows-core" name = "windows-core"
version = "0.62.1" version = "0.62.1"

View File

@@ -39,6 +39,8 @@ tokio = { version = "1.47.1", features = ["macros", "rt-multi-thread", "process"
toml = "0.9.7" toml = "0.9.7"
base64 = "0.22" base64 = "0.22"
flate2 = "1.0" flate2 = "1.0"
dialoguer = "0.11"
indicatif = "0.17"
[dev-dependencies] [dev-dependencies]
httpmock = "0.8" httpmock = "0.8"
@@ -46,6 +48,9 @@ tokio-test = "0.4"
temp-env = "0.3" temp-env = "0.3"
tempfile = "3.12" tempfile = "3.12"
futures = "0.3" futures = "0.3"
assert_cmd = "2.0"
assert_fs = "1.1"
predicates = "3.1"
[lints.clippy] [lints.clippy]
uninlined-format-args = "warn" uninlined-format-args = "warn"

197
src/cli/init_cli.rs Normal file
View File

@@ -0,0 +1,197 @@
//! # Initialization CLI Module
//!
//! This module provides CLI functionality for initializing the cull-gmail application,
//! including creating configuration directories, setting up OAuth2 credentials,
//! generating default configuration files, and completing the initial authentication flow.
//!
//! ## Overview
//!
//! The initialization system allows users to:
//!
//! - **Create configuration directory**: Set up the cull-gmail configuration directory
//! - **Install credentials**: Copy and validate OAuth2 credential files
//! - **Generate configuration**: Create default cull-gmail.toml and rules.toml files
//! - **Complete OAuth2 flow**: Authenticate with Gmail API and persist tokens
//! - **Interactive setup**: Guide users through setup with prompts and confirmations
//! - **Dry-run mode**: Preview all actions without making changes
//!
//! ## Use Cases
//!
//! ### First-time Setup
//! ```bash
//! # Interactive setup with credential file
//! cull-gmail init --interactive --credential-file ~/Downloads/client_secret.json
//!
//! # Non-interactive setup (credential file copied manually later)
//! cull-gmail init --config-dir ~/.cull-gmail
//! ```
//!
//! ### Planning and Verification
//! ```bash
//! # See what would be created without making changes
//! cull-gmail init --dry-run
//!
//! # Preview with specific options
//! cull-gmail init --config-dir /custom/path --credential-file credentials.json --dry-run
//! ```
//!
//! ### Force Overwrite
//! ```bash
//! # Recreate configuration, backing up existing files
//! cull-gmail init --force
//! ```
//!
//! ## Security Considerations
//!
//! - **Credential Protection**: OAuth2 credential files are copied with 0600 permissions
//! - **Token Directory**: Token cache directory is created with 0700 permissions
//! - **Backup Safety**: Existing files are backed up with timestamps before overwriting
//! - **Interactive Confirmation**: Prompts for confirmation before overwriting existing files
use clap::Parser;
use std::path::PathBuf;
/// Initialize cull-gmail configuration, credentials, and OAuth2 tokens.
///
/// This command sets up the complete cull-gmail environment by creating the configuration
/// directory, installing OAuth2 credentials, generating default configuration files,
/// and completing the initial Gmail API authentication to persist tokens.
///
/// ## Setup Process
///
/// 1. **Configuration Directory**: Create or verify the configuration directory
/// 2. **Credential Installation**: Copy and validate OAuth2 credential file (if provided)
/// 3. **Configuration Generation**: Create cull-gmail.toml with safe defaults
/// 4. **Rules Template**: Generate rules.toml with example retention rules
/// 5. **Token Directory**: Ensure OAuth2 token cache directory exists
/// 6. **Authentication**: Complete OAuth2 flow to generate and persist tokens
///
/// ## Interactive vs Non-Interactive
///
/// - **Non-interactive** (default): Proceeds with provided options, fails if conflicts exist
/// - **Interactive** (`--interactive`): Prompts for missing information and confirmation for conflicts
/// - **Dry-run** (`--dry-run`): Shows planned actions without making any changes
///
/// ## Examples
///
/// ```bash
/// # Basic initialization
/// cull-gmail init
///
/// # Interactive setup with credential file
/// cull-gmail init --interactive --credential-file client_secret.json
///
/// # Custom configuration directory
/// cull-gmail init --config-dir /path/to/config
///
/// # Preview actions without changes
/// cull-gmail init --dry-run
///
/// # Force overwrite existing files
/// cull-gmail init --force
/// ```
#[derive(Parser, Debug)]
pub struct InitCli {
/// Configuration directory path.
///
/// Supports path prefixes:
/// - `h:path` - Relative to home directory (default: `h:.cull-gmail`)
/// - `c:path` - Relative to current directory
/// - `r:path` - Relative to filesystem root
/// - `path` - Use path as-is
#[arg(
long = "config-dir",
value_name = "DIR",
default_value = "h:.cull-gmail",
help = "Configuration directory path"
)]
pub config_dir: String,
/// OAuth2 credential file path.
///
/// This should be the JSON file downloaded from Google Cloud Console
/// containing your OAuth2 client credentials for Desktop application type.
/// The file will be copied to the configuration directory as `credential.json`.
#[arg(
long = "credential-file",
value_name = "PATH",
help = "Path to OAuth2 credential JSON file"
)]
pub credential_file: Option<PathBuf>,
/// Overwrite existing files without prompting.
///
/// When enabled, existing configuration files will be backed up with
/// timestamps and then overwritten with new defaults. Use with caution
/// as this will replace your current configuration.
#[arg(
long = "force",
help = "Overwrite existing files (creates timestamped backups)"
)]
pub force: bool,
/// Show planned actions without making changes.
///
/// Enables preview mode where all planned operations are displayed
/// but no files are created, modified, or removed. OAuth2 authentication
/// flow is also skipped in dry-run mode.
#[arg(
long = "dry-run",
help = "Preview actions without making changes"
)]
pub dry_run: bool,
/// Enable interactive prompts and confirmations.
///
/// When enabled, the command will prompt for missing information
/// (such as credential file path) and ask for confirmation before
/// overwriting existing files. Recommended for first-time users.
#[arg(
long = "interactive",
short = 'i',
help = "Prompt for missing information and confirmations"
)]
pub interactive: bool,
}
impl InitCli {
/// Execute the initialization command.
///
/// This method orchestrates the complete initialization workflow including
/// configuration directory creation, credential installation, file generation,
/// and OAuth2 authentication based on the provided command-line options.
///
/// # Returns
///
/// Returns `Result<()>` indicating success or failure of the initialization process.
///
/// # Process Flow
///
/// 1. **Plan Operations**: Analyze current state and generate operation plan
/// 2. **Validate Inputs**: Check credential file validity and resolve paths
/// 3. **Interactive Prompts**: Request missing information if in interactive mode
/// 4. **Execute or Preview**: Apply operations or show dry-run preview
/// 5. **OAuth2 Flow**: Complete authentication and token generation
/// 6. **Success Reporting**: Display results and next steps
///
/// # Errors
///
/// This method can return errors for:
/// - Invalid or missing credential files
/// - File system permission issues
/// - Configuration conflicts without force or interactive resolution
/// - OAuth2 authentication failures
/// - Network connectivity issues during authentication
pub async fn run(&self) -> cull_gmail::Result<()> {
log::info!("Starting cull-gmail initialization");
if self.dry_run {
println!("DRY RUN: No changes will be made");
}
// TODO: Implement initialization logic
println!("Init command called with options: {self:?}");
Ok(())
}
}

View File

@@ -111,6 +111,7 @@
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
mod init_cli;
mod labels_cli; mod labels_cli;
mod messages_cli; mod messages_cli;
mod rules_cli; mod rules_cli;
@@ -120,6 +121,7 @@ use config::Config;
use cull_gmail::{ClientConfig, EolAction, GmailClient, Result, RuleProcessor, Rules}; use cull_gmail::{ClientConfig, EolAction, GmailClient, Result, RuleProcessor, Rules};
use std::{env, error::Error as stdError}; use std::{env, error::Error as stdError};
use init_cli::InitCli;
use labels_cli::LabelsCli; use labels_cli::LabelsCli;
use messages_cli::MessagesCli; use messages_cli::MessagesCli;
use rules_cli::RulesCli; use rules_cli::RulesCli;
@@ -175,6 +177,13 @@ struct Cli {
/// then query specific messages, and finally configure automated rules. /// then query specific messages, and finally configure automated rules.
#[derive(Subcommand, Debug)] #[derive(Subcommand, Debug)]
enum SubCmds { enum SubCmds {
/// Initialize cull-gmail configuration, credentials, and OAuth2 tokens.
///
/// Sets up the complete cull-gmail environment including configuration directory,
/// OAuth2 credentials, default configuration files, and initial authentication flow.
#[clap(name = "init", display_order = 1)]
Init(InitCli),
/// Query, filter, and perform batch operations on Gmail messages. /// Query, filter, and perform batch operations on Gmail messages.
/// ///
/// Supports advanced Gmail query syntax, label filtering, and batch actions /// Supports advanced Gmail query syntax, label filtering, and batch actions
@@ -295,6 +304,10 @@ async fn run(args: Cli) -> Result<()> {
}; };
match sub_command { match sub_command {
SubCmds::Init(init_cli) => {
// Init commands don't need a Gmail client since they set up the config
init_cli.run().await
}
SubCmds::Message(messages_cli) => messages_cli.run(&mut client).await, SubCmds::Message(messages_cli) => messages_cli.run(&mut client).await,
SubCmds::Labels(labels_cli) => labels_cli.run(client).await, SubCmds::Labels(labels_cli) => labels_cli.run(client).await,
SubCmds::Rules(rules_cli) => rules_cli.run(&mut client).await, SubCmds::Rules(rules_cli) => rules_cli.run(&mut client).await,