Skip to main content

par_term/cli/
mod.rs

1//! Command-line interface for par-term.
2//!
3//! This module handles CLI argument parsing and subcommands like shader installation.
4//! Install/uninstall procedure implementations live in the [`install`] submodule.
5
6pub mod install;
7
8use crate::config::ShellType;
9use clap::{Parser, Subcommand};
10use std::path::PathBuf;
11
12/// Shell type argument for CLI
13#[derive(Debug, Clone, Copy, clap::ValueEnum)]
14pub enum ShellTypeArg {
15    Bash,
16    Zsh,
17    Fish,
18}
19
20impl From<ShellTypeArg> for ShellType {
21    fn from(arg: ShellTypeArg) -> Self {
22        match arg {
23            ShellTypeArg::Bash => ShellType::Bash,
24            ShellTypeArg::Zsh => ShellType::Zsh,
25            ShellTypeArg::Fish => ShellType::Fish,
26        }
27    }
28}
29
30/// par-term - A GPU-accelerated terminal emulator
31#[derive(Parser)]
32#[command(name = "par-term")]
33#[command(author, version, about, long_about = None)]
34pub struct Cli {
35    #[command(subcommand)]
36    pub command: Option<Commands>,
37
38    /// Background shader to use (filename from shaders directory)
39    #[arg(long, value_name = "SHADER")]
40    pub shader: Option<String>,
41
42    /// Exit after the specified number of seconds
43    #[arg(long, value_name = "SECONDS")]
44    pub exit_after: Option<f64>,
45
46    /// Take a screenshot and save to the specified path (default: timestamped PNG in current dir)
47    #[arg(long, value_name = "PATH", num_args = 0..=1, default_missing_value = "")]
48    pub screenshot: Option<PathBuf>,
49
50    /// Send a command to the shell after 1 second delay
51    #[arg(long, value_name = "COMMAND")]
52    pub command_to_send: Option<String>,
53
54    /// Enable session logging (overrides config setting)
55    #[arg(long)]
56    pub log_session: bool,
57
58    /// Set debug log level (overrides config and RUST_LOG)
59    #[arg(long, value_enum, value_name = "LEVEL")]
60    pub log_level: Option<LogLevelArg>,
61}
62
63/// Log level argument for CLI
64#[derive(Debug, Clone, Copy, clap::ValueEnum)]
65pub enum LogLevelArg {
66    Off,
67    Error,
68    Warn,
69    Info,
70    Debug,
71    Trace,
72}
73
74impl LogLevelArg {
75    /// Convert to `log::LevelFilter`
76    pub fn to_level_filter(self) -> log::LevelFilter {
77        match self {
78            LogLevelArg::Off => log::LevelFilter::Off,
79            LogLevelArg::Error => log::LevelFilter::Error,
80            LogLevelArg::Warn => log::LevelFilter::Warn,
81            LogLevelArg::Info => log::LevelFilter::Info,
82            LogLevelArg::Debug => log::LevelFilter::Debug,
83            LogLevelArg::Trace => log::LevelFilter::Trace,
84        }
85    }
86}
87
88#[derive(Subcommand)]
89pub enum Commands {
90    /// Install shaders from the latest GitHub release
91    InstallShaders {
92        /// Skip confirmation prompt
93        #[arg(short = 'y', long)]
94        yes: bool,
95
96        /// Force overwrite without prompting
97        #[arg(short, long)]
98        force: bool,
99    },
100
101    /// Install shell integration for your shell
102    InstallShellIntegration {
103        /// Specify shell type (auto-detected if not provided)
104        #[arg(long, value_enum)]
105        shell: Option<ShellTypeArg>,
106    },
107
108    /// Uninstall shell integration
109    UninstallShellIntegration,
110
111    /// Uninstall shaders (removes bundled files, keeps user files)
112    UninstallShaders {
113        /// Force removal without prompting
114        #[arg(short, long)]
115        force: bool,
116    },
117
118    /// Lint a custom background shader file
119    ShaderLint {
120        /// Path to the .glsl shader file to lint
121        path: PathBuf,
122
123        /// Include source-level readability scoring and suggested readability defaults
124        #[arg(long)]
125        readability: bool,
126
127        /// Apply suggested readability defaults to the shader metadata without prompting
128        #[arg(long)]
129        apply: bool,
130
131        /// Do not prompt to apply suggested readability defaults
132        #[arg(long)]
133        no_prompt: bool,
134    },
135
136    /// Install both shaders and shell integration
137    InstallIntegrations {
138        /// Skip confirmation prompts
139        #[arg(short = 'y', long)]
140        yes: bool,
141    },
142
143    /// Update par-term to the latest version
144    SelfUpdate {
145        /// Skip confirmation prompt
146        #[arg(short = 'y', long)]
147        yes: bool,
148    },
149
150    /// Run as an MCP server (used by ACP agents for config updates)
151    McpServer,
152}
153
154/// Runtime options passed from CLI to the application
155#[derive(Clone, Debug, Default)]
156pub struct RuntimeOptions {
157    /// Background shader to use
158    pub shader: Option<String>,
159    /// Exit after this many seconds
160    pub exit_after: Option<f64>,
161    /// Take a screenshot (Some(empty path) = auto-name, Some(path) = specific path, None = no screenshot)
162    pub screenshot: Option<PathBuf>,
163    /// Command to send to shell after delay
164    pub command_to_send: Option<String>,
165    /// Enable session logging (overrides config)
166    pub log_session: bool,
167    /// Log level override from CLI
168    pub log_level: Option<log::LevelFilter>,
169}
170
171/// Result of CLI processing
172pub enum CliResult {
173    /// Continue with normal application startup, with optional runtime options
174    Continue(RuntimeOptions),
175    /// Exit with the given code (subcommand completed)
176    Exit(i32),
177}
178
179/// Process CLI arguments and handle subcommands
180pub fn process_cli() -> CliResult {
181    use install::{
182        install_integrations_cli, install_shaders_cli, install_shell_integration_cli,
183        self_update_cli, uninstall_shaders_cli, uninstall_shell_integration_cli,
184    };
185
186    let cli = Cli::parse();
187
188    match cli.command {
189        Some(Commands::InstallShaders { yes, force }) => {
190            let result = install_shaders_cli(yes || force);
191            CliResult::Exit(if result.is_ok() { 0 } else { 1 })
192        }
193        Some(Commands::InstallShellIntegration { shell }) => {
194            let result = install_shell_integration_cli(shell.map(Into::into));
195            CliResult::Exit(if result.is_ok() { 0 } else { 1 })
196        }
197        Some(Commands::UninstallShellIntegration) => {
198            let result = uninstall_shell_integration_cli();
199            CliResult::Exit(if result.is_ok() { 0 } else { 1 })
200        }
201        Some(Commands::UninstallShaders { force }) => {
202            let result = uninstall_shaders_cli(force);
203            CliResult::Exit(if result.is_ok() { 0 } else { 1 })
204        }
205        Some(Commands::ShaderLint {
206            path,
207            readability,
208            apply,
209            no_prompt,
210        }) => {
211            let result = crate::shader_lint::shader_lint_cli(&path, readability, apply, !no_prompt);
212            CliResult::Exit(if result.is_ok() { 0 } else { 1 })
213        }
214        Some(Commands::InstallIntegrations { yes }) => {
215            let result = install_integrations_cli(yes);
216            CliResult::Exit(if result.is_ok() { 0 } else { 1 })
217        }
218        Some(Commands::SelfUpdate { yes }) => {
219            let result = self_update_cli(yes);
220            CliResult::Exit(if result.is_ok() { 0 } else { 1 })
221        }
222        Some(Commands::McpServer) => {
223            crate::mcp_server::set_app_version(crate::VERSION);
224            crate::mcp_server::run_mcp_server();
225            CliResult::Exit(0)
226        }
227        None => {
228            // Extract runtime options from CLI flags
229            let options = RuntimeOptions {
230                shader: cli.shader,
231                exit_after: cli.exit_after,
232                screenshot: cli.screenshot,
233                command_to_send: cli.command_to_send,
234                log_session: cli.log_session,
235                log_level: cli.log_level.map(|l| l.to_level_filter()),
236            };
237            CliResult::Continue(options)
238        }
239    }
240}