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("frontend", |arg| {
144 arg.help(crate::i18n::t(locale, "cli.command.wizard.frontend"))
145 })
146 .mut_arg("locale", |arg| {
147 arg.help(crate::i18n::t(locale, "cli.command.wizard.locale"))
148 })
149 .mut_arg("emit_answers", |arg| {
150 arg.help(crate::i18n::t(locale, "cli.command.wizard.emit_answers"))
151 })
152 .mut_arg("schema_version", |arg| {
153 arg.help(crate::i18n::t(locale, "cli.command.wizard.schema_version"))
154 })
155 .mut_arg("migrate", |arg| {
156 arg.help(crate::i18n::t(locale, "cli.command.wizard.migrate"))
157 })
158 .mut_arg("out", |arg| {
159 arg.help(crate::i18n::t(locale, "cli.command.wizard.out"))
160 })
161 .mut_arg("dry_run", |arg| {
162 arg.help(crate::i18n::t(locale, "cli.command.wizard.dry_run"))
163 })
164 .mut_arg("yes", |arg| {
165 arg.help(crate::i18n::t(locale, "cli.command.wizard.yes"))
166 })
167 .mut_arg("non_interactive", |arg| {
168 arg.help(crate::i18n::t(locale, "cli.command.wizard.non_interactive"))
169 })
170 .mut_arg("unsafe_commands", |arg| {
171 arg.help(crate::i18n::t(locale, "cli.command.wizard.unsafe_commands"))
172 })
173 .mut_arg("allow_destructive", |arg| {
174 arg.help(crate::i18n::t(
175 locale,
176 "cli.command.wizard.allow_destructive",
177 ))
178 })
179 .mut_subcommand("validate", |sub| {
180 sub.about(crate::i18n::t(locale, "cli.command.wizard.validate.about"))
181 .mut_arg("answers", |arg| {
182 arg.help(crate::i18n::t(locale, "cli.command.wizard.answers"))
183 })
184 .mut_arg("frontend", |arg| {
185 arg.help(crate::i18n::t(locale, "cli.command.wizard.frontend"))
186 })
187 .mut_arg("locale", |arg| {
188 arg.help(crate::i18n::t(locale, "cli.command.wizard.locale"))
189 })
190 .mut_arg("emit_answers", |arg| {
191 arg.help(crate::i18n::t(locale, "cli.command.wizard.emit_answers"))
192 })
193 .mut_arg("schema_version", |arg| {
194 arg.help(crate::i18n::t(locale, "cli.command.wizard.schema_version"))
195 })
196 .mut_arg("migrate", |arg| {
197 arg.help(crate::i18n::t(locale, "cli.command.wizard.migrate"))
198 })
199 .mut_arg("out", |arg| {
200 arg.help(crate::i18n::t(locale, "cli.command.wizard.out"))
201 })
202 })
203 .mut_subcommand("apply", |sub| {
204 sub.about(crate::i18n::t(locale, "cli.command.wizard.apply.about"))
205 .mut_arg("answers", |arg| {
206 arg.help(crate::i18n::t(locale, "cli.command.wizard.answers"))
207 })
208 .mut_arg("frontend", |arg| {
209 arg.help(crate::i18n::t(locale, "cli.command.wizard.frontend"))
210 })
211 .mut_arg("locale", |arg| {
212 arg.help(crate::i18n::t(locale, "cli.command.wizard.locale"))
213 })
214 .mut_arg("emit_answers", |arg| {
215 arg.help(crate::i18n::t(locale, "cli.command.wizard.emit_answers"))
216 })
217 .mut_arg("schema_version", |arg| {
218 arg.help(crate::i18n::t(locale, "cli.command.wizard.schema_version"))
219 })
220 .mut_arg("migrate", |arg| {
221 arg.help(crate::i18n::t(locale, "cli.command.wizard.migrate"))
222 })
223 .mut_arg("out", |arg| {
224 arg.help(crate::i18n::t(locale, "cli.command.wizard.out"))
225 })
226 .mut_arg("yes", |arg| {
227 arg.help(crate::i18n::t(locale, "cli.command.wizard.yes"))
228 })
229 .mut_arg("non_interactive", |arg| {
230 arg.help(crate::i18n::t(locale, "cli.command.wizard.non_interactive"))
231 })
232 .mut_arg("unsafe_commands", |arg| {
233 arg.help(crate::i18n::t(locale, "cli.command.wizard.unsafe_commands"))
234 })
235 .mut_arg("allow_destructive", |arg| {
236 arg.help(crate::i18n::t(
237 locale,
238 "cli.command.wizard.allow_destructive",
239 ))
240 })
241 })
242 });
243
244 localize_help_tree(command, locale, true)
245}
246
247fn localize_help_tree(mut command: clap::Command, locale: &str, is_root: bool) -> clap::Command {
248 command = command
249 .disable_help_subcommand(true)
250 .disable_help_flag(true);
251 let arg_ids = command
252 .get_arguments()
253 .map(|arg| arg.get_id().as_str().to_string())
254 .collect::<Vec<_>>();
255 if arg_ids.iter().any(|id| id == "help") {
256 command = command.mut_arg("help", |arg| {
257 arg.help(crate::i18n::t(locale, "cli.help.flag"))
258 });
259 } else {
260 command = command.arg(
261 Arg::new("help")
262 .short('h')
263 .long("help")
264 .action(ArgAction::Help)
265 .help(crate::i18n::t(locale, "cli.help.flag")),
266 );
267 }
268 if is_root && arg_ids.iter().any(|id| id == "version") {
269 command = command.mut_arg("version", |arg| {
270 arg.help(crate::i18n::t(locale, "cli.version.flag"))
271 });
272 }
273
274 let sub_names = command
275 .get_subcommands()
276 .map(|sub| sub.get_name().to_string())
277 .collect::<Vec<_>>();
278 for name in sub_names {
279 command = command.mut_subcommand(name, |sub| localize_help_tree(sub, locale, false));
280 }
281
282 command
283}
284
285#[derive(Subcommand, Debug)]
286pub enum Command {
287 Flow(PassthroughArgs),
289 Pack(PassthroughArgs),
291 Component(PassthroughArgs),
293 #[command(subcommand)]
295 Config(ConfigCommand),
296 #[command(subcommand)]
298 Mcp(McpCommand),
299 Gui(PassthroughArgs),
301 #[command(subcommand)]
303 Secrets(SecretsCommand),
304 #[command(subcommand)]
306 Tools(ToolsCommand),
307 Install(InstallArgs),
309 Cbor(CborArgs),
311 Wizard(Box<WizardCommand>),
313}
314
315#[derive(Args, Debug, Clone)]
316#[command(disable_help_flag = true)]
317pub struct PassthroughArgs {
318 #[arg(
320 value_name = "ARGS",
321 trailing_var_arg = true,
322 allow_hyphen_values = true
323 )]
324 pub args: Vec<OsString>,
325}
326
327#[derive(Subcommand, Debug)]
328pub enum McpCommand {
329 Doctor(McpDoctorArgs),
331}
332
333#[derive(Args, Debug)]
334pub struct McpDoctorArgs {
335 pub provider: String,
337 #[arg(long = "json")]
339 pub json: bool,
340}
341
342#[derive(Subcommand, Debug)]
343pub enum ConfigCommand {
344 Set(ConfigSetArgs),
346}
347
348#[derive(Subcommand, Debug)]
349pub enum ToolsCommand {
350 Install(ToolsInstallArgs),
352}
353
354#[derive(Subcommand, Debug)]
355pub enum InstallSubcommand {
356 Tools(ToolsInstallArgs),
358}
359
360#[derive(Args, Debug)]
361pub struct InstallArgs {
362 #[command(subcommand)]
363 pub command: Option<InstallSubcommand>,
364 #[arg(long = "tenant")]
366 pub tenant: Option<String>,
367 #[arg(long = "token")]
369 pub token: Option<String>,
370 #[arg(long = "bin-dir")]
372 pub bin_dir: Option<PathBuf>,
373 #[arg(long = "docs-dir")]
375 pub docs_dir: Option<PathBuf>,
376 #[arg(long = "locale")]
378 pub locale: Option<String>,
379}
380
381#[derive(Args, Debug)]
382pub struct ToolsInstallArgs {
383 #[arg(long = "latest")]
385 pub latest: bool,
386}
387
388#[derive(Args, Debug)]
389pub struct ConfigSetArgs {
390 pub key: String,
392 pub value: String,
394 #[arg(long = "file")]
396 pub file: Option<PathBuf>,
397}
398
399#[derive(Args, Debug)]
400pub struct CborArgs {
401 #[arg(value_name = "PATH")]
403 pub path: PathBuf,
404}
405
406#[derive(Args, Debug, Clone)]
407pub struct WizardCommand {
408 #[command(subcommand)]
409 pub command: Option<WizardSubcommand>,
410 #[command(flatten)]
411 pub launch: WizardLaunchArgs,
412}
413
414#[derive(Subcommand, Debug, Clone)]
415pub enum WizardSubcommand {
416 Validate(WizardValidateArgs),
418 Apply(WizardApplyArgs),
420}
421
422#[derive(Args, Debug, Clone)]
423pub struct WizardLaunchArgs {
424 #[arg(long = "frontend", default_value = "json")]
426 pub frontend: String,
427 #[arg(long = "locale")]
429 pub locale: Option<String>,
430 #[arg(long = "emit-answers")]
432 pub emit_answers: Option<PathBuf>,
433 #[arg(long = "schema-version")]
435 pub schema_version: Option<String>,
436 #[arg(long = "migrate")]
438 pub migrate: bool,
439 #[arg(long = "out")]
441 pub out: Option<PathBuf>,
442 #[arg(long = "dry-run")]
444 pub dry_run: bool,
445 #[arg(long = "yes")]
447 pub yes: bool,
448 #[arg(long = "non-interactive")]
450 pub non_interactive: bool,
451 #[arg(long = "unsafe-commands")]
453 pub unsafe_commands: bool,
454 #[arg(long = "allow-destructive")]
456 pub allow_destructive: bool,
457}
458
459#[derive(Args, Debug, Clone)]
460pub struct WizardValidateArgs {
461 #[arg(long = "answers")]
463 pub answers: PathBuf,
464 #[arg(long = "frontend", default_value = "json")]
466 pub frontend: String,
467 #[arg(long = "locale")]
469 pub locale: Option<String>,
470 #[arg(long = "emit-answers")]
472 pub emit_answers: Option<PathBuf>,
473 #[arg(long = "schema-version")]
475 pub schema_version: Option<String>,
476 #[arg(long = "migrate")]
478 pub migrate: bool,
479 #[arg(long = "out")]
481 pub out: Option<PathBuf>,
482}
483
484#[derive(Args, Debug, Clone)]
485pub struct WizardApplyArgs {
486 #[arg(long = "answers")]
488 pub answers: PathBuf,
489 #[arg(long = "frontend", default_value = "json")]
491 pub frontend: String,
492 #[arg(long = "locale")]
494 pub locale: Option<String>,
495 #[arg(long = "emit-answers")]
497 pub emit_answers: Option<PathBuf>,
498 #[arg(long = "schema-version")]
500 pub schema_version: Option<String>,
501 #[arg(long = "migrate")]
503 pub migrate: bool,
504 #[arg(long = "out")]
506 pub out: Option<PathBuf>,
507 #[arg(long = "yes")]
509 pub yes: bool,
510 #[arg(long = "non-interactive")]
512 pub non_interactive: bool,
513 #[arg(long = "unsafe-commands")]
515 pub unsafe_commands: bool,
516 #[arg(long = "allow-destructive")]
518 pub allow_destructive: bool,
519}