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        });
79
80    // Show existing packs
81    println!();
82    if discovered.providers.is_empty() {
83        println!("  No packs found in bundle.");
84    } else {
85        println!("  Found {} pack(s) in bundle:", discovered.providers.len());
86        for p in &discovered.providers {
87            println!("    - {} ({})", p.provider_id, p.domain);
88        }
89    }
90
91    // Add packs loop
92    println!();
93    println!("  Add packs to bundle");
94    println!("  Enter path to a .gtpack file or OCI reference, or press Enter to skip.");
95    println!("  Local:  ./messaging-telegram.gtpack  ../packs/state-redis.gtpack");
96    println!("  OCI:    oci://ghcr.io/greentic-ai-org/packs/mcp-github.gtpack:latest");
97    loop {
98        print!("  add pack> ");
99        io::stdout().flush()?;
100        let mut pack_input = String::new();
101        io::stdin().read_line(&mut pack_input)?;
102        let pack_str = pack_input.trim();
103        if pack_str.is_empty() {
104            break;
105        }
106
107        match resolve_pack_source(pack_str) {
108            Ok(pack_path) => {
109                let filename = pack_path
110                    .file_name()
111                    .and_then(|n| n.to_str())
112                    .unwrap_or("pack.gtpack");
113                let domain = detect_domain_from_filename(filename);
114
115                let target_dir = bundle_dir.join("providers").join(domain);
116                std::fs::create_dir_all(&target_dir)?;
117
118                let target = target_dir.join(filename);
119                std::fs::copy(&pack_path, &target)?;
120                println!("    Added {filename} -> providers/{domain}/");
121            }
122            Err(e) => {
123                println!("    Error: {e}");
124                continue;
125            }
126        }
127    }
128
129    // Tenant
130    let default_tenant = &cli.tenant;
131    println!();
132    println!("  Tenant (optional)");
133    println!("  Tenant identifier for multi-tenant isolation.");
134    print!("  > (default: {default_tenant}) ");
135    io::stdout().flush()?;
136    let mut tenant_input = String::new();
137    io::stdin().read_line(&mut tenant_input)?;
138    let tenant = if tenant_input.trim().is_empty() {
139        default_tenant.clone()
140    } else {
141        tenant_input.trim().to_string()
142    };
143
144    // Team
145    println!();
146    println!("  Team (optional)");
147    println!("  Team within the tenant. Leave blank for default.");
148    print!("  > ");
149    io::stdout().flush()?;
150    let mut team_input = String::new();
151    io::stdin().read_line(&mut team_input)?;
152    let team = if team_input.trim().is_empty() {
153        None
154    } else {
155        Some(team_input.trim().to_string())
156    };
157
158    // Env
159    let default_env = &cli.env;
160    println!();
161    println!("  Environment (optional)");
162    println!("  Deployment environment for secrets and configuration.");
163    print!("  > (default: {default_env}) ");
164    io::stdout().flush()?;
165    let mut env_input = String::new();
166    io::stdin().read_line(&mut env_input)?;
167    let env = if env_input.trim().is_empty() {
168        default_env.clone()
169    } else {
170        env_input.trim().to_string()
171    };
172
173    // Advanced mode
174    println!();
175    println!("  Advanced mode");
176    println!("  Show all configuration options including optional ones.");
177    print!("  > [y/N] ");
178    io::stdout().flush()?;
179    let mut adv_input = String::new();
180    io::stdin().read_line(&mut adv_input)?;
181    let advanced = matches!(
182        adv_input.trim().to_ascii_lowercase().as_str(),
183        "y" | "yes" | "true" | "1"
184    );
185
186    println!();
187
188    Ok(SetupParams {
189        bundle,
190        tenant,
191        team,
192        env,
193        advanced,
194    })
195}