greentic_setup/cli_helpers/
prompts.rs1use 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
17pub 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
26pub 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 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 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 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 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 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 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 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 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 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}