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