Skip to main content

greentic_setup/
cli_args.rs

1//! CLI argument definitions for greentic-setup.
2
3use std::path::PathBuf;
4
5use clap::{Args, Parser, Subcommand};
6
7#[derive(Parser, Debug)]
8#[command(name = "greentic-setup")]
9#[command(version)]
10#[command(about = "Greentic bundle setup CLI")]
11#[command(after_help = r#"EXAMPLES:
12  Interactive wizard:
13    greentic-setup ./my-bundle
14
15  Preview without executing:
16    greentic-setup --dry-run ./my-bundle
17
18  Generate answers template:
19    greentic-setup --dry-run --emit-answers answers.json ./my-bundle
20
21  Apply answers file:
22    greentic-setup --answers answers.json ./my-bundle.gtbundle
23
24  Advanced (bundle subcommands):
25    greentic-setup bundle init ./my-bundle
26    greentic-setup bundle add pack.gtpack --bundle ./my-bundle
27    greentic-setup bundle status --bundle ./my-bundle
28"#)]
29pub struct Cli {
30    /// Bundle path (.gtbundle file or directory)
31    #[arg(value_name = "BUNDLE")]
32    pub bundle: Option<PathBuf>,
33
34    /// Dry run - show wizard but don't execute
35    #[arg(long = "dry-run", global = true)]
36    pub dry_run: bool,
37
38    /// Emit answers template to file (combine with --dry-run to only generate)
39    #[arg(long = "emit-answers", value_name = "FILE", global = true)]
40    pub emit_answers: Option<PathBuf>,
41
42    /// Apply answers from file
43    #[arg(long = "answers", short = 'a', value_name = "FILE", global = true)]
44    pub answers: Option<PathBuf>,
45
46    /// Encryption/decryption key for answer documents that include secrets
47    #[arg(long = "key", value_name = "KEY", global = true)]
48    pub key: Option<String>,
49
50    /// Tenant identifier
51    #[arg(long = "tenant", short = 't', default_value = "demo", global = true)]
52    pub tenant: String,
53
54    /// Team identifier
55    #[arg(long = "team", global = true)]
56    pub team: Option<String>,
57
58    /// Environment (defaults to `local`; legacy `dev` remapped via the A4b
59    /// compat alias with a once-per-process warning until removal).
60    #[arg(long = "env", short = 'e', default_value = "local", global = true)]
61    pub env: String,
62
63    /// UI locale (BCP-47 tag, e.g., en, ja, id)
64    #[arg(long = "locale", global = true)]
65    pub locale: Option<String>,
66
67    /// Advanced mode — show all questions including optional ones
68    #[arg(long = "advanced", global = true)]
69    pub advanced: bool,
70
71    /// Launch web-based setup UI in browser (enabled by default).
72    /// Use --no-ui to disable the UI; stdin prompts may still be used.
73    #[arg(long = "ui", global = true, default_value_t = true)]
74    pub ui: bool,
75
76    /// Disable web UI; stdin prompts may still be used.
77    #[arg(long = "no-ui", global = true)]
78    pub no_ui: bool,
79
80    /// Strict non-interactive mode: no prompts, fail if answers incomplete
81    #[arg(long = "non-interactive", global = true)]
82    pub non_interactive: bool,
83
84    #[command(subcommand)]
85    pub command: Option<Command>,
86}
87
88#[derive(Subcommand, Debug)]
89pub enum Command {
90    /// Diagnose bundle setup inputs and generated setup outputs
91    Doctor(DoctorArgs),
92    /// Bundle lifecycle management (advanced)
93    #[command(subcommand)]
94    Bundle(Box<BundleCommand>),
95}
96
97#[derive(Args, Debug, Clone)]
98pub struct DoctorArgs {
99    /// Bundle path (.gtbundle file or directory)
100    #[arg(value_name = "BUNDLE")]
101    pub bundle: Option<PathBuf>,
102    #[command(subcommand)]
103    pub command: Option<DoctorCommand>,
104    /// Emit stable machine-readable JSON
105    #[arg(long = "json")]
106    pub json: bool,
107    /// Treat warnings as command failures
108    #[arg(long = "strict")]
109    pub strict: bool,
110    /// Include fix hints in human-readable output
111    #[arg(long = "fix-hints")]
112    pub fix_hints: bool,
113    /// Show informational diagnostics in human-readable output
114    #[arg(long = "show-info")]
115    pub show_info: bool,
116    /// Limit checks to one stage
117    #[arg(long = "stage", value_enum)]
118    pub stage: Option<DoctorStageArg>,
119}
120
121#[derive(Subcommand, Debug, Clone)]
122pub enum DoctorCommand {
123    /// Validate a provider pack's setup contract
124    Provider(DoctorProviderArgs),
125}
126
127#[derive(Args, Debug, Clone)]
128pub struct DoctorProviderArgs {
129    /// Provider pack path (.gtpack)
130    #[arg(value_name = "PACK")]
131    pub pack: PathBuf,
132}
133
134#[derive(clap::ValueEnum, Debug, Clone, Copy, PartialEq, Eq)]
135pub enum DoctorStageArg {
136    Setup,
137    Cache,
138    Locks,
139    Answers,
140    Runtime,
141    Routes,
142}
143
144#[derive(Subcommand, Debug, Clone)]
145pub enum BundleCommand {
146    /// Initialize a new bundle directory
147    Init(BundleInitArgs),
148    /// Add a pack to a bundle
149    Add(BundleAddArgs),
150    /// Run setup flow for provider(s) in a bundle
151    Setup(BundleSetupArgs),
152    /// Update a provider's configuration in a bundle
153    Update(BundleSetupArgs),
154    /// Show persisted generic provider setup status
155    SetupStatus(BundleSetupStatusArgs),
156    /// Inspect and record the next generic provider setup step
157    SetupNext(BundleSetupNextArgs),
158    /// Clear retry-blocking state for a generic provider setup step
159    SetupRetry(BundleSetupRetryArgs),
160    /// Reset persisted generic provider setup state
161    SetupReset(BundleSetupResetArgs),
162    /// Migrate legacy provider setup state into the generic setup state layout
163    SetupMigrate(BundleSetupMigrateArgs),
164    /// Remove a provider from a bundle
165    Remove(BundleRemoveArgs),
166    /// Build a portable bundle (copy + resolve)
167    Build(BundleBuildArgs),
168    /// List packs or flows in a bundle
169    List(BundleListArgs),
170    /// Show bundle status
171    Status(BundleStatusArgs),
172}
173
174#[derive(Args, Debug, Clone)]
175pub struct BundleInitArgs {
176    /// Bundle directory (default: current directory)
177    #[arg(value_name = "PATH")]
178    pub path: Option<PathBuf>,
179    /// Bundle name
180    #[arg(long = "name", short = 'n')]
181    pub name: Option<String>,
182}
183
184#[derive(Args, Debug, Clone)]
185pub struct BundleAddArgs {
186    /// Pack reference (local path or OCI reference)
187    #[arg(value_name = "PACK_REF")]
188    pub pack_ref: String,
189    /// Bundle directory (default: current directory)
190    #[arg(long = "bundle", short = 'b')]
191    pub bundle: Option<PathBuf>,
192    /// Tenant identifier
193    #[arg(long = "tenant", short = 't', default_value = "demo")]
194    pub tenant: String,
195    /// Team identifier
196    #[arg(long = "team")]
197    pub team: Option<String>,
198    /// Environment (defaults to `local`; legacy `dev` remapped via the A4b
199    /// compat alias with a once-per-process warning until removal).
200    #[arg(long = "env", short = 'e', default_value = "local")]
201    pub env: String,
202    /// Dry run (don't actually add)
203    #[arg(long = "dry-run")]
204    pub dry_run: bool,
205}
206
207#[derive(Args, Debug, Clone)]
208pub struct BundleSetupArgs {
209    /// Provider ID to setup/update (optional, setup all if not specified)
210    #[arg(value_name = "PROVIDER_ID")]
211    pub provider_id: Option<String>,
212    /// Bundle directory (default: current directory)
213    #[arg(long = "bundle", short = 'b')]
214    pub bundle: Option<PathBuf>,
215    /// Answers file (JSON/YAML)
216    #[arg(long = "answers", short = 'a')]
217    pub answers: Option<PathBuf>,
218    /// Encryption/decryption key for answer documents that include secrets
219    #[arg(long = "key", value_name = "KEY")]
220    pub key: Option<String>,
221    /// Tenant identifier
222    #[arg(long = "tenant", short = 't', default_value = "demo")]
223    pub tenant: String,
224    /// Team identifier
225    #[arg(long = "team")]
226    pub team: Option<String>,
227    /// Environment (defaults to `local`; legacy `dev` remapped via the A4b
228    /// compat alias with a once-per-process warning until removal).
229    #[arg(long = "env", short = 'e', default_value = "local")]
230    pub env: String,
231    /// Filter by domain (messaging/events/secrets/oauth/all)
232    #[arg(long = "domain", short = 'd', default_value = "all")]
233    pub domain: String,
234    /// Number of parallel setup operations
235    #[arg(long = "parallel", default_value = "1")]
236    pub parallel: usize,
237    /// Backup existing config before setup
238    #[arg(long = "backup")]
239    pub backup: bool,
240    /// Skip secrets initialization
241    #[arg(long = "skip-secrets-init")]
242    pub skip_secrets_init: bool,
243    /// Continue on error (best effort)
244    #[arg(long = "best-effort")]
245    pub best_effort: bool,
246    /// Populated from the global --non-interactive flag before dispatch.
247    #[arg(skip)]
248    pub non_interactive: bool,
249    /// Dry run (plan only, don't execute)
250    #[arg(long = "dry-run")]
251    pub dry_run: bool,
252    /// Emit answers template JSON (use with --dry-run)
253    #[arg(long = "emit-answers")]
254    pub emit_answers: Option<PathBuf>,
255    /// Advanced mode — show all questions including optional ones
256    #[arg(long = "advanced")]
257    pub advanced: bool,
258}
259
260#[derive(Args, Debug, Clone)]
261pub struct BundleSetupStatusArgs {
262    /// Provider ID to inspect
263    #[arg(value_name = "PROVIDER_ID")]
264    pub provider_id: String,
265    /// Bundle directory (default: current directory)
266    #[arg(long = "bundle", short = 'b')]
267    pub bundle: Option<PathBuf>,
268    /// Tenant identifier
269    #[arg(long = "tenant", short = 't', default_value = "demo")]
270    pub tenant: String,
271    /// Team identifier
272    #[arg(long = "team")]
273    pub team: Option<String>,
274    /// Environment (dev/staging/prod)
275    #[arg(long = "env", short = 'e', default_value = "dev")]
276    pub env: String,
277    /// Output format: text or json
278    #[arg(long = "format", default_value = "text")]
279    pub format: String,
280}
281
282#[derive(Args, Debug, Clone)]
283pub struct BundleSetupNextArgs {
284    /// Provider ID to advance
285    #[arg(value_name = "PROVIDER_ID")]
286    pub provider_id: String,
287    /// Bundle directory (default: current directory)
288    #[arg(long = "bundle", short = 'b')]
289    pub bundle: Option<PathBuf>,
290    /// Tenant identifier
291    #[arg(long = "tenant", short = 't', default_value = "demo")]
292    pub tenant: String,
293    /// Team identifier
294    #[arg(long = "team")]
295    pub team: Option<String>,
296    /// Environment (dev/staging/prod)
297    #[arg(long = "env", short = 'e', default_value = "dev")]
298    pub env: String,
299    /// Output format: text or json
300    #[arg(long = "format", default_value = "text")]
301    pub format: String,
302    /// Only report the next action; do not write state or events
303    #[arg(long = "dry-run")]
304    pub dry_run: bool,
305}
306
307#[derive(Args, Debug, Clone)]
308pub struct BundleSetupRetryArgs {
309    /// Provider ID to retry
310    #[arg(value_name = "PROVIDER_ID")]
311    pub provider_id: String,
312    /// Bundle directory (default: current directory)
313    #[arg(long = "bundle", short = 'b')]
314    pub bundle: Option<PathBuf>,
315    /// Tenant identifier
316    #[arg(long = "tenant", short = 't', default_value = "demo")]
317    pub tenant: String,
318    /// Team identifier
319    #[arg(long = "team")]
320    pub team: Option<String>,
321    /// Environment (dev/staging/prod)
322    #[arg(long = "env", short = 'e', default_value = "dev")]
323    pub env: String,
324    /// Optional step to retry; defaults to the last recorded setup step
325    #[arg(long = "step")]
326    pub step: Option<String>,
327    /// Emit stable machine-readable JSON
328    #[arg(long = "json")]
329    pub json: bool,
330}
331
332#[derive(Args, Debug, Clone)]
333pub struct BundleSetupResetArgs {
334    /// Provider ID to reset
335    #[arg(value_name = "PROVIDER_ID")]
336    pub provider_id: String,
337    /// Bundle directory (default: current directory)
338    #[arg(long = "bundle", short = 'b')]
339    pub bundle: Option<PathBuf>,
340    /// Tenant identifier
341    #[arg(long = "tenant", short = 't', default_value = "demo")]
342    pub tenant: String,
343    /// Team identifier
344    #[arg(long = "team")]
345    pub team: Option<String>,
346    /// Confirm destructive reset of setup progress
347    #[arg(long = "yes")]
348    pub yes: bool,
349    /// Emit stable machine-readable JSON
350    #[arg(long = "json")]
351    pub json: bool,
352}
353
354#[derive(Args, Debug, Clone)]
355pub struct BundleSetupMigrateArgs {
356    /// Provider ID to migrate
357    #[arg(value_name = "PROVIDER_ID")]
358    pub provider_id: String,
359    /// Bundle directory (default: current directory)
360    #[arg(long = "bundle", short = 'b')]
361    pub bundle: Option<PathBuf>,
362    /// Tenant identifier
363    #[arg(long = "tenant", short = 't', default_value = "demo")]
364    pub tenant: String,
365    /// Team identifier
366    #[arg(long = "team")]
367    pub team: Option<String>,
368    /// Environment (dev/staging/prod)
369    #[arg(long = "env", short = 'e', default_value = "dev")]
370    pub env: String,
371    /// Emit stable machine-readable JSON
372    #[arg(long = "json")]
373    pub json: bool,
374}
375
376#[derive(Args, Debug, Clone)]
377pub struct BundleRemoveArgs {
378    /// Provider ID to remove
379    #[arg(value_name = "PROVIDER_ID")]
380    pub provider_id: String,
381    /// Bundle directory (default: current directory)
382    #[arg(long = "bundle", short = 'b')]
383    pub bundle: Option<PathBuf>,
384    /// Tenant identifier
385    #[arg(long = "tenant", short = 't', default_value = "demo")]
386    pub tenant: String,
387    /// Team identifier
388    #[arg(long = "team")]
389    pub team: Option<String>,
390    /// Force removal without confirmation
391    #[arg(long = "force", short = 'f')]
392    pub force: bool,
393}
394
395#[derive(Args, Debug, Clone)]
396pub struct BundleBuildArgs {
397    /// Bundle directory (default: current directory)
398    #[arg(long = "bundle", short = 'b')]
399    pub bundle: Option<PathBuf>,
400    /// Output directory for portable bundle
401    #[arg(long = "out", short = 'o')]
402    pub out: PathBuf,
403    /// Tenant identifier
404    #[arg(long = "tenant", short = 't')]
405    pub tenant: Option<String>,
406    /// Team identifier
407    #[arg(long = "team")]
408    pub team: Option<String>,
409    /// Only include used providers
410    #[arg(long = "only-used-providers")]
411    pub only_used_providers: bool,
412    /// Run doctor validation after build
413    #[arg(long = "doctor")]
414    pub doctor: bool,
415    /// Skip doctor validation
416    #[arg(long = "skip-doctor")]
417    pub skip_doctor: bool,
418}
419
420#[derive(Args, Debug, Clone)]
421pub struct BundleListArgs {
422    /// Bundle directory (default: current directory)
423    #[arg(long = "bundle", short = 'b')]
424    pub bundle: Option<PathBuf>,
425    /// Filter by domain (messaging/events/secrets/oauth)
426    #[arg(long = "domain", short = 'd', default_value = "messaging")]
427    pub domain: String,
428    /// Show flows for a specific pack
429    #[arg(long = "pack", short = 'p')]
430    pub pack: Option<String>,
431    /// Output format (text/json)
432    #[arg(long = "format", default_value = "text")]
433    pub format: String,
434}
435
436#[derive(Args, Debug, Clone)]
437pub struct BundleStatusArgs {
438    /// Bundle directory (default: current directory)
439    #[arg(long = "bundle", short = 'b')]
440    pub bundle: Option<PathBuf>,
441    /// Output format (text/json)
442    #[arg(long = "format", default_value = "text")]
443    pub format: String,
444}