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}