Skip to main content

dial9_viewer/
cli.rs

1use clap::{Parser, Subcommand};
2use std::path::PathBuf;
3
4mod skills {
5    include!(concat!(env!("OUT_DIR"), "/skills.rs"));
6
7    pub fn get(name: &str) -> Option<&'static str> {
8        SKILL_DIRS.iter().find(|s| s.name == name).map(|s| s.body)
9    }
10}
11
12#[derive(Parser, Debug)]
13#[command(
14    name = "dial9",
15    about = "Trace browser and viewer for dial9-tokio-telemetry"
16)]
17pub struct Cli {
18    #[command(subcommand)]
19    command: Commands,
20}
21
22#[derive(Subcommand, Debug)]
23enum Commands {
24    /// Agent skill documentation and analysis toolkit
25    Agents {
26        #[command(subcommand)]
27        action: Option<AgentsAction>,
28    },
29    /// Start the web server
30    Serve {
31        /// Port to listen on
32        #[arg(long, default_value = "3000")]
33        port: u16,
34
35        /// S3 bucket name
36        #[arg(long)]
37        bucket: Option<String>,
38
39        /// S3 key prefix
40        #[arg(long)]
41        prefix: Option<String>,
42
43        /// Serve traces from a local directory instead of S3
44        #[arg(long, conflicts_with = "bucket")]
45        local_dir: Option<PathBuf>,
46
47        /// Dev mode: serve UI files from disk for faster iteration
48        #[arg(long)]
49        dev: bool,
50    },
51}
52
53#[derive(Subcommand, Debug)]
54enum AgentsAction {
55    /// Copy the analysis toolkit (JS modules) to a directory
56    Toolkit {
57        /// Directory to write toolkit files into (created if missing)
58        path: PathBuf,
59    },
60    /// Print a specific skill's instructions
61    Skill {
62        /// Skill name (e.g. dial9-trace-loading, dial9-red-flags)
63        name: String,
64    },
65    /// Unpack all skills as an Agent Skills spec directory
66    Skills {
67        /// Directory to write skills into (created if missing)
68        path: PathBuf,
69    },
70}
71
72/// Run the CLI. Call this from your binary's `main()`.
73pub async fn run() -> anyhow::Result<()> {
74    let cli = Cli::parse();
75
76    match cli.command {
77        Commands::Agents { action } => match action {
78            None => print!("{}", skills::HEADER),
79            Some(AgentsAction::Toolkit { path }) => {
80                std::fs::create_dir_all(&path)?;
81                for (name, content) in skills::TOOLKIT_FILES {
82                    std::fs::write(path.join(name), content)?;
83                }
84                let abs = std::fs::canonicalize(&path)?;
85                eprintln!("Toolkit written to {}", abs.display());
86                eprintln!(
87                    "Run: node {}/analyze.js <trace.bin or directory>",
88                    abs.display()
89                );
90            }
91            Some(AgentsAction::Skill { name }) => match skills::get(&name) {
92                Some(content) => print!("{}", content),
93                None => {
94                    eprintln!("Unknown skill: {name}");
95                    eprintln!("Available skills:");
96                    for skill in skills::SKILL_DIRS {
97                        eprintln!("  {:24} {}", skill.name, skill.description);
98                    }
99                    std::process::exit(1);
100                }
101            },
102            Some(AgentsAction::Skills { path }) => {
103                for skill in skills::SKILL_DIRS {
104                    let skill_dir = path.join(skill.name);
105                    for (rel_path, content) in skill.files {
106                        let file_path = skill_dir.join(rel_path);
107                        if let Some(parent) = file_path.parent() {
108                            std::fs::create_dir_all(parent)?;
109                        }
110                        std::fs::write(&file_path, content)?;
111                    }
112                }
113                let abs = std::fs::canonicalize(&path)?;
114                eprintln!("Skills unpacked to {}", abs.display());
115                eprintln!("Add to .kiro/skills/ or point your agent at this directory.");
116            }
117        },
118        Commands::Serve {
119            port,
120            bucket,
121            prefix,
122            local_dir,
123            dev,
124        } => {
125            return crate::serve(port, bucket, prefix, local_dir, dev).await;
126        }
127    }
128    Ok(())
129}