Skip to main content

kagi_app/
lib.rs

1pub mod application;
2pub mod cli;
3
4use clap::{CommandFactory, FromArgMatches};
5use std::io::IsTerminal;
6
7pub async fn run() {
8    let filter = tracing_subscriber::EnvFilter::try_from_default_env()
9        .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info"));
10    tracing_subscriber::fmt().with_env_filter(filter).init();
11
12    let mut cmd = cli::args::Cli::command();
13    cmd = cmd.styles(cli::style::kagi_styles());
14
15    let tty = std::io::stdout().is_terminal();
16    let c = cli::style::Palette::new(tty);
17    let logo = c.accent(
18        r#"  _              _
19 | | ____ _  __ _(_)
20 | |/ / _` |/ _` | |
21 |   < (_| | (_| | |
22 |_|\_\__,_|\__, |_|
23            |___/   K"#,
24    );
25    #[cfg(not(feature = "server"))]
26    let cmd_lines: Vec<(&str, &str)> = vec![
27        ("init", "create a team-ready encrypted project"),
28        ("set", "store one encrypted value"),
29        ("run", "start a process with injected env vars"),
30        (
31            "get",
32            "show service/env keys or print one value after confirmation",
33        ),
34        ("export", "print KEY=value lines after confirmation"),
35        ("import", "import values from a .env file"),
36        ("sync", "sync keys from .env.example"),
37        ("file", "manage encrypted files"),
38        ("env", "manage default environments"),
39        ("member", "request, list, approve, or remove members"),
40    ];
41    #[cfg(feature = "server")]
42    let cmd_lines: Vec<(&str, &str)> = vec![
43        ("init", "create a team-ready encrypted project"),
44        ("set", "store one encrypted value"),
45        ("run", "start a process with injected env vars"),
46        (
47            "get",
48            "show service/env keys or print one value after confirmation",
49        ),
50        ("export", "print KEY=value lines after confirmation"),
51        ("import", "import values from a .env file"),
52        ("sync", "sync keys from .env.example"),
53        ("file", "manage encrypted files"),
54        ("env", "manage default environments"),
55        ("member", "request, list, approve, or remove members"),
56        ("serve", "start the remote sync server"),
57        ("remote", "login, register, sync, and administer remotes"),
58    ];
59    let max_cmd = cmd_lines.iter().map(|(n, _)| n.len()).max().unwrap_or(0);
60    let cmd_list = cmd_lines
61        .iter()
62        .map(|(name, desc)| {
63            let pad = " ".repeat(max_cmd.saturating_sub(name.len()));
64            format!("  {}{} {}", c.accent(name), pad, c.muted(desc))
65        })
66        .collect::<Vec<_>>()
67        .join("\n");
68
69    let flow_lines: Vec<(&str, &str)> = vec![
70        ("init", "--envs development,production"),
71        ("set", "api DATABASE_URL '<value>'"),
72        ("run", "api bun dev"),
73    ];
74    let max_flow = flow_lines.iter().map(|(n, _)| n.len()).max().unwrap_or(0);
75    let flow_list = flow_lines
76        .iter()
77        .map(|(name, args)| {
78            let pad = " ".repeat(max_flow.saturating_sub(name.len()));
79            format!(
80                "  {} {}{} {}",
81                c.accent("kagi"),
82                c.accent(name),
83                pad,
84                c.muted(args)
85            )
86        })
87        .collect::<Vec<_>>()
88        .join("\n");
89
90    let cmd_ref = format!(
91        "{logo}\n{rule}\n{tagline}\n\n{usage}\n  {kagi} {command}\n  {kagi} {command_help}\n\n{flow}\n{flow_list}\n\n{commands}\n{cmd_list}\n\n{security}\n  {security_note}",
92        rule = c.warning("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"),
93        tagline = c.muted("Encrypted envs, scoped safely."),
94        usage = c.warning("Usage"),
95        kagi = c.accent("kagi"),
96        command = c.key("<command>"),
97        command_help = c.key("<command> --help"),
98        flow = c.warning("Core Flow"),
99        flow_list = flow_list,
100        commands = c.warning("Commands"),
101        cmd_list = cmd_list,
102        security = c.warning("Security"),
103        security_note =
104            c.muted("Use kagi run for scripts. get --show/export require a terminal prompt."),
105    );
106    cmd = cmd.before_help(cmd_ref);
107    cmd = cmd.help_template("{before-help}");
108
109    if std::env::args_os().len() == 1 {
110        if let Err(e) = cmd.print_help() {
111            eprintln!("{e}");
112            std::process::exit(1);
113        }
114        println!();
115        return;
116    }
117
118    let matches = cmd.get_matches();
119    let cli = match cli::args::Cli::from_arg_matches(&matches) {
120        Ok(c) => c,
121        Err(e) => e.exit(),
122    };
123    if let Err(e) = cli::commands::run(cli).await {
124        let tty = std::io::stdout().is_terminal();
125        let c = cli::style::Palette::new(tty);
126        eprintln!("{} {}", c.prefix(), c.error(&format!("error: {e}")));
127        std::process::exit(1);
128    }
129}