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 ("bundle", "cli.command.bundle.about"),
48 ("runner", "cli.command.runner.about"),
49 ("config", "cli.command.config.about"),
50 ("coverage", "cli.command.coverage.about"),
51 ("mcp", "cli.command.mcp.about"),
52 ("gui", "cli.command.gui.about"),
53 ("secrets", "cli.command.secrets.about"),
54 ("tools", "cli.command.tools.about"),
55 ("install", "cli.command.install.about"),
56 ("cbor", "cli.command.cbor.about"),
57 ("wizard", "cli.command.wizard.about"),
58 ] {
59 command = command.mut_subcommand(name, |sub| sub.about(crate::i18n::t(locale, key)));
60 }
61
62 command = command.mut_subcommand("secrets", |sub| {
63 sub.about(crate::i18n::t(locale, "cli.command.secrets.about"))
64 .mut_subcommand("init", |sub| {
65 sub.about(crate::i18n::t(locale, "cli.command.secrets.init.about"))
66 .mut_arg("pack", |arg| {
67 arg.help(crate::i18n::t(locale, "cli.command.secrets.init.pack"))
68 })
69 .mut_arg("passthrough", |arg| {
70 arg.help(crate::i18n::t(
71 locale,
72 "cli.command.secrets.init.passthrough",
73 ))
74 })
75 })
76 });
77 command = command
78 .mut_subcommand("config", |sub| {
79 sub.about(crate::i18n::t(locale, "cli.command.config.about"))
80 .mut_subcommand("set", |sub| {
81 sub.about(crate::i18n::t(locale, "cli.command.config.set.about"))
82 .mut_arg("key", |arg| {
83 arg.help(crate::i18n::t(locale, "cli.command.config.set.key"))
84 })
85 .mut_arg("value", |arg| {
86 arg.help(crate::i18n::t(locale, "cli.command.config.set.value"))
87 })
88 .mut_arg("file", |arg| {
89 arg.help(crate::i18n::t(locale, "cli.command.config.set.file"))
90 })
91 })
92 })
93 .mut_subcommand("mcp", |sub| {
94 sub.about(crate::i18n::t(locale, "cli.command.mcp.about"))
95 .mut_subcommand("doctor", |sub| {
96 sub.about(crate::i18n::t(locale, "cli.command.mcp.doctor.about"))
97 .mut_arg("provider", |arg| {
98 arg.help(crate::i18n::t(locale, "cli.command.mcp.doctor.provider"))
99 })
100 .mut_arg("json", |arg| {
101 arg.help(crate::i18n::t(locale, "cli.command.mcp.doctor.json"))
102 })
103 })
104 })
105 .mut_subcommand("tools", |sub| {
106 sub.about(crate::i18n::t(locale, "cli.command.tools.about"))
107 .mut_subcommand("install", |sub| {
108 sub.about(crate::i18n::t(locale, "cli.command.tools.install.about"))
109 .mut_arg("latest", |arg| {
110 arg.help(crate::i18n::t(locale, "cli.command.tools.install.latest"))
111 })
112 })
113 })
114 .mut_subcommand("install", |sub| {
115 sub.about(crate::i18n::t(locale, "cli.command.install.about"))
116 .mut_subcommand("tools", |sub| {
117 sub.about(crate::i18n::t(locale, "cli.command.install.tools.about"))
118 .mut_arg("latest", |arg| {
119 arg.help(crate::i18n::t(locale, "cli.command.tools.install.latest"))
120 })
121 })
122 .mut_arg("tenant", |arg| {
123 arg.help(crate::i18n::t(locale, "cli.command.install.tenant"))
124 })
125 .mut_arg("token", |arg| {
126 arg.help(crate::i18n::t(locale, "cli.command.install.token"))
127 })
128 .mut_arg("bin_dir", |arg| {
129 arg.help(crate::i18n::t(locale, "cli.command.install.bin_dir"))
130 })
131 .mut_arg("docs_dir", |arg| {
132 arg.help(crate::i18n::t(locale, "cli.command.install.docs_dir"))
133 })
134 .mut_arg("locale", |arg| {
135 arg.help(crate::i18n::t(locale, "cli.command.install.locale"))
136 })
137 })
138 .mut_subcommand("cbor", |sub| {
139 sub.about(crate::i18n::t(locale, "cli.command.cbor.about"))
140 .mut_arg("path", |arg| {
141 arg.help(crate::i18n::t(locale, "cli.command.cbor.path"))
142 })
143 })
144 .mut_subcommand("coverage", |sub| {
145 sub.about(crate::i18n::t(locale, "cli.command.coverage.about"))
146 .mut_arg("skip_run", |arg| {
147 arg.help(crate::i18n::t(locale, "cli.command.coverage.skip_run"))
148 })
149 })
150 .mut_subcommand("wizard", |sub| {
151 sub.about(crate::i18n::t(locale, "cli.command.wizard.about"))
152 .mut_arg("answers", |arg| {
153 arg.help(crate::i18n::t(locale, "cli.command.wizard.answers"))
154 })
155 .mut_arg("frontend", |arg| {
156 arg.help(crate::i18n::t(locale, "cli.command.wizard.frontend"))
157 })
158 .mut_arg("locale", |arg| {
159 arg.help(crate::i18n::t(locale, "cli.command.wizard.locale"))
160 })
161 .mut_arg("emit_answers", |arg| {
162 arg.help(crate::i18n::t(locale, "cli.command.wizard.emit_answers"))
163 })
164 .mut_arg("schema", |arg| {
165 arg.help(crate::i18n::t(locale, "cli.command.wizard.schema"))
166 .long_help(crate::i18n::t(locale, "cli.command.wizard.schema_long"))
167 })
168 .mut_arg("schema_version", |arg| {
169 arg.help(crate::i18n::t(locale, "cli.command.wizard.schema_version"))
170 })
171 .mut_arg("migrate", |arg| {
172 arg.help(crate::i18n::t(locale, "cli.command.wizard.migrate"))
173 })
174 .mut_arg("out", |arg| {
175 arg.help(crate::i18n::t(locale, "cli.command.wizard.out"))
176 })
177 .mut_arg("dry_run", |arg| {
178 arg.help(crate::i18n::t(locale, "cli.command.wizard.dry_run"))
179 })
180 .mut_arg("yes", |arg| {
181 arg.help(crate::i18n::t(locale, "cli.command.wizard.yes"))
182 })
183 .mut_arg("non_interactive", |arg| {
184 arg.help(crate::i18n::t(locale, "cli.command.wizard.non_interactive"))
185 })
186 .mut_arg("unsafe_commands", |arg| {
187 arg.help(crate::i18n::t(locale, "cli.command.wizard.unsafe_commands"))
188 })
189 .mut_arg("allow_destructive", |arg| {
190 arg.help(crate::i18n::t(
191 locale,
192 "cli.command.wizard.allow_destructive",
193 ))
194 })
195 .mut_subcommand("validate", |sub| {
196 sub.about(crate::i18n::t(locale, "cli.command.wizard.validate.about"))
197 .mut_arg("answers", |arg| {
198 arg.help(crate::i18n::t(locale, "cli.command.wizard.answers"))
199 })
200 .mut_arg("frontend", |arg| {
201 arg.help(crate::i18n::t(locale, "cli.command.wizard.frontend"))
202 })
203 .mut_arg("locale", |arg| {
204 arg.help(crate::i18n::t(locale, "cli.command.wizard.locale"))
205 })
206 .mut_arg("emit_answers", |arg| {
207 arg.help(crate::i18n::t(locale, "cli.command.wizard.emit_answers"))
208 })
209 .mut_arg("schema_version", |arg| {
210 arg.help(crate::i18n::t(locale, "cli.command.wizard.schema_version"))
211 })
212 .mut_arg("migrate", |arg| {
213 arg.help(crate::i18n::t(locale, "cli.command.wizard.migrate"))
214 })
215 .mut_arg("out", |arg| {
216 arg.help(crate::i18n::t(locale, "cli.command.wizard.out"))
217 })
218 })
219 .mut_subcommand("apply", |sub| {
220 sub.about(crate::i18n::t(locale, "cli.command.wizard.apply.about"))
221 .mut_arg("answers", |arg| {
222 arg.help(crate::i18n::t(locale, "cli.command.wizard.answers"))
223 })
224 .mut_arg("frontend", |arg| {
225 arg.help(crate::i18n::t(locale, "cli.command.wizard.frontend"))
226 })
227 .mut_arg("locale", |arg| {
228 arg.help(crate::i18n::t(locale, "cli.command.wizard.locale"))
229 })
230 .mut_arg("emit_answers", |arg| {
231 arg.help(crate::i18n::t(locale, "cli.command.wizard.emit_answers"))
232 })
233 .mut_arg("schema_version", |arg| {
234 arg.help(crate::i18n::t(locale, "cli.command.wizard.schema_version"))
235 })
236 .mut_arg("migrate", |arg| {
237 arg.help(crate::i18n::t(locale, "cli.command.wizard.migrate"))
238 })
239 .mut_arg("out", |arg| {
240 arg.help(crate::i18n::t(locale, "cli.command.wizard.out"))
241 })
242 .mut_arg("yes", |arg| {
243 arg.help(crate::i18n::t(locale, "cli.command.wizard.yes"))
244 })
245 .mut_arg("non_interactive", |arg| {
246 arg.help(crate::i18n::t(locale, "cli.command.wizard.non_interactive"))
247 })
248 .mut_arg("unsafe_commands", |arg| {
249 arg.help(crate::i18n::t(locale, "cli.command.wizard.unsafe_commands"))
250 })
251 .mut_arg("allow_destructive", |arg| {
252 arg.help(crate::i18n::t(
253 locale,
254 "cli.command.wizard.allow_destructive",
255 ))
256 })
257 })
258 });
259
260 localize_help_tree(command, locale, true)
261}
262
263fn localize_help_tree(mut command: clap::Command, locale: &str, is_root: bool) -> clap::Command {
264 command = command
265 .disable_help_subcommand(true)
266 .disable_help_flag(true);
267 let arg_ids = command
268 .get_arguments()
269 .map(|arg| arg.get_id().as_str().to_string())
270 .collect::<Vec<_>>();
271 if arg_ids.iter().any(|id| id == "help") {
272 command = command.mut_arg("help", |arg| {
273 arg.help(crate::i18n::t(locale, "cli.help.flag"))
274 });
275 } else {
276 command = command.arg(
277 Arg::new("help")
278 .short('h')
279 .long("help")
280 .action(ArgAction::Help)
281 .help(crate::i18n::t(locale, "cli.help.flag")),
282 );
283 }
284 if is_root && arg_ids.iter().any(|id| id == "version") {
285 command = command.mut_arg("version", |arg| {
286 arg.help(crate::i18n::t(locale, "cli.version.flag"))
287 });
288 }
289
290 let sub_names = command
291 .get_subcommands()
292 .map(|sub| sub.get_name().to_string())
293 .collect::<Vec<_>>();
294 for name in sub_names {
295 command = command.mut_subcommand(name, |sub| localize_help_tree(sub, locale, false));
296 }
297
298 command
299}
300
301#[derive(Subcommand, Debug)]
302pub enum Command {
303 Flow(PassthroughArgs),
305 Pack(PassthroughArgs),
307 Component(PassthroughArgs),
309 Bundle(PassthroughArgs),
311 Runner(PassthroughArgs),
313 #[command(subcommand)]
315 Config(ConfigCommand),
316 Coverage(CoverageArgs),
318 #[command(subcommand)]
320 Mcp(McpCommand),
321 Gui(PassthroughArgs),
323 #[command(subcommand)]
325 Secrets(SecretsCommand),
326 #[command(subcommand)]
328 Tools(ToolsCommand),
329 Install(InstallArgs),
331 Cbor(CborArgs),
333 Wizard(Box<WizardCommand>),
335}
336
337#[derive(Args, Debug, Clone)]
338#[command(disable_help_flag = true)]
339pub struct PassthroughArgs {
340 #[arg(
342 value_name = "ARGS",
343 trailing_var_arg = true,
344 allow_hyphen_values = true
345 )]
346 pub args: Vec<OsString>,
347}
348
349#[derive(Subcommand, Debug)]
350pub enum McpCommand {
351 Doctor(McpDoctorArgs),
353}
354
355#[derive(Args, Debug)]
356pub struct McpDoctorArgs {
357 pub provider: String,
359 #[arg(long = "json")]
361 pub json: bool,
362}
363
364#[derive(Subcommand, Debug)]
365pub enum ConfigCommand {
366 Set(ConfigSetArgs),
368}
369
370#[derive(Subcommand, Debug)]
371pub enum ToolsCommand {
372 Install(ToolsInstallArgs),
374}
375
376#[derive(Subcommand, Debug)]
377pub enum InstallSubcommand {
378 Tools(ToolsInstallArgs),
380}
381
382#[derive(Args, Debug)]
383pub struct InstallArgs {
384 #[command(subcommand)]
385 pub command: Option<InstallSubcommand>,
386 #[arg(long = "tenant")]
388 pub tenant: Option<String>,
389 #[arg(long = "token")]
391 pub token: Option<String>,
392 #[arg(long = "bin-dir")]
394 pub bin_dir: Option<PathBuf>,
395 #[arg(long = "docs-dir")]
397 pub docs_dir: Option<PathBuf>,
398 #[arg(long = "locale")]
400 pub locale: Option<String>,
401}
402
403#[derive(Args, Debug)]
404pub struct ToolsInstallArgs {
405 #[arg(long = "latest")]
407 pub latest: bool,
408}
409
410#[derive(Args, Debug)]
411pub struct ConfigSetArgs {
412 pub key: String,
414 pub value: String,
416 #[arg(long = "file")]
418 pub file: Option<PathBuf>,
419}
420
421#[derive(Args, Debug)]
422pub struct CborArgs {
423 #[arg(value_name = "PATH")]
425 pub path: PathBuf,
426}
427
428#[derive(Args, Debug, Clone)]
429pub struct CoverageArgs {
430 #[arg(long = "skip-run")]
432 pub skip_run: bool,
433}
434
435#[derive(Args, Debug, Clone)]
436pub struct WizardCommand {
437 #[command(subcommand)]
438 pub command: Option<WizardSubcommand>,
439 #[command(flatten)]
440 pub launch: WizardLaunchArgs,
441}
442
443#[derive(Subcommand, Debug, Clone)]
444pub enum WizardSubcommand {
445 Validate(WizardValidateArgs),
447 Apply(WizardApplyArgs),
449}
450
451#[derive(Args, Debug, Clone)]
452pub struct WizardLaunchArgs {
453 #[arg(long = "answers")]
455 pub answers: Option<String>,
456 #[arg(long = "frontend", default_value = "json")]
458 pub frontend: String,
459 #[arg(long = "locale")]
461 pub locale: Option<String>,
462 #[arg(long = "emit-answers")]
464 pub emit_answers: Option<PathBuf>,
465 #[arg(long = "schema")]
467 pub schema: bool,
468 #[arg(long = "schema-version")]
470 pub schema_version: Option<String>,
471 #[arg(long = "migrate")]
473 pub migrate: bool,
474 #[arg(long = "out")]
476 pub out: Option<PathBuf>,
477 #[arg(long = "dry-run")]
479 pub dry_run: bool,
480 #[arg(long = "yes")]
482 pub yes: bool,
483 #[arg(long = "non-interactive")]
485 pub non_interactive: bool,
486 #[arg(long = "unsafe-commands")]
488 pub unsafe_commands: bool,
489 #[arg(long = "allow-destructive")]
491 pub allow_destructive: bool,
492}
493
494#[derive(Args, Debug, Clone)]
495pub struct WizardValidateArgs {
496 #[arg(long = "answers")]
498 pub answers: String,
499 #[arg(long = "frontend", default_value = "json")]
501 pub frontend: String,
502 #[arg(long = "locale")]
504 pub locale: Option<String>,
505 #[arg(long = "emit-answers")]
507 pub emit_answers: Option<PathBuf>,
508 #[arg(long = "schema-version")]
510 pub schema_version: Option<String>,
511 #[arg(long = "migrate")]
513 pub migrate: bool,
514 #[arg(long = "out")]
516 pub out: Option<PathBuf>,
517}
518
519#[derive(Args, Debug, Clone)]
520pub struct WizardApplyArgs {
521 #[arg(long = "answers")]
523 pub answers: String,
524 #[arg(long = "frontend", default_value = "json")]
526 pub frontend: String,
527 #[arg(long = "locale")]
529 pub locale: Option<String>,
530 #[arg(long = "emit-answers")]
532 pub emit_answers: Option<PathBuf>,
533 #[arg(long = "schema-version")]
535 pub schema_version: Option<String>,
536 #[arg(long = "migrate")]
538 pub migrate: bool,
539 #[arg(long = "out")]
541 pub out: Option<PathBuf>,
542 #[arg(long = "yes")]
544 pub yes: bool,
545 #[arg(long = "non-interactive")]
547 pub non_interactive: bool,
548 #[arg(long = "unsafe-commands")]
550 pub unsafe_commands: bool,
551 #[arg(long = "allow-destructive")]
553 pub allow_destructive: bool,
554}