Skip to main content

usage/spec/
cmd.rs

1use std::collections::HashMap;
2use std::sync::OnceLock;
3
4use crate::error::UsageErr;
5use crate::sh::sh;
6use crate::spec::builder::SpecCommandBuilder;
7use crate::spec::context::ParsingContext;
8use crate::spec::helpers::{string_entry, NodeHelper};
9use crate::spec::is_false;
10use crate::spec::mount::SpecMount;
11use crate::{Spec, SpecArg, SpecComplete, SpecFlag};
12use indexmap::IndexMap;
13use itertools::Itertools;
14use kdl::{KdlDocument, KdlEntry, KdlNode};
15use serde::Serialize;
16
17/// A CLI command or subcommand specification.
18///
19/// Commands define the structure of a CLI, including their flags, arguments,
20/// and nested subcommands. The root command represents the main CLI entry point.
21///
22/// # Example
23///
24/// ```
25/// use usage::{SpecCommand, SpecFlag, SpecArg};
26///
27/// let cmd = SpecCommand::builder()
28///     .name("install")
29///     .help("Install a package")
30///     .alias("i")
31///     .flag(SpecFlag::builder().short('f').long("force").build())
32///     .arg(SpecArg::builder().name("package").required(true).build())
33///     .build();
34/// ```
35#[derive(Debug, Serialize, Clone)]
36pub struct SpecCommand {
37    /// Full command path from root (e.g., ["git", "remote", "add"])
38    pub full_cmd: Vec<String>,
39    /// Generated usage string
40    pub usage: String,
41    /// Nested subcommands indexed by name
42    pub subcommands: IndexMap<String, SpecCommand>,
43    /// Positional arguments for this command
44    pub args: Vec<SpecArg>,
45    /// Flags/options for this command
46    pub flags: Vec<SpecFlag>,
47    /// Mounted external specs
48    pub mounts: Vec<SpecMount>,
49    /// Deprecation message if this command is deprecated
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub deprecated: Option<String>,
52    /// Whether to hide this command from help output
53    pub hide: bool,
54    /// True when this command came from a [`SpecMount`], i.e. it describes another
55    /// program's CLI that was merged in at parse time.
56    ///
57    /// The flags of the commands *above* a mounted command belong to the mounting CLI,
58    /// not to the mounted program, so they are not offered in completions once a mounted
59    /// command has been reached. They stay recognized by the parser, since they may
60    /// legitimately appear *before* the mounted command on the command line.
61    ///
62    /// Runtime-only: it is derived from `mount` nodes and is not part of the spec syntax.
63    #[serde(skip)]
64    pub mounted: bool,
65    /// True when a [`SpecMount`] brought flags of its own onto this command. A mounted spec's
66    /// root flags are merged into the command the mount sits on, *replacing* that command's
67    /// flags (see [`SpecCommand::merge`]), so when this is set every flag here describes the
68    /// mounted program and is offered inside the mounted commands accordingly.
69    ///
70    /// Runtime-only, like [`SpecCommand::mounted`].
71    #[serde(skip)]
72    pub flags_from_mount: bool,
73    /// Whether a subcommand must be provided
74    #[serde(skip_serializing_if = "is_false")]
75    pub subcommand_required: bool,
76    /// Token that resets argument parsing, allowing multiple command invocations.
77    /// e.g., `mise run lint ::: test ::: check` with restart_token=":::"
78    #[serde(skip_serializing_if = "Option::is_none")]
79    pub restart_token: Option<String>,
80    /// Short help text shown in command listings
81    #[serde(skip_serializing_if = "Option::is_none")]
82    pub help: Option<String>,
83    /// Extended help text shown with --help
84    #[serde(skip_serializing_if = "Option::is_none")]
85    pub help_long: Option<String>,
86    /// Markdown-formatted help text
87    #[serde(skip_serializing_if = "Option::is_none")]
88    pub help_md: Option<String>,
89    /// Command name (e.g., "install")
90    pub name: String,
91    /// Alternative names for this command
92    pub aliases: Vec<String>,
93    /// Hidden alternative names (not shown in help)
94    pub hidden_aliases: Vec<String>,
95    /// Text displayed before the help content
96    #[serde(skip_serializing_if = "Option::is_none")]
97    pub before_help: Option<String>,
98    /// Extended text displayed before help content
99    #[serde(skip_serializing_if = "Option::is_none")]
100    pub before_help_long: Option<String>,
101    /// Markdown text displayed before help content
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub before_help_md: Option<String>,
104    /// Text displayed after the help content
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub after_help: Option<String>,
107    /// Extended text displayed after help content
108    #[serde(skip_serializing_if = "Option::is_none")]
109    pub after_help_long: Option<String>,
110    /// Markdown text displayed after help content
111    #[serde(skip_serializing_if = "Option::is_none")]
112    pub after_help_md: Option<String>,
113    /// Usage examples for this command
114    pub examples: Vec<SpecExample>,
115    /// Custom completers for arguments
116    #[serde(skip_serializing_if = "IndexMap::is_empty")]
117    pub complete: IndexMap<String, SpecComplete>,
118
119    /// Cache for subcommand name lookups (including aliases)
120    #[serde(skip)]
121    subcommand_lookup: OnceLock<HashMap<String, String>>,
122}
123
124impl Default for SpecCommand {
125    fn default() -> Self {
126        Self {
127            full_cmd: vec![],
128            usage: "".to_string(),
129            subcommands: IndexMap::new(),
130            args: vec![],
131            flags: vec![],
132            mounts: vec![],
133            deprecated: None,
134            hide: false,
135            mounted: false,
136            flags_from_mount: false,
137            subcommand_required: false,
138            restart_token: None,
139            help: None,
140            help_long: None,
141            help_md: None,
142            name: "".to_string(),
143            aliases: vec![],
144            hidden_aliases: vec![],
145            before_help: None,
146            before_help_long: None,
147            before_help_md: None,
148            after_help: None,
149            after_help_long: None,
150            after_help_md: None,
151            examples: vec![],
152            subcommand_lookup: OnceLock::new(),
153            complete: IndexMap::new(),
154        }
155    }
156}
157
158#[derive(Debug, Default, Serialize, Clone)]
159pub struct SpecExample {
160    pub code: String,
161    pub header: Option<String>,
162    pub help: Option<String>,
163    pub lang: String,
164}
165
166impl SpecExample {
167    pub(crate) fn new(code: String) -> Self {
168        Self {
169            code,
170            ..Default::default()
171        }
172    }
173}
174
175impl From<&SpecExample> for KdlNode {
176    fn from(example: &SpecExample) -> KdlNode {
177        let mut node = KdlNode::new("example");
178        node.push(string_entry(None, &example.code));
179        if let Some(header) = &example.header {
180            node.push(string_entry(Some("header"), header));
181        }
182        if let Some(help) = &example.help {
183            node.push(string_entry(Some("help"), help));
184        }
185        if !example.lang.is_empty() {
186            node.push(string_entry(Some("lang"), &example.lang));
187        }
188        node
189    }
190}
191
192impl SpecCommand {
193    /// Create a new builder for SpecCommand
194    pub fn builder() -> SpecCommandBuilder {
195        SpecCommandBuilder::new()
196    }
197
198    pub(crate) fn parse(ctx: &ParsingContext, node: &NodeHelper) -> Result<Self, UsageErr> {
199        node.ensure_arg_len(1..=1)?;
200        let mut cmd = Self {
201            name: node.arg(0)?.ensure_string()?.to_string(),
202            ..Default::default()
203        };
204        for (k, v) in node.props() {
205            match k {
206                "help" => cmd.help = Some(v.ensure_string()?),
207                "long_help" => cmd.help_long = Some(v.ensure_string()?),
208                "help_long" => cmd.help_long = Some(v.ensure_string()?),
209                "help_md" => cmd.help_md = Some(v.ensure_string()?),
210                "before_help" => cmd.before_help = Some(v.ensure_string()?),
211                "before_long_help" => cmd.before_help_long = Some(v.ensure_string()?),
212                "before_help_long" => cmd.before_help_long = Some(v.ensure_string()?),
213                "before_help_md" => cmd.before_help_md = Some(v.ensure_string()?),
214                "after_help" => cmd.after_help = Some(v.ensure_string()?),
215                "after_long_help" => {
216                    cmd.after_help_long = Some(v.ensure_string()?);
217                }
218                "after_help_long" => {
219                    cmd.after_help_long = Some(v.ensure_string()?);
220                }
221                "after_help_md" => cmd.after_help_md = Some(v.ensure_string()?),
222                "subcommand_required" => cmd.subcommand_required = v.ensure_bool()?,
223                "hide" => cmd.hide = v.ensure_bool()?,
224                "restart_token" => cmd.restart_token = Some(v.ensure_string()?),
225                "deprecated" => {
226                    cmd.deprecated = match v.value.as_bool() {
227                        Some(true) => Some("deprecated".to_string()),
228                        Some(false) => None,
229                        None => Some(v.ensure_string()?),
230                    }
231                }
232                k => bail_parse!(ctx, v.entry.span(), "unsupported cmd prop {k}"),
233            }
234        }
235        for child in node.children() {
236            match child.name() {
237                "flag" => cmd.flags.push(SpecFlag::parse(ctx, &child)?),
238                "arg" => cmd.args.push(SpecArg::parse(ctx, &child)?),
239                "mount" => cmd.mounts.push(SpecMount::parse(ctx, &child)?),
240                "cmd" => {
241                    let node = SpecCommand::parse(ctx, &child)?;
242                    cmd.subcommands.insert(node.name.to_string(), node);
243                }
244                "alias" => {
245                    let alias = child
246                        .ensure_arg_len(1..)?
247                        .args()
248                        .map(|e| e.ensure_string())
249                        .collect::<Result<Vec<_>, _>>()?;
250                    let hide = child
251                        .get("hide")
252                        .map(|n| n.ensure_bool())
253                        .unwrap_or(Ok(false))?;
254                    if hide {
255                        cmd.hidden_aliases.extend(alias);
256                    } else {
257                        cmd.aliases.extend(alias);
258                    }
259                }
260                "example" => {
261                    let code = child.ensure_arg_len(1..=1)?.arg(0)?.ensure_string()?;
262                    let mut example = SpecExample::new(code.trim().to_string());
263                    for (k, v) in child.props() {
264                        match k {
265                            "header" => example.header = Some(v.ensure_string()?),
266                            "help" => example.help = Some(v.ensure_string()?),
267                            "lang" => example.lang = v.ensure_string()?,
268                            k => bail_parse!(ctx, v.entry.span(), "unsupported example key {k}"),
269                        }
270                    }
271                    cmd.examples.push(example);
272                }
273                "help" => {
274                    cmd.help = Some(child.ensure_arg_len(1..=1)?.arg(0)?.ensure_string()?);
275                }
276                "long_help" => {
277                    cmd.help_long = Some(child.ensure_arg_len(1..=1)?.arg(0)?.ensure_string()?);
278                }
279                "before_help" => {
280                    cmd.before_help = Some(child.ensure_arg_len(1..=1)?.arg(0)?.ensure_string()?);
281                }
282                "before_long_help" => {
283                    cmd.before_help_long =
284                        Some(child.ensure_arg_len(1..=1)?.arg(0)?.ensure_string()?);
285                }
286                "after_help" => {
287                    cmd.after_help = Some(child.ensure_arg_len(1..=1)?.arg(0)?.ensure_string()?);
288                }
289                "after_long_help" => {
290                    cmd.after_help_long =
291                        Some(child.ensure_arg_len(1..=1)?.arg(0)?.ensure_string()?);
292                }
293                "subcommand_required" => {
294                    cmd.subcommand_required = child.ensure_arg_len(1..=1)?.arg(0)?.ensure_bool()?
295                }
296                "hide" => cmd.hide = child.ensure_arg_len(1..=1)?.arg(0)?.ensure_bool()?,
297                "restart_token" => {
298                    cmd.restart_token = Some(child.ensure_arg_len(1..=1)?.arg(0)?.ensure_string()?)
299                }
300                "deprecated" => {
301                    cmd.deprecated = match child.arg(0)?.value.as_bool() {
302                        Some(true) => Some("deprecated".to_string()),
303                        Some(false) => None,
304                        None => Some(child.arg(0)?.ensure_string()?),
305                    }
306                }
307                "complete" => {
308                    let complete = SpecComplete::parse(ctx, &child)?;
309                    cmd.complete.insert(complete.name.clone(), complete);
310                }
311                k => bail_parse!(ctx, child.node.name().span(), "unsupported cmd key {k}"),
312            }
313        }
314        Ok(cmd)
315    }
316    pub(crate) fn is_empty(&self) -> bool {
317        self.args.is_empty()
318            && self.flags.is_empty()
319            && self.mounts.is_empty()
320            && self.subcommands.is_empty()
321    }
322    pub fn usage(&self) -> String {
323        let mut usage = self.full_cmd.join(" ");
324        let flags = self.flags.iter().filter(|f| !f.hide).collect_vec();
325        let args = self.args.iter().filter(|a| !a.hide).collect_vec();
326        if !flags.is_empty() {
327            if flags.len() <= 2 {
328                let inlines = flags
329                    .iter()
330                    .map(|f| {
331                        if f.required {
332                            format!("<{}>", f.usage())
333                        } else {
334                            format!("[{}]", f.usage())
335                        }
336                    })
337                    .join(" ");
338                usage = format!("{usage} {inlines}").trim().to_string();
339            } else if flags.iter().any(|f| f.required) {
340                usage = format!("{usage} <FLAGS>");
341            } else {
342                usage = format!("{usage} [FLAGS]");
343            }
344        }
345        if !args.is_empty() {
346            if args.len() <= 2 {
347                let inlines = args.iter().map(|a| a.usage()).join(" ");
348                usage = format!("{usage} {inlines}").trim().to_string();
349            } else if args.iter().any(|a| a.required) {
350                usage = format!("{usage} <ARGS>…");
351            } else {
352                usage = format!("{usage} [ARGS]…");
353            }
354        }
355        // TODO: mounts?
356        // if !self.mounts.is_empty() {
357        //     name = format!("{name} [mounts]");
358        // }
359        if !self.subcommands.is_empty() {
360            usage = format!("{usage} <SUBCOMMAND>");
361        }
362        usage.trim().to_string()
363    }
364    pub(crate) fn merge(&mut self, other: Self) {
365        if !other.name.is_empty() {
366            self.name = other.name;
367        }
368        if other.help.is_some() {
369            self.help = other.help;
370        }
371        if other.help_long.is_some() {
372            self.help_long = other.help_long;
373        }
374        if other.help_md.is_some() {
375            self.help_md = other.help_md;
376        }
377        if other.before_help.is_some() {
378            self.before_help = other.before_help;
379        }
380        if other.before_help_long.is_some() {
381            self.before_help_long = other.before_help_long;
382        }
383        if other.before_help_md.is_some() {
384            self.before_help_md = other.before_help_md;
385        }
386        if other.after_help.is_some() {
387            self.after_help = other.after_help;
388        }
389        if other.after_help_long.is_some() {
390            self.after_help_long = other.after_help_long;
391        }
392        if other.after_help_md.is_some() {
393            self.after_help_md = other.after_help_md;
394        }
395        if !other.args.is_empty() {
396            self.args = other.args;
397        }
398        if !other.flags.is_empty() {
399            self.flags = other.flags;
400        }
401        if !other.mounts.is_empty() {
402            self.mounts = other.mounts;
403        }
404        if !other.aliases.is_empty() {
405            self.aliases = other.aliases;
406        }
407        if !other.hidden_aliases.is_empty() {
408            self.hidden_aliases = other.hidden_aliases;
409        }
410        if !other.examples.is_empty() {
411            self.examples = other.examples;
412        }
413        self.hide = other.hide;
414        self.subcommand_required = other.subcommand_required;
415        if other.restart_token.is_some() {
416            self.restart_token = other.restart_token;
417        }
418        for (name, cmd) in other.subcommands {
419            self.subcommands.insert(name, cmd);
420        }
421        for (name, complete) in other.complete {
422            self.complete.insert(name, complete);
423        }
424    }
425
426    pub fn all_subcommands(&self) -> Vec<&SpecCommand> {
427        let mut cmds = vec![];
428        for cmd in self.subcommands.values() {
429            cmds.push(cmd);
430            cmds.extend(cmd.all_subcommands());
431        }
432        cmds
433    }
434
435    pub fn find_subcommand(&self, name: &str) -> Option<&SpecCommand> {
436        let sl = self.subcommand_lookup.get_or_init(|| {
437            let mut map = HashMap::new();
438            for (name, cmd) in &self.subcommands {
439                map.insert(name.clone(), name.clone());
440                for alias in &cmd.aliases {
441                    map.insert(alias.clone(), name.clone());
442                }
443                for alias in &cmd.hidden_aliases {
444                    map.insert(alias.clone(), name.clone());
445                }
446            }
447            map
448        });
449        let name = sl.get(name)?;
450        self.subcommands.get(name)
451    }
452
453    pub(crate) fn mount(&mut self, global_flag_args: &[String]) -> Result<(), UsageErr> {
454        for mount in self.mounts.iter().cloned().collect_vec() {
455            let cmd = if global_flag_args.is_empty() {
456                mount.run.clone()
457            } else {
458                // Parse the mount command into tokens, insert global flags after the first token
459                // e.g., "mise tasks ls" becomes "mise --cd dir2 tasks ls"
460                // Handles quoted arguments correctly: "cmd 'arg with spaces'" stays correct
461                let mut tokens = shell_words::split(&mount.run)
462                    .expect("mount command should be valid shell syntax");
463                if !tokens.is_empty() {
464                    // Insert global flags after the first token (the command name)
465                    tokens.splice(1..1, global_flag_args.iter().cloned());
466                }
467                // Join tokens back into a properly quoted command string
468                shell_words::join(tokens)
469            };
470            let output = sh(&cmd)?;
471            let mut spec: Spec = output.parse()?;
472            // The subcommands emitted by a mount describe another program, so mark them (and
473            // everything below them) as mounted. See `SpecCommand::mounted`.
474            for cmd in spec.cmd.subcommands.values_mut() {
475                cmd.mark_mounted();
476            }
477            // `merge` folds the mounted spec's root flags into this command; remember that they
478            // came from the mount. See `SpecCommand::flags_from_mount`.
479            self.flags_from_mount |= !spec.cmd.flags.is_empty();
480            self.merge(spec.cmd);
481        }
482        Ok(())
483    }
484
485    /// Mark this command and all of its subcommands as coming from a mount.
486    pub(crate) fn mark_mounted(&mut self) {
487        self.mounted = true;
488        for cmd in self.subcommands.values_mut() {
489            cmd.mark_mounted();
490        }
491    }
492}
493
494impl From<&SpecCommand> for KdlNode {
495    fn from(cmd: &SpecCommand) -> Self {
496        let mut node = Self::new("cmd");
497        node.entries_mut().push(cmd.name.clone().into());
498        if cmd.hide {
499            node.entries_mut().push(KdlEntry::new_prop("hide", true));
500        }
501        if cmd.subcommand_required {
502            node.entries_mut()
503                .push(KdlEntry::new_prop("subcommand_required", true));
504        }
505        if let Some(restart_token) = &cmd.restart_token {
506            node.entries_mut()
507                .push(KdlEntry::new_prop("restart_token", restart_token.clone()));
508        }
509        if !cmd.aliases.is_empty() {
510            let mut aliases = KdlNode::new("alias");
511            for alias in &cmd.aliases {
512                aliases.entries_mut().push(alias.clone().into());
513            }
514            let children = node.children_mut().get_or_insert_with(KdlDocument::new);
515            children.nodes_mut().push(aliases);
516        }
517        if !cmd.hidden_aliases.is_empty() {
518            let mut aliases = KdlNode::new("alias");
519            for alias in &cmd.hidden_aliases {
520                aliases.entries_mut().push(alias.clone().into());
521            }
522            aliases.entries_mut().push(KdlEntry::new_prop("hide", true));
523            let children = node.children_mut().get_or_insert_with(KdlDocument::new);
524            children.nodes_mut().push(aliases);
525        }
526        if let Some(help) = &cmd.help {
527            node.entries_mut().push(string_entry(Some("help"), help));
528        }
529        if let Some(help) = &cmd.help_long {
530            let children = node.children_mut().get_or_insert_with(KdlDocument::new);
531            let mut node = KdlNode::new("long_help");
532            node.push(string_entry(None, help));
533            children.nodes_mut().push(node);
534        }
535        if let Some(help) = &cmd.help_md {
536            let children = node.children_mut().get_or_insert_with(KdlDocument::new);
537            let mut node = KdlNode::new("help_md");
538            node.push(string_entry(None, help));
539            children.nodes_mut().push(node);
540        }
541        if let Some(help) = &cmd.before_help {
542            node.entries_mut()
543                .push(string_entry(Some("before_help"), help));
544        }
545        if let Some(help) = &cmd.before_help_long {
546            let children = node.children_mut().get_or_insert_with(KdlDocument::new);
547            let mut node = KdlNode::new("before_long_help");
548            node.push(string_entry(None, help));
549            children.nodes_mut().push(node);
550        }
551        if let Some(help) = &cmd.before_help_md {
552            let children = node.children_mut().get_or_insert_with(KdlDocument::new);
553            let mut node = KdlNode::new("before_help_md");
554            node.push(string_entry(None, help));
555            children.nodes_mut().push(node);
556        }
557        if let Some(help) = &cmd.after_help {
558            node.entries_mut()
559                .push(string_entry(Some("after_help"), help));
560        }
561        if let Some(help) = &cmd.after_help_long {
562            let children = node.children_mut().get_or_insert_with(KdlDocument::new);
563            let mut node = KdlNode::new("after_long_help");
564            node.push(string_entry(None, help));
565            children.nodes_mut().push(node);
566        }
567        if let Some(help) = &cmd.after_help_md {
568            let children = node.children_mut().get_or_insert_with(KdlDocument::new);
569            let mut node = KdlNode::new("after_help_md");
570            node.push(string_entry(None, help));
571            children.nodes_mut().push(node);
572        }
573        if let Some(deprecated) = &cmd.deprecated {
574            node.entries_mut()
575                .push(string_entry(Some("deprecated"), deprecated));
576        }
577        for flag in &cmd.flags {
578            let children = node.children_mut().get_or_insert_with(KdlDocument::new);
579            children.nodes_mut().push(flag.into());
580        }
581        for arg in &cmd.args {
582            let children = node.children_mut().get_or_insert_with(KdlDocument::new);
583            children.nodes_mut().push(arg.into());
584        }
585        for mount in &cmd.mounts {
586            let children = node.children_mut().get_or_insert_with(KdlDocument::new);
587            children.nodes_mut().push(mount.into());
588        }
589        for cmd in cmd.subcommands.values() {
590            let children = node.children_mut().get_or_insert_with(KdlDocument::new);
591            children.nodes_mut().push(cmd.into());
592        }
593        for complete in cmd.complete.values() {
594            let children = node.children_mut().get_or_insert_with(KdlDocument::new);
595            children.nodes_mut().push(complete.into());
596        }
597        node
598    }
599}
600
601#[cfg(feature = "clap")]
602impl From<&clap::Command> for SpecCommand {
603    fn from(cmd: &clap::Command) -> Self {
604        let mut spec = Self {
605            name: cmd.get_name().to_string(),
606            hide: cmd.is_hide_set(),
607            help: cmd.get_about().map(|s| s.to_string()),
608            help_long: cmd.get_long_about().map(|s| s.to_string()),
609            before_help: cmd.get_before_help().map(|s| s.to_string()),
610            before_help_long: cmd.get_before_long_help().map(|s| s.to_string()),
611            after_help: cmd.get_after_help().map(|s| s.to_string()),
612            after_help_long: cmd.get_after_long_help().map(|s| s.to_string()),
613            ..Default::default()
614        };
615        for alias in cmd.get_visible_aliases() {
616            spec.aliases.push(alias.to_string());
617        }
618        for alias in cmd.get_all_aliases() {
619            if spec.aliases.contains(&alias.to_string()) {
620                continue;
621            }
622            spec.hidden_aliases.push(alias.to_string());
623        }
624        for arg in cmd.get_arguments() {
625            if arg.is_positional() {
626                spec.args.push(arg.into())
627            } else {
628                spec.flags.push(arg.into())
629            }
630        }
631        spec.subcommand_required = cmd.is_subcommand_required_set();
632        for subcmd in cmd.get_subcommands() {
633            let mut scmd: SpecCommand = subcmd.into();
634            scmd.name = subcmd.get_name().to_string();
635            spec.subcommands.insert(scmd.name.clone(), scmd);
636        }
637        spec
638    }
639}
640
641#[cfg(feature = "clap")]
642impl From<clap::Command> for Spec {
643    fn from(cmd: clap::Command) -> Self {
644        (&cmd).into()
645    }
646}