1use std::{ffi::OsString, path::PathBuf};
2
3use crate::secrets_cli::SecretsCommand;
4use clap::{Arg, ArgAction, Args, CommandFactory, Parser, Subcommand};
5
6#[derive(Parser, Debug)]
7#[command(name = "greentic-dev")]
8#[command(version)]
9#[command(about = "cli.root.about")]
10pub struct Cli {
11 #[command(subcommand)]
12 pub command: Command,
13}
14
15pub fn localized_help_command(locale: &str) -> clap::Command {
16 let mut command = Cli::command()
17 .about(crate::i18n::t(locale, "cli.root.about"))
18 .disable_help_subcommand(true)
19 .disable_help_flag(true)
20 .disable_version_flag(true)
21 .arg(
22 Arg::new("help")
23 .short('h')
24 .long("help")
25 .action(ArgAction::Help)
26 .help(crate::i18n::t(locale, "cli.help.flag")),
27 )
28 .arg(
29 Arg::new("version")
30 .short('V')
31 .long("version")
32 .action(ArgAction::Version)
33 .help(crate::i18n::t(locale, "cli.version.flag")),
34 )
35 .arg(
36 Arg::new("locale")
37 .long("locale")
38 .global(true)
39 .value_name("LOCALE")
40 .help(crate::i18n::t(locale, "cli.option.locale")),
41 );
42
43 for (name, key) in [
44 ("flow", "cli.command.flow.about"),
45 ("pack", "cli.command.pack.about"),
46 ("component", "cli.command.component.about"),
47 ("config", "cli.command.config.about"),
48 ("mcp", "cli.command.mcp.about"),
49 ("gui", "cli.command.gui.about"),
50 ("secrets", "cli.command.secrets.about"),
51 ("tools", "cli.command.tools.about"),
52 ("install", "cli.command.install.about"),
53 ("cbor", "cli.command.cbor.about"),
54 ("wizard", "cli.command.wizard.about"),
55 ] {
56 command = command.mut_subcommand(name, |sub| sub.about(crate::i18n::t(locale, key)));
57 }
58
59 command = command.mut_subcommand("secrets", |sub| {
60 sub.about(crate::i18n::t(locale, "cli.command.secrets.about"))
61 .mut_subcommand("init", |sub| {
62 sub.about(crate::i18n::t(locale, "cli.command.secrets.init.about"))
63 .mut_arg("pack", |arg| {
64 arg.help(crate::i18n::t(locale, "cli.command.secrets.init.pack"))
65 })
66 .mut_arg("passthrough", |arg| {
67 arg.help(crate::i18n::t(
68 locale,
69 "cli.command.secrets.init.passthrough",
70 ))
71 })
72 })
73 });
74 command = command
75 .mut_subcommand("config", |sub| {
76 sub.about(crate::i18n::t(locale, "cli.command.config.about"))
77 .mut_subcommand("set", |sub| {
78 sub.about(crate::i18n::t(locale, "cli.command.config.set.about"))
79 .mut_arg("key", |arg| {
80 arg.help(crate::i18n::t(locale, "cli.command.config.set.key"))
81 })
82 .mut_arg("value", |arg| {
83 arg.help(crate::i18n::t(locale, "cli.command.config.set.value"))
84 })
85 .mut_arg("file", |arg| {
86 arg.help(crate::i18n::t(locale, "cli.command.config.set.file"))
87 })
88 })
89 })
90 .mut_subcommand("mcp", |sub| {
91 sub.about(crate::i18n::t(locale, "cli.command.mcp.about"))
92 .mut_subcommand("doctor", |sub| {
93 sub.about(crate::i18n::t(locale, "cli.command.mcp.doctor.about"))
94 .mut_arg("provider", |arg| {
95 arg.help(crate::i18n::t(locale, "cli.command.mcp.doctor.provider"))
96 })
97 .mut_arg("json", |arg| {
98 arg.help(crate::i18n::t(locale, "cli.command.mcp.doctor.json"))
99 })
100 })
101 })
102 .mut_subcommand("tools", |sub| {
103 sub.about(crate::i18n::t(locale, "cli.command.tools.about"))
104 .mut_subcommand("install", |sub| {
105 sub.about(crate::i18n::t(locale, "cli.command.tools.install.about"))
106 .mut_arg("latest", |arg| {
107 arg.help(crate::i18n::t(locale, "cli.command.tools.install.latest"))
108 })
109 })
110 })
111 .mut_subcommand("install", |sub| {
112 sub.about(crate::i18n::t(locale, "cli.command.install.about"))
113 .mut_subcommand("tools", |sub| {
114 sub.about(crate::i18n::t(locale, "cli.command.install.tools.about"))
115 .mut_arg("latest", |arg| {
116 arg.help(crate::i18n::t(locale, "cli.command.tools.install.latest"))
117 })
118 })
119 .mut_arg("tenant", |arg| {
120 arg.help(crate::i18n::t(locale, "cli.command.install.tenant"))
121 })
122 .mut_arg("token", |arg| {
123 arg.help(crate::i18n::t(locale, "cli.command.install.token"))
124 })
125 .mut_arg("bin_dir", |arg| {
126 arg.help(crate::i18n::t(locale, "cli.command.install.bin_dir"))
127 })
128 .mut_arg("docs_dir", |arg| {
129 arg.help(crate::i18n::t(locale, "cli.command.install.docs_dir"))
130 })
131 .mut_arg("locale", |arg| {
132 arg.help(crate::i18n::t(locale, "cli.command.install.locale"))
133 })
134 })
135 .mut_subcommand("cbor", |sub| {
136 sub.about(crate::i18n::t(locale, "cli.command.cbor.about"))
137 .mut_arg("path", |arg| {
138 arg.help(crate::i18n::t(locale, "cli.command.cbor.path"))
139 })
140 })
141 .mut_subcommand("wizard", |sub| {
142 sub.about(crate::i18n::t(locale, "cli.command.wizard.about"))
143 .mut_arg("answers", |arg| {
144 arg.help(crate::i18n::t(locale, "cli.command.wizard.answers"))
145 })
146 .mut_arg("frontend", |arg| {
147 arg.help(crate::i18n::t(locale, "cli.command.wizard.frontend"))
148 })
149 .mut_arg("locale", |arg| {
150 arg.help(crate::i18n::t(locale, "cli.command.wizard.locale"))
151 })
152 .mut_arg("emit_answers", |arg| {
153 arg.help(crate::i18n::t(locale, "cli.command.wizard.emit_answers"))
154 })
155 .mut_arg("schema_version", |arg| {
156 arg.help(crate::i18n::t(locale, "cli.command.wizard.schema_version"))
157 })
158 .mut_arg("migrate", |arg| {
159 arg.help(crate::i18n::t(locale, "cli.command.wizard.migrate"))
160 })
161 .mut_arg("out", |arg| {
162 arg.help(crate::i18n::t(locale, "cli.command.wizard.out"))
163 })
164 .mut_arg("dry_run", |arg| {
165 arg.help(crate::i18n::t(locale, "cli.command.wizard.dry_run"))
166 })
167 .mut_arg("yes", |arg| {
168 arg.help(crate::i18n::t(locale, "cli.command.wizard.yes"))
169 })
170 .mut_arg("non_interactive", |arg| {
171 arg.help(crate::i18n::t(locale, "cli.command.wizard.non_interactive"))
172 })
173 .mut_arg("unsafe_commands", |arg| {
174 arg.help(crate::i18n::t(locale, "cli.command.wizard.unsafe_commands"))
175 })
176 .mut_arg("allow_destructive", |arg| {
177 arg.help(crate::i18n::t(
178 locale,
179 "cli.command.wizard.allow_destructive",
180 ))
181 })
182 .mut_subcommand("validate", |sub| {
183 sub.about(crate::i18n::t(locale, "cli.command.wizard.validate.about"))
184 .mut_arg("answers", |arg| {
185 arg.help(crate::i18n::t(locale, "cli.command.wizard.answers"))
186 })
187 .mut_arg("frontend", |arg| {
188 arg.help(crate::i18n::t(locale, "cli.command.wizard.frontend"))
189 })
190 .mut_arg("locale", |arg| {
191 arg.help(crate::i18n::t(locale, "cli.command.wizard.locale"))
192 })
193 .mut_arg("emit_answers", |arg| {
194 arg.help(crate::i18n::t(locale, "cli.command.wizard.emit_answers"))
195 })
196 .mut_arg("schema_version", |arg| {
197 arg.help(crate::i18n::t(locale, "cli.command.wizard.schema_version"))
198 })
199 .mut_arg("migrate", |arg| {
200 arg.help(crate::i18n::t(locale, "cli.command.wizard.migrate"))
201 })
202 .mut_arg("out", |arg| {
203 arg.help(crate::i18n::t(locale, "cli.command.wizard.out"))
204 })
205 })
206 .mut_subcommand("apply", |sub| {
207 sub.about(crate::i18n::t(locale, "cli.command.wizard.apply.about"))
208 .mut_arg("answers", |arg| {
209 arg.help(crate::i18n::t(locale, "cli.command.wizard.answers"))
210 })
211 .mut_arg("frontend", |arg| {
212 arg.help(crate::i18n::t(locale, "cli.command.wizard.frontend"))
213 })
214 .mut_arg("locale", |arg| {
215 arg.help(crate::i18n::t(locale, "cli.command.wizard.locale"))
216 })
217 .mut_arg("emit_answers", |arg| {
218 arg.help(crate::i18n::t(locale, "cli.command.wizard.emit_answers"))
219 })
220 .mut_arg("schema_version", |arg| {
221 arg.help(crate::i18n::t(locale, "cli.command.wizard.schema_version"))
222 })
223 .mut_arg("migrate", |arg| {
224 arg.help(crate::i18n::t(locale, "cli.command.wizard.migrate"))
225 })
226 .mut_arg("out", |arg| {
227 arg.help(crate::i18n::t(locale, "cli.command.wizard.out"))
228 })
229 .mut_arg("yes", |arg| {
230 arg.help(crate::i18n::t(locale, "cli.command.wizard.yes"))
231 })
232 .mut_arg("non_interactive", |arg| {
233 arg.help(crate::i18n::t(locale, "cli.command.wizard.non_interactive"))
234 })
235 .mut_arg("unsafe_commands", |arg| {
236 arg.help(crate::i18n::t(locale, "cli.command.wizard.unsafe_commands"))
237 })
238 .mut_arg("allow_destructive", |arg| {
239 arg.help(crate::i18n::t(
240 locale,
241 "cli.command.wizard.allow_destructive",
242 ))
243 })
244 })
245 });
246
247 localize_help_tree(command, locale, true)
248}
249
250fn localize_help_tree(mut command: clap::Command, locale: &str, is_root: bool) -> clap::Command {
251 command = command
252 .disable_help_subcommand(true)
253 .disable_help_flag(true);
254 let arg_ids = command
255 .get_arguments()
256 .map(|arg| arg.get_id().as_str().to_string())
257 .collect::<Vec<_>>();
258 if arg_ids.iter().any(|id| id == "help") {
259 command = command.mut_arg("help", |arg| {
260 arg.help(crate::i18n::t(locale, "cli.help.flag"))
261 });
262 } else {
263 command = command.arg(
264 Arg::new("help")
265 .short('h')
266 .long("help")
267 .action(ArgAction::Help)
268 .help(crate::i18n::t(locale, "cli.help.flag")),
269 );
270 }
271 if is_root && arg_ids.iter().any(|id| id == "version") {
272 command = command.mut_arg("version", |arg| {
273 arg.help(crate::i18n::t(locale, "cli.version.flag"))
274 });
275 }
276
277 let sub_names = command
278 .get_subcommands()
279 .map(|sub| sub.get_name().to_string())
280 .collect::<Vec<_>>();
281 for name in sub_names {
282 command = command.mut_subcommand(name, |sub| localize_help_tree(sub, locale, false));
283 }
284
285 command
286}
287
288#[derive(Subcommand, Debug)]
289pub enum Command {
290 Flow(PassthroughArgs),
292 Pack(PassthroughArgs),
294 Component(PassthroughArgs),
296 #[command(subcommand)]
298 Config(ConfigCommand),
299 #[command(subcommand)]
301 Mcp(McpCommand),
302 Gui(PassthroughArgs),
304 #[command(subcommand)]
306 Secrets(SecretsCommand),
307 #[command(subcommand)]
309 Tools(ToolsCommand),
310 Install(InstallArgs),
312 Cbor(CborArgs),
314 Wizard(Box<WizardCommand>),
316}
317
318#[derive(Args, Debug, Clone)]
319#[command(disable_help_flag = true)]
320pub struct PassthroughArgs {
321 #[arg(
323 value_name = "ARGS",
324 trailing_var_arg = true,
325 allow_hyphen_values = true
326 )]
327 pub args: Vec<OsString>,
328}
329
330#[derive(Subcommand, Debug)]
331pub enum McpCommand {
332 Doctor(McpDoctorArgs),
334}
335
336#[derive(Args, Debug)]
337pub struct McpDoctorArgs {
338 pub provider: String,
340 #[arg(long = "json")]
342 pub json: bool,
343}
344
345#[derive(Subcommand, Debug)]
346pub enum ConfigCommand {
347 Set(ConfigSetArgs),
349}
350
351#[derive(Subcommand, Debug)]
352pub enum ToolsCommand {
353 Install(ToolsInstallArgs),
355}
356
357#[derive(Subcommand, Debug)]
358pub enum InstallSubcommand {
359 Tools(ToolsInstallArgs),
361}
362
363#[derive(Args, Debug)]
364pub struct InstallArgs {
365 #[command(subcommand)]
366 pub command: Option<InstallSubcommand>,
367 #[arg(long = "tenant")]
369 pub tenant: Option<String>,
370 #[arg(long = "token")]
372 pub token: Option<String>,
373 #[arg(long = "bin-dir")]
375 pub bin_dir: Option<PathBuf>,
376 #[arg(long = "docs-dir")]
378 pub docs_dir: Option<PathBuf>,
379 #[arg(long = "locale")]
381 pub locale: Option<String>,
382}
383
384#[derive(Args, Debug)]
385pub struct ToolsInstallArgs {
386 #[arg(long = "latest")]
388 pub latest: bool,
389}
390
391#[derive(Args, Debug)]
392pub struct ConfigSetArgs {
393 pub key: String,
395 pub value: String,
397 #[arg(long = "file")]
399 pub file: Option<PathBuf>,
400}
401
402#[derive(Args, Debug)]
403pub struct CborArgs {
404 #[arg(value_name = "PATH")]
406 pub path: PathBuf,
407}
408
409#[derive(Args, Debug, Clone)]
410pub struct WizardCommand {
411 #[command(subcommand)]
412 pub command: Option<WizardSubcommand>,
413 #[command(flatten)]
414 pub launch: WizardLaunchArgs,
415}
416
417#[derive(Subcommand, Debug, Clone)]
418pub enum WizardSubcommand {
419 Validate(WizardValidateArgs),
421 Apply(WizardApplyArgs),
423}
424
425#[derive(Args, Debug, Clone)]
426pub struct WizardLaunchArgs {
427 #[arg(long = "answers")]
429 pub answers: Option<PathBuf>,
430 #[arg(long = "frontend", default_value = "json")]
432 pub frontend: String,
433 #[arg(long = "locale")]
435 pub locale: Option<String>,
436 #[arg(long = "emit-answers")]
438 pub emit_answers: Option<PathBuf>,
439 #[arg(long = "schema-version")]
441 pub schema_version: Option<String>,
442 #[arg(long = "migrate")]
444 pub migrate: bool,
445 #[arg(long = "out")]
447 pub out: Option<PathBuf>,
448 #[arg(long = "dry-run")]
450 pub dry_run: bool,
451 #[arg(long = "yes")]
453 pub yes: bool,
454 #[arg(long = "non-interactive")]
456 pub non_interactive: bool,
457 #[arg(long = "unsafe-commands")]
459 pub unsafe_commands: bool,
460 #[arg(long = "allow-destructive")]
462 pub allow_destructive: bool,
463}
464
465#[derive(Args, Debug, Clone)]
466pub struct WizardValidateArgs {
467 #[arg(long = "answers")]
469 pub answers: PathBuf,
470 #[arg(long = "frontend", default_value = "json")]
472 pub frontend: String,
473 #[arg(long = "locale")]
475 pub locale: Option<String>,
476 #[arg(long = "emit-answers")]
478 pub emit_answers: Option<PathBuf>,
479 #[arg(long = "schema-version")]
481 pub schema_version: Option<String>,
482 #[arg(long = "migrate")]
484 pub migrate: bool,
485 #[arg(long = "out")]
487 pub out: Option<PathBuf>,
488}
489
490#[derive(Args, Debug, Clone)]
491pub struct WizardApplyArgs {
492 #[arg(long = "answers")]
494 pub answers: PathBuf,
495 #[arg(long = "frontend", default_value = "json")]
497 pub frontend: String,
498 #[arg(long = "locale")]
500 pub locale: Option<String>,
501 #[arg(long = "emit-answers")]
503 pub emit_answers: Option<PathBuf>,
504 #[arg(long = "schema-version")]
506 pub schema_version: Option<String>,
507 #[arg(long = "migrate")]
509 pub migrate: bool,
510 #[arg(long = "out")]
512 pub out: Option<PathBuf>,
513 #[arg(long = "yes")]
515 pub yes: bool,
516 #[arg(long = "non-interactive")]
518 pub non_interactive: bool,
519 #[arg(long = "unsafe-commands")]
521 pub unsafe_commands: bool,
522 #[arg(long = "allow-destructive")]
524 pub allow_destructive: bool,
525}