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 });
79
80 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 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 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 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 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 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}