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