Skip to main content

omni_dev/cli/
resources.rs

1//! `omni-dev resources` — embedded reference content (specs, etc.) shared
2//! with the MCP `omni-dev://specs/{name}` resource family.
3
4use anyhow::{anyhow, Result};
5use clap::{Parser, Subcommand};
6
7use crate::resources;
8
9/// Embedded resource operations.
10#[derive(Parser)]
11pub struct ResourcesCommand {
12    /// Resources subcommand to execute.
13    #[command(subcommand)]
14    pub command: ResourcesSubcommands,
15}
16
17/// Resources subcommands.
18#[derive(Subcommand)]
19pub enum ResourcesSubcommands {
20    /// Print the raw content of an embedded resource to stdout.
21    Show(ShowCommand),
22    /// List every embedded resource id, one per line.
23    List(ListCommand),
24}
25
26/// `omni-dev resources show <id>`.
27#[derive(Parser)]
28pub struct ShowCommand {
29    /// Resource id. Accepts `specs/jfm` or `omni-dev://specs/jfm` (the
30    /// `omni-dev://` scheme is stripped before lookup).
31    pub id: String,
32}
33
34/// `omni-dev resources list`.
35#[derive(Parser)]
36pub struct ListCommand {}
37
38impl ResourcesCommand {
39    /// Executes the resources command.
40    pub fn execute(self) -> Result<()> {
41        match self.command {
42            ResourcesSubcommands::Show(c) => c.execute(),
43            ResourcesSubcommands::List(c) => c.execute(),
44        }
45    }
46}
47
48impl ShowCommand {
49    /// Executes `resources show`.
50    pub fn execute(self) -> Result<()> {
51        let canonical = normalize_id(&self.id);
52        match resources::get(canonical) {
53            Some(r) => {
54                // Raw content only — no header. `print!` (not `println!`) so a
55                // trailing newline is not appended to content that may not
56                // have one.
57                print!("{}", r.content);
58                Ok(())
59            }
60            None => Err(anyhow!(
61                "unknown resource `{id}`; known: {known}",
62                id = self.id,
63                known = resources::known_ids_csv(),
64            )),
65        }
66    }
67}
68
69impl ListCommand {
70    /// Executes `resources list`.
71    pub fn execute(self) -> Result<()> {
72        for id in resources::ids() {
73            println!("{id}");
74        }
75        Ok(())
76    }
77}
78
79/// Strips a single leading `omni-dev://` scheme if present, returning the
80/// canonical path-style id.
81fn normalize_id(input: &str) -> &str {
82    input.strip_prefix("omni-dev://").unwrap_or(input)
83}
84
85#[cfg(test)]
86#[allow(clippy::unwrap_used, clippy::expect_used)]
87mod tests {
88    use super::*;
89    use crate::cli::{Cli, Commands};
90
91    #[test]
92    fn normalize_id_strips_scheme() {
93        assert_eq!(normalize_id("omni-dev://specs/jfm"), "specs/jfm");
94    }
95
96    #[test]
97    fn normalize_id_passes_through_plain() {
98        assert_eq!(normalize_id("specs/jfm"), "specs/jfm");
99    }
100
101    #[test]
102    fn normalize_id_strips_only_one_scheme() {
103        // `strip_prefix` removes at most one occurrence.
104        assert_eq!(
105            normalize_id("omni-dev://omni-dev://specs/jfm"),
106            "omni-dev://specs/jfm"
107        );
108    }
109
110    #[test]
111    fn normalize_id_does_not_strip_other_schemes() {
112        assert_eq!(normalize_id("jira://issue/X-1"), "jira://issue/X-1");
113        assert_eq!(
114            normalize_id("git://repo/commits/HEAD"),
115            "git://repo/commits/HEAD"
116        );
117    }
118
119    #[test]
120    fn show_unknown_id_errors_with_known_list() {
121        let cmd = ShowCommand {
122            id: "specs/does-not-exist".into(),
123        };
124        let err = cmd.execute().expect_err("unknown id must error");
125        let chain = format!("{err:#}");
126        assert!(
127            chain.contains("unknown resource"),
128            "missing prefix: {chain}"
129        );
130        assert!(chain.contains("specs/jfm"), "known list missing: {chain}");
131    }
132
133    #[test]
134    fn clap_parses_show_command() {
135        let cli = Cli::try_parse_from(["omni-dev", "resources", "show", "specs/jfm"]).unwrap();
136        match cli.command {
137            Commands::Resources(ResourcesCommand {
138                command: ResourcesSubcommands::Show(c),
139            }) => assert_eq!(c.id, "specs/jfm"),
140            _ => panic!("expected resources show"),
141        }
142    }
143
144    #[test]
145    fn clap_parses_list_command() {
146        let cli = Cli::try_parse_from(["omni-dev", "resources", "list"]).unwrap();
147        assert!(matches!(
148            cli.command,
149            Commands::Resources(ResourcesCommand {
150                command: ResourcesSubcommands::List(_)
151            })
152        ));
153    }
154
155    #[test]
156    fn clap_parses_show_with_omni_dev_uri() {
157        let cli =
158            Cli::try_parse_from(["omni-dev", "resources", "show", "omni-dev://specs/jfm"]).unwrap();
159        match cli.command {
160            Commands::Resources(ResourcesCommand {
161                command: ResourcesSubcommands::Show(c),
162            }) => assert_eq!(c.id, "omni-dev://specs/jfm"),
163            _ => panic!("expected resources show"),
164        }
165    }
166}