Skip to main content

skill_embed/
usage.rs

1use crate::Installer;
2use crate::cli::flag_block;
3use crate::textfmt::{self, Columns};
4
5impl Installer {
6    /// The help text for the skill command. A tool that writes its own help can
7    /// print it, so that the two agree.
8    #[must_use]
9    pub fn usage(&self) -> String {
10        self.usage_for(None)
11    }
12
13    /// One line naming the skill command, for a tool whose own help would
14    /// otherwise never mention it.
15    ///
16    /// ```no_run
17    /// # let skills: skill_embed::Installer = unimplemented!();
18    /// eprintln!("{}", skills.usage_hint());
19    /// ```
20    ///
21    /// ```text
22    /// Run "mytool skill" to install the 2 agent skills embedded in mytool.
23    /// ```
24    #[must_use]
25    pub fn usage_hint(&self) -> String {
26        let n = self.skills().len();
27        let plural = if n == 1 { "skill" } else { "skills" };
28        format!(
29            "Run \"{tool} {cmd}\" to install the {n} agent {plural} embedded in {tool}.",
30            tool = self.tool_name(),
31            cmd = self.command_name(),
32        )
33    }
34
35    /// The help for one subcommand, or for the command itself when `sub` is
36    /// [`None`].
37    ///
38    /// [`Installer::usage`] is the whole command's, which is the one a tool
39    /// prints. A subcommand's is only ever asked for by `--help`.
40    pub(crate) fn usage_for(&self, sub: Option<&str>) -> String {
41        let (summary, lines) = self.headings(sub);
42        let default_agents: Vec<String> =
43            self.default_agents().iter().map(ToString::to_string).collect();
44
45        let mut skills = Columns::default();
46        for sk in self.skills().skills() {
47            skills.push([format!("  {}", sk.name()), textfmt::first_line(sk.description())]);
48        }
49
50        let flags =
51            flag_block(&self.agent_choices(), &default_agents.join(","), self.default_scope());
52        let out = format!(
53            "{summary}\n\nUsage:\n{lines}\nFlags:\n{flags}\nEmbedded skills:\n{}",
54            skills.render()
55        );
56        textfmt::trim_lines(&out)
57    }
58
59    /// The first line and the usage line of each subcommand's help.
60    fn headings(&self, sub: Option<&str>) -> (String, String) {
61        let (tool, cmd) = (self.tool_name(), self.command_name());
62        let line =
63            |name: &str, pad: &str| format!("  {tool} {cmd} {name}{pad} [flags] [skill...]\n");
64        match sub {
65            // Naming the removal here because `--help` is the only surface many
66            // people read, and a subcommand called install deleting a directory
67            // is not something to leave to the README.
68            Some("install") => (
69                format!(
70                    "Install the agent skills embedded in {tool}, \
71                     and remove the ones it no longer carries."
72                ),
73                line("install", ""),
74            ),
75            Some("uninstall") => {
76                (format!("Remove the agent skills embedded in {tool}."), line("uninstall", ""))
77            }
78            Some("list") => (
79                format!("Show the agent skills embedded in {tool}, and where each one stands."),
80                line("list", ""),
81            ),
82            _ => (
83                format!("Manage the agent skills embedded in {tool}."),
84                line("install", "  ") + &line("uninstall", "") + &line("list", "     "),
85            ),
86        }
87    }
88}