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