Skip to main content

cli/commands/
cli.rs

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