Skip to main content

cli/commands/
cli.rs

1use crate::completion;
2use crate::version;
3use clap::{Args, Parser, Subcommand, ValueEnum};
4
5use super::{
6    AppCommands, EnvCommands, LocalCommands, PresetCommands, SelfCommands, ServeCommands,
7    ShellCommands, StateCommands, SysCommands, TaskCommands, TaskRunCommand, ThemeCommands,
8};
9
10/// Manage shell presets, app configs, system setup, and personal tools
11#[derive(Parser, Debug)]
12#[command(name = "shine")]
13#[command(version = version::display(), about, long_about = None)]
14#[command(
15    after_help = "QUICK START:\n  shine list --available\n  shine info app/starship\n  shine install app/starship\n  shine update && shine upgrade\n\nTARGETS:\n  Use app/<category>, shell/<category>, or sys/<item>. A bare app/shell category is accepted when unique.\n\nNAMESPACES:\n  app, shell, and sys expose resource-specific operations; preset, state, self, serve, completions, theme, and local are advanced tools."
16)]
17pub struct Cli {
18    #[arg(long, global = true)]
19    pub config_dir: Option<String>,
20
21    #[command(subcommand)]
22    pub command: Commands,
23}
24
25#[derive(Subcommand, Debug)]
26pub enum Commands {
27    #[command(name = "__shell-render", hide = true)]
28    ShellRender {
29        #[arg(value_name = "TARGET")]
30        target: String,
31    },
32    /// Initialize the current directory as a shine presets directory
33    Init(InitCommand),
34    /// Manage shell command presets
35    Shell {
36        #[command(subcommand)]
37        command: ShellCommands,
38    },
39    /// Manage application configuration presets
40    App {
41        #[command(subcommand)]
42        command: AppCommands,
43    },
44    /// Install or repair one shell or app preset
45    Install {
46        /// Preset target: app/<category>, shell/<category>, or a unique category name
47        #[arg(value_name = "TARGET")]
48        target: String,
49        /// Replace user-modified files that are already managed by shine
50        #[arg(long)]
51        replace_managed: bool,
52    },
53    /// Uninstall one shell or app preset
54    Uninstall {
55        /// Preset target: app/<category>, shell/<category>, or a unique category name
56        #[arg(value_name = "TARGET")]
57        target: String,
58        /// Remove managed files even when they were modified after installation (app only)
59        #[arg(long)]
60        force: bool,
61        /// Also remove empty managed preset directories
62        #[arg(long)]
63        purge: bool,
64        /// Print what would be removed without changing anything
65        #[arg(long)]
66        dry_run: bool,
67    },
68    /// Generate or install shell completion scripts
69    Completions {
70        #[command(subcommand)]
71        command: CompletionCommands,
72    },
73    /// List installed resources, or browse available resources with --available
74    List {
75        /// List available resources instead of installed resources
76        #[arg(long)]
77        available: bool,
78        /// Limit --available output to app, shell, or sys resources
79        #[arg(value_enum, requires = "available", value_name = "KIND")]
80        kind: Option<ResourceKind>,
81    },
82    /// Show details for an available or installed app/shell target, or `sys/<ITEM>`
83    Info {
84        /// Installed item to inspect (e.g. git, starship, proxy, setproxy)
85        #[arg(value_name = "TARGET")]
86        target: String,
87        /// Also print a unified diff against the expected content
88        #[arg(long)]
89        diff: bool,
90        /// Also print the installed or rendered file content
91        #[arg(long)]
92        verbose: bool,
93    },
94    /// Manage preset sources, overlays, exports, and Git synchronization
95    Preset {
96        #[command(subcommand)]
97        command: PresetCommands,
98    },
99    /// Check managed configuration and shine release updates
100    Update(UpdateCommand),
101    /// Apply available managed configuration updates
102    Upgrade(UpgradeCommand),
103    /// Manage shine-owned runtime state
104    State {
105        #[command(subcommand)]
106        command: StateCommands,
107    },
108    /// Manage the shine binary itself
109    #[command(name = "self")]
110    Self_ {
111        #[command(subcommand)]
112        command: SelfCommands,
113    },
114    /// Serve shine-managed HTTP resources from ~/.shine/http
115    Serve {
116        #[command(subcommand)]
117        command: ServeCommands,
118    },
119    /// Manage preset variables and workspace command environments
120    Env {
121        #[command(subcommand)]
122        command: EnvCommands,
123    },
124    /// Manage system bootstrap and configuration for the current OS
125    Sys {
126        #[command(subcommand)]
127        command: SysCommands,
128    },
129    /// Resolve and sync the terminal's light/dark theme (see `shine theme sync`)
130    Theme {
131        #[command(subcommand)]
132        command: ThemeCommands,
133    },
134    /// Open an interactive SSH session with a session-scoped file transfer channel
135    Ssh {
136        /// Remote command shell (must appear before the SSH destination).
137        /// Windows mode injects environment variables only; `shine local` is unavailable.
138        #[arg(long, value_enum, default_value_t = RemoteShell::Posix)]
139        remote_shell: RemoteShell,
140        /// Inject a plaintext config [env] value as KEY or KEY=ALIAS (repeatable;
141        /// must appear before the SSH destination)
142        #[arg(long = "with", value_name = "KEY[=ALIAS]")]
143        with: Vec<String>,
144        /// Decrypt KEY_SECRET and inject it as KEY or ALIAS (repeatable; must
145        /// appear before the SSH destination)
146        #[arg(long = "with-secret", value_name = "KEY[=ALIAS]")]
147        with_secret: Vec<String>,
148        /// ssh options, the destination, and an optional remote command
149        /// (passed through to the system `ssh` binary; see `ssh(1)`)
150        #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
151        args: Vec<String>,
152    },
153    /// Transfer files between this machine and the other end of a `shine ssh` session
154    Local {
155        #[command(subcommand)]
156        command: LocalCommands,
157    },
158    /// Save, run, and manage personal shortcut commands
159    Task {
160        #[command(subcommand)]
161        command: TaskCommands,
162    },
163    /// Run a saved task (alias for `shine task run`)
164    Run(TaskRunCommand),
165}
166
167/// Shell used by the remote SSH server to interpret Shine's command wrapper.
168#[derive(Copy, Clone, Debug, Eq, PartialEq, ValueEnum)]
169pub enum RemoteShell {
170    /// POSIX shell with session-scoped `shine local` file transfer support.
171    Posix,
172    /// Windows PowerShell environment injection only; `shine local` is unavailable.
173    Windows,
174}
175
176#[derive(Copy, Clone, Debug, Eq, PartialEq, ValueEnum)]
177pub enum ResourceKind {
178    App,
179    Shell,
180    Sys,
181}
182
183#[derive(Args, Debug)]
184pub struct InitCommand {
185    /// Skip the confirmation prompt
186    #[arg(long)]
187    pub yes: bool,
188}
189
190#[derive(Copy, Clone, Debug, Eq, PartialEq, ValueEnum)]
191pub enum CompletionShell {
192    #[value(name = "bash")]
193    Bash,
194    #[value(name = "powershell")]
195    PowerShell,
196    #[value(name = "zsh")]
197    Zsh,
198}
199
200#[derive(Copy, Clone, Debug, Eq, PartialEq, Subcommand)]
201pub enum CompletionCommands {
202    /// Install completions into the managed shell profile without installing presets
203    Install,
204    /// Generate bash completion registration script
205    Bash,
206    /// Generate PowerShell completion registration script
207    #[command(name = "powershell")]
208    PowerShell,
209    /// Generate zsh completion registration script
210    Zsh,
211}
212
213impl CompletionCommands {
214    pub fn generate(self) {
215        match self {
216            CompletionCommands::Bash => completion::generate_registration(CompletionShell::Bash),
217            CompletionCommands::PowerShell => {
218                completion::generate_registration(CompletionShell::PowerShell)
219            }
220            CompletionCommands::Zsh => completion::generate_registration(CompletionShell::Zsh),
221            CompletionCommands::Install => unreachable!("install is handled by the async runtime"),
222        }
223    }
224}
225
226impl CompletionShell {
227    pub fn from_command(command: &CompletionCommands) -> Option<Self> {
228        match command {
229            CompletionCommands::Bash => Some(CompletionShell::Bash),
230            CompletionCommands::PowerShell => Some(CompletionShell::PowerShell),
231            CompletionCommands::Zsh => Some(CompletionShell::Zsh),
232            CompletionCommands::Install => None,
233        }
234    }
235
236    pub fn as_str(self) -> &'static str {
237        match self {
238            CompletionShell::Bash => "bash",
239            CompletionShell::PowerShell => "powershell",
240            CompletionShell::Zsh => "zsh",
241        }
242    }
243}
244
245#[derive(Parser, Debug)]
246pub struct UpdateCommand {
247    /// Installed shell or app target to inspect (shows pending content differences)
248    #[arg(value_name = "TARGET")]
249    pub target: Option<String>,
250    /// Pull Git-managed preset sources before checking status
251    #[arg(long)]
252    pub pull: bool,
253    /// Show content differences for available shell and app updates
254    #[arg(long)]
255    pub diff: bool,
256    /// Show installed entries that are already current or need attention
257    #[arg(long, conflicts_with = "target")]
258    pub verbose: bool,
259    /// Bypass the 24-hour version cache and check GitHub now
260    #[arg(long, conflicts_with = "target")]
261    pub refresh_release: bool,
262}
263
264#[derive(Parser, Debug)]
265pub struct UpgradeCommand {
266    /// Installed app, shell, or managed sys target to upgrade
267    #[arg(value_name = "TARGET")]
268    pub target: Option<String>,
269    /// Pull Git-managed preset sources before upgrading installed configs
270    #[arg(long)]
271    pub pull: bool,
272    /// Show detailed env-template checks and skipped rows
273    #[arg(long)]
274    pub verbose: bool,
275    /// Remove stale managed app files whose preset source no longer exists
276    #[arg(long)]
277    pub prune_stale: bool,
278}