Skip to main content

greentic_setup/cli_helpers/
prompts.rs

1//! Interactive prompts for setup parameters.
2//!
3//! Functions for prompting users to enter setup configuration interactively.
4
5use std::io::{self, Write as _};
6use std::path::PathBuf;
7
8use anyhow::Result;
9
10use crate::cli_args::Cli;
11use crate::cli_i18n::CliI18n;
12use crate::discovery;
13
14use super::bundle::resolve_bundle_source;
15use super::bundle::{detect_domain_from_filename, resolve_pack_source};
16
17/// Parameters collected from interactive prompts.
18pub struct SetupParams {
19    pub bundle: PathBuf,
20    pub tenant: String,
21    pub team: Option<String>,
22    pub env: String,
23    pub advanced: bool,
24}
25
26/// Prompt the user for setup parameters when no arguments are given.
27pub fn prompt_setup_params(cli: &Cli, i18n: &CliI18n) -> Result<SetupParams> {
28    println!();
29    println!("Greentic Setup");
30    println!("==============");
31    println!();
32    println!("Configure a bundle for deployment. A bundle is a directory or");
33    println!(".gtbundle archive containing provider packs and configuration.");
34    println!();
35
36    // Bundle name
37    println!("  Bundle name (required)");
38    println!("  A short name for this bundle (used as the directory name).");
39    println!("  Examples: my-demo  telecom-bot  customer-support");
40    print!("  > ");
41    io::stdout().flush()?;
42    let mut name_input = String::new();
43    io::stdin().read_line(&mut name_input)?;
44    let bundle_name = name_input.trim().to_string();
45    if bundle_name.is_empty() {
46        anyhow::bail!("Bundle name is required.");
47    }
48    println!();
49
50    // Bundle path
51    let default_path = format!("./{bundle_name}");
52    println!("  Bundle path");
53    println!("  Path to a bundle directory or .gtbundle file.");
54    println!("  Press Enter to use: {default_path}");
55    print!("  > ");
56    io::stdout().flush()?;
57    let mut bundle_input = String::new();
58    io::stdin().read_line(&mut bundle_input)?;
59    let bundle_str = bundle_input.trim();
60    let bundle = if bundle_str.is_empty() {
61        PathBuf::from(&default_path)
62    } else {
63        PathBuf::from(bundle_str)
64    };
65
66    // Resolve bundle and discover existing packs
67    let bundle_dir = resolve_bundle_source(&bundle, i18n)?;
68    let discovered =
69        discovery::discover(&bundle_dir).unwrap_or_else(|_| discovery::DiscoveryResult {
70            domains: discovery::DetectedDomains {
71                messaging: false,
72                events: false,
73                oauth: false,
74                state: false,
75                secrets: false,
76            },
77            providers: Vec::new(),
78            app_packs: Vec::new(),
79        });
80
81    // Show existing packs
82    println!();
83    let setup_targets = discovered.setup_targets();
84    if setup_targets.is_empty() {
85        println!("  No packs found in bundle.");
86    } else {
87        println!("  Found {} pack(s) in bundle:", setup_targets.len());
88        for p in setup_targets {
89            println!("    - {} ({})", p.provider_id, p.domain);
90        }
91    }
92
93    // Add packs loop
94    println!();
95    println!("  Add packs to bundle");
96    println!("  Enter path to a .gtpack file or OCI reference, or press Enter to skip.");
97    println!("  Local:  ./messaging-telegram.gtpack  ../packs/state-redis.gtpack");
98    println!("  OCI:    oci://ghcr.io/greentic-ai-org/packs/mcp-github.gtpack:latest");
99    loop {
100        print!("  add pack> ");
101        io::stdout().flush()?;
102        let mut pack_input = String::new();
103        io::stdin().read_line(&mut pack_input)?;
104        let pack_str = pack_input.trim();
105        if pack_str.is_empty() {
106            break;
107        }
108
109        match resolve_pack_source(pack_str) {
110            Ok(pack_path) => {
111                let filename = pack_path
112                    .file_name()
113                    .and_then(|n| n.to_str())
114                    .unwrap_or("pack.gtpack");
115                let domain = detect_domain_from_filename(filename);
116
117                let target_dir = bundle_dir.join("providers").join(domain);
118                std::fs::create_dir_all(&target_dir)?;
119
120                let target = target_dir.join(filename);
121                std::fs::copy(&pack_path, &target)?;
122                println!("    Added {filename} -> providers/{domain}/");
123            }
124            Err(e) => {
125                println!("    Error: {e}");
126                continue;
127            }
128        }
129    }
130
131    // Tenant
132    let default_tenant = &cli.tenant;
133    println!();
134    println!("  Tenant (optional)");
135    println!("  Tenant identifier for multi-tenant isolation.");
136    print!("  > (default: {default_tenant}) ");
137    io::stdout().flush()?;
138    let mut tenant_input = String::new();
139    io::stdin().read_line(&mut tenant_input)?;
140    let tenant = if tenant_input.trim().is_empty() {
141        default_tenant.clone()
142    } else {
143        tenant_input.trim().to_string()
144    };
145
146    // Team
147    println!();
148    println!("  Team (optional)");
149    println!("  Team within the tenant. Leave blank for default.");
150    print!("  > ");
151    io::stdout().flush()?;
152    let mut team_input = String::new();
153    io::stdin().read_line(&mut team_input)?;
154    let team = if team_input.trim().is_empty() {
155        None
156    } else {
157        Some(team_input.trim().to_string())
158    };
159
160    // Env
161    let default_env = &cli.env;
162    println!();
163    println!("  Environment (optional)");
164    println!("  Deployment environment for secrets and configuration.");
165    print!("  > (default: {default_env}) ");
166    io::stdout().flush()?;
167    let mut env_input = String::new();
168    io::stdin().read_line(&mut env_input)?;
169    let env = if env_input.trim().is_empty() {
170        default_env.clone()
171    } else {
172        env_input.trim().to_string()
173    };
174
175    // Advanced mode
176    println!();
177    println!("  Advanced mode");
178    println!("  Show all configuration options including optional ones.");
179    print!("  > [y/N] ");
180    io::stdout().flush()?;
181    let mut adv_input = String::new();
182    io::stdin().read_line(&mut adv_input)?;
183    let advanced = matches!(
184        adv_input.trim().to_ascii_lowercase().as_str(),
185        "y" | "yes" | "true" | "1"
186    );
187
188    println!();
189
190    Ok(SetupParams {
191        bundle,
192        tenant,
193        team,
194        env,
195        advanced,
196    })
197}