Skip to main content

cli/commands/
env.rs

1use clap::{Args, Subcommand};
2use std::{ffi::OsString, path::PathBuf};
3
4#[derive(Subcommand, Debug)]
5pub enum EnvCommands {
6    /// List all env variables
7    List {
8        /// Show sensitive values instead of redacting them
9        #[arg(long)]
10        reveal: bool,
11    },
12    /// Set a variable in config.toml [env]
13    Set {
14        /// Variable name (e.g. HTTP_PROXY_PORT)
15        key: String,
16        /// Variable value
17        value: String,
18        /// Write directly into the env override file that currently shadows this
19        /// key (global/overlay/project shine.env.toml) instead of refusing
20        #[arg(long)]
21        force: bool,
22    },
23    /// Delete a variable from config.toml [env]
24    Delete {
25        /// Variable name
26        key: String,
27        /// Delete directly from the env override file that currently shadows
28        /// this key (global/overlay/project shine.env.toml) instead of refusing
29        #[arg(long)]
30        force: bool,
31    },
32    /// Get a single variable value
33    Get {
34        /// Variable name
35        key: String,
36    },
37    /// Run a command with the workspace environment
38    Run(EnvRunCommand),
39    /// Create and manage workspace environment definitions
40    Workspace(EnvWorkspaceCommand),
41    /// Transparently proxy selected commands with explicitly injected values
42    Proxy(EnvProxyCommand),
43    /// Manage SSH secret-broker policies and describe workspace requests
44    Broker(EnvBrokerCommand),
45    /// Encrypt, decrypt, export, and manage secret identities
46    Secret(EnvSecretCommand),
47}
48
49#[derive(Args, Debug)]
50pub struct EnvWorkspaceCommand {
51    #[command(subcommand)]
52    pub command: EnvWorkspaceSubcommand,
53}
54
55#[derive(Subcommand, Debug)]
56pub enum EnvWorkspaceSubcommand {
57    /// Create a workspace from conventional dotenv files
58    Init(EnvWorkspaceInitCommand),
59}
60
61#[derive(Args, Debug)]
62pub struct EnvWorkspaceInitCommand {
63    /// Import .env, .env.local, .env.<mode>, and .env.<mode>.local files
64    #[arg(long)]
65    pub from_dotenv: bool,
66    /// Mode to import (repeatable); modes are discovered when omitted
67    #[arg(long, value_name = "MODE")]
68    pub mode: Vec<String>,
69    /// Import this key as an encrypted workspace secret (repeatable)
70    #[arg(long, value_name = "KEY")]
71    pub secret: Vec<String>,
72    /// Replace generated workspace files that already exist
73    #[arg(long)]
74    pub force: bool,
75    /// Print planned files without writing them
76    #[arg(long)]
77    pub dry_run: bool,
78}
79
80#[derive(Args, Debug)]
81pub struct EnvProxyCommand {
82    #[command(subcommand)]
83    pub command: EnvProxySubcommand,
84}
85
86#[derive(Args, Debug)]
87pub struct EnvBrokerCommand {
88    #[command(subcommand)]
89    pub command: EnvBrokerSubcommand,
90}
91
92#[derive(Subcommand, Debug)]
93pub enum EnvBrokerSubcommand {
94    /// Describe a workspace request without decrypting or running its command
95    Describe {
96        #[arg(long, value_name = "FILE")]
97        workspace: Option<PathBuf>,
98        #[arg(long)]
99        mode: String,
100        #[arg(
101            long,
102            value_name = "KEY",
103            required_unless_present = "release_all_declared",
104            conflicts_with = "release_all_declared"
105        )]
106        release: Vec<String>,
107        /// Release every secret declared by the selected source snapshot
108        #[arg(long)]
109        release_all_declared: bool,
110        #[arg(required = true, trailing_var_arg = true, allow_hyphen_values = true)]
111        command: Vec<String>,
112    },
113    /// Manage local SSH secret-broker authorization policies
114    Policy(EnvBrokerPolicyCommand),
115}
116
117#[derive(Args, Debug)]
118pub struct EnvBrokerPolicyCommand {
119    #[command(subcommand)]
120    pub command: EnvBrokerPolicySubcommand,
121}
122
123#[derive(Args, Debug)]
124pub struct EnvBrokerPolicyInput {
125    #[arg(long)]
126    pub name: String,
127    #[arg(long)]
128    pub ssh_target: String,
129    #[arg(long, default_value = "")]
130    pub project: String,
131    #[arg(long, value_name = "FILE")]
132    pub workspace: PathBuf,
133    /// Optionally require the remote workspace file to have this exact path
134    #[arg(long, value_name = "REMOTE_FILE")]
135    pub remote_workspace: Option<String>,
136    #[arg(long)]
137    pub mode: String,
138    #[arg(
139        long,
140        value_name = "KEY",
141        required_unless_present = "release_all_declared",
142        conflicts_with = "release_all_declared"
143    )]
144    pub release: Vec<String>,
145    /// Release every secret declared by the selected source snapshot
146    #[arg(long)]
147    pub release_all_declared: bool,
148    #[arg(required = true, trailing_var_arg = true, allow_hyphen_values = true)]
149    pub command: Vec<String>,
150}
151
152#[derive(Subcommand, Debug)]
153pub enum EnvBrokerPolicySubcommand {
154    /// Add a policy generated from a trusted local workspace checkout
155    Add(EnvBrokerPolicyInput),
156    /// Replace a policy from a trusted local workspace checkout
157    Update(EnvBrokerPolicyInput),
158    /// Show whether a trusted local workspace still matches a policy
159    Diff {
160        name: String,
161        #[arg(long, value_name = "FILE")]
162        workspace: PathBuf,
163        #[arg(long)]
164        mode: String,
165        #[arg(
166            long,
167            value_name = "KEY",
168            required_unless_present = "release_all_declared",
169            conflicts_with = "release_all_declared"
170        )]
171        release: Vec<String>,
172        /// Release every secret declared by the selected source snapshot
173        #[arg(long)]
174        release_all_declared: bool,
175        #[arg(required = true, trailing_var_arg = true, allow_hyphen_values = true)]
176        command: Vec<String>,
177    },
178    /// List configured policies
179    List,
180    /// Print one policy
181    Info { name: String },
182    /// Remove one policy
183    Remove { name: String },
184}
185
186#[derive(Subcommand, Debug)]
187pub enum EnvProxySubcommand {
188    /// Install a PATH shim and configure its allowed environment values
189    Install {
190        #[arg(value_name = "COMMAND")]
191        command: String,
192        #[arg(long = "with", value_name = "KEY[=ALIAS]", required = true)]
193        with: Vec<String>,
194        /// Store the rule in the current project's shine.config.toml
195        #[arg(long)]
196        project: bool,
197    },
198    /// List installed transparent command proxies
199    List,
200    /// Remove a shine-managed command proxy and its user-level rule
201    Uninstall {
202        #[arg(value_name = "COMMAND")]
203        command: String,
204    },
205    /// Enable secret injection for an installed command proxy
206    Enable {
207        #[arg(value_name = "COMMAND")]
208        command: String,
209        /// Change the rule in the current project's shine.config.toml
210        #[arg(long)]
211        project: bool,
212    },
213    /// Bypass secret injection while retaining the installed command proxy
214    Disable {
215        #[arg(value_name = "COMMAND")]
216        command: String,
217        /// Change the rule in the current project's shine.config.toml
218        #[arg(long)]
219        project: bool,
220    },
221    #[command(hide = true)]
222    Exec {
223        #[arg(long)]
224        target: PathBuf,
225        #[arg(value_name = "COMMAND")]
226        command: String,
227        #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
228        args: Vec<OsString>,
229    },
230}
231
232#[derive(Args, Debug)]
233pub struct EnvSecretCommand {
234    #[command(subcommand)]
235    pub command: EnvSecretSubcommand,
236}
237
238#[derive(Subcommand, Debug)]
239pub enum EnvSecretSubcommand {
240    /// Decode and decrypt an encrypted secret from [env] (GPG or age)
241    Decrypt {
242        /// Variable name containing encrypted ciphertext
243        key: String,
244    },
245    /// Decrypt KEY_SECRET and print shell code that exports KEY
246    Export {
247        /// Variable name to export from KEY_SECRET
248        key: String,
249        /// Export under a different name in the current shell
250        #[arg(long = "as", value_name = "ALIAS")]
251        alias: Option<String>,
252    },
253    /// Encrypt stdin and print ciphertext (GPG by default, or age with --backend age)
254    Encrypt(EnvEncryptCommand),
255    /// Seal pending secrets in workspace environment files
256    Seal(EnvSealCommand),
257    /// Manage age identities used to decrypt age-backed secrets
258    Identity(EnvIdentityCommand),
259}
260
261#[derive(Args, Debug)]
262pub struct EnvEncryptCommand {
263    /// Secret backend to use: "gpg" (default) or "age"
264    #[arg(long)]
265    pub backend: Option<String>,
266    /// Recipient (repeatable): GPG key ID/fingerprint/email, or age recipient
267    #[arg(short = 'r', long = "recipient")]
268    pub recipients: Vec<String>,
269    /// Store the encrypted ciphertext in config.toml [env] instead of printing it
270    #[arg(long)]
271    pub set: Option<String>,
272    /// Read plaintext from an existing config.toml [env] variable instead of stdin
273    #[arg(long)]
274    pub from: Option<String>,
275    /// Write directly into the env override file that currently shadows the
276    /// target key (global/overlay/project shine.env.toml) instead of refusing
277    #[arg(long)]
278    pub force: bool,
279}
280
281#[derive(Args, Debug)]
282pub struct EnvSealCommand {
283    /// Seal only this environment source file
284    #[arg(value_name = "FILE")]
285    pub file: Option<PathBuf>,
286    /// Workspace definition (defaults to the nearest shine.workspace.toml)
287    #[arg(long, value_name = "FILE")]
288    pub workspace: Option<PathBuf>,
289    /// Secret backend to use: "gpg" (default) or "age"
290    #[arg(long)]
291    pub backend: Option<String>,
292    /// Recipient (repeatable): GPG key ID/fingerprint/email, or age recipient
293    #[arg(short = 'r', long = "recipient")]
294    pub recipients: Vec<String>,
295}
296
297#[derive(Args, Debug)]
298pub struct EnvIdentityCommand {
299    #[command(subcommand)]
300    pub command: EnvIdentitySubcommand,
301}
302
303#[derive(Subcommand, Debug)]
304pub enum EnvIdentitySubcommand {
305    /// Generate a new age identity, optionally backed by Touch ID (Secure Enclave)
306    Init {
307        /// Generate a Secure Enclave identity requiring Touch ID (macOS only)
308        #[arg(long)]
309        touch_id: bool,
310        /// Secure Enclave access control policy (only with --touch-id): any-biometry
311        /// (default), any-biometry-or-passcode, current-biometry, or passcode
312        #[arg(long, value_name = "POLICY")]
313        access_control: Option<String>,
314        /// Output path (defaults to <shine_dir>/age/identity.txt)
315        #[arg(short = 'o', long, value_name = "PATH")]
316        output: Option<PathBuf>,
317        /// Overwrite an existing identity file
318        #[arg(long)]
319        force: bool,
320    },
321    /// Print the recipient(s) for the configured identity file(s)
322    List,
323}
324
325#[derive(Args, Debug)]
326pub struct EnvRunCommand {
327    /// Workspace definition (defaults to the nearest shine.workspace.toml)
328    #[arg(long, value_name = "FILE")]
329    pub workspace: Option<PathBuf>,
330    /// Environment mode used to expand {mode} paths
331    #[arg(long)]
332    pub mode: Option<String>,
333    /// Skip workspace discovery entirely; use only --with values and inherited env
334    #[arg(long, conflicts_with_all = ["workspace", "mode"])]
335    pub no_workspace: bool,
336    /// Inject a config [env] value as KEY or KEY=ALIAS (repeatable)
337    #[arg(long = "with", value_name = "KEY[=ALIAS]")]
338    pub with: Vec<String>,
339    /// Request secrets from the local end of the current shine ssh session
340    #[arg(long)]
341    pub secret_broker: bool,
342    /// Request one session-authorized encrypted key as KEY or KEY=ALIAS
343    #[arg(
344        long = "secret",
345        value_name = "KEY[=ALIAS]",
346        requires = "secret_broker",
347        requires = "no_workspace"
348    )]
349    pub secret: Vec<String>,
350    /// Command and arguments to run
351    #[arg(required = true, trailing_var_arg = true, allow_hyphen_values = true)]
352    pub command: Vec<OsString>,
353}