1pub mod args;
7pub mod bundle;
8pub mod checkout;
9pub mod client;
10pub mod commands;
11pub mod config;
12pub mod credentials;
13pub mod docref;
14pub mod exit;
15pub mod output;
16pub mod project_link;
17pub mod remember;
18
19use clap::Parser;
20
21use crate::args::{Cli, Cmd};
22use crate::config::{Flags, Settings};
23
24pub struct Ctx {
26 pub settings: Settings,
27 pub json: bool,
28 pub store: credentials::Store,
29}
30
31impl Ctx {
32 pub fn client(&self) -> anyhow::Result<client::Client> {
33 client::Client::new(&self.settings)
34 }
35}
36
37pub async fn main_entry() -> i32 {
39 config::load_dotenv();
40 let cli = match Cli::try_parse() {
41 Ok(cli) => cli,
42 Err(e) => {
43 print_banner(&e);
44 e.exit()
45 }
46 };
47 let env = |k: &str| std::env::var(k).ok();
48 let result = async {
49 let ctx = build_ctx(&cli, &env)?;
50 run(ctx, cli.cmd).await
51 }
52 .await;
53 match result {
54 Ok(code) => code,
55 Err(e) => {
56 match e.downcast_ref::<exit::Exit>() {
57 Some(x) => output::error(&x.message),
58 None => output::error(format!("{e:#}")),
59 }
60 exit::code_of(&e)
61 }
62 }
63}
64
65fn print_banner(e: &clap::Error) {
69 use std::io::IsTerminal;
70 if e.kind() == clap::error::ErrorKind::DisplayVersion {
71 if std::io::stdout().is_terminal() {
72 anstream::println!("{}", output::banner(output::out()));
73 }
74 } else if std::env::args_os().len() == 1 && std::io::stderr().is_terminal() {
75 anstream::eprintln!("{}\n", output::banner(output::err()));
77 }
78}
79
80fn build_ctx(cli: &Cli, env: &dyn Fn(&str) -> Option<String>) -> anyhow::Result<Ctx> {
81 let dir = config::config_dir(env);
82 let file = config::read_file_config(&dir)?;
83 let no_keyring = env("SEMORA_NO_KEYRING").is_some_and(|v| !v.is_empty() && v != "0");
84 let store = credentials::Store::new(&dir, !no_keyring);
85 let flags = Flags {
86 server: cli.server.clone(),
87 api_key: cli.api_key.clone(),
88 };
89 let settings = config::resolve(&flags, env, &file, store.default_server(), &|u| {
90 store.get(&config::origin(u))
91 })?;
92 Ok(Ctx {
93 settings,
94 json: cli.json,
95 store,
96 })
97}
98
99async fn run(ctx: Ctx, cmd: Cmd) -> anyhow::Result<i32> {
100 use commands::*;
101 match cmd {
102 Cmd::Push(a) => push::run(&ctx, a).await,
103 Cmd::Watch(a) => watch::run(&ctx, a).await,
104 Cmd::Pull(a) => pull::pull(&ctx, a).await,
105 Cmd::Status { path } => pull::status(&ctx, path).await,
106 Cmd::Wait(a) => wait::run(&ctx, a).await,
107 Cmd::Say {
108 doc,
109 text,
110 reply_to,
111 } => events::say(&ctx, &doc, &text, reply_to).await,
112 Cmd::Ls {
113 query,
114 tag,
115 kind,
116 project,
117 limit,
118 } => docs::ls(&ctx, query, tag, kind, project, limit).await,
119 Cmd::Show { doc } => docs::show(&ctx, &doc).await,
120 Cmd::Get {
121 doc,
122 path,
123 rev,
124 output,
125 } => docs::get(&ctx, &doc, path, rev, output).await,
126 Cmd::Tag { doc, changes } => docs::tag(&ctx, &doc, &changes).await,
127 Cmd::Rm { doc } => docs::rm(&ctx, &doc).await,
128 Cmd::Open { doc } => docs::open(&ctx, &doc).await,
129 Cmd::Whoami => account::whoami(&ctx).await,
130 Cmd::Login { no_browser, name } => login::login(&ctx, no_browser, name).await,
131 Cmd::Logout => login::logout(&ctx).await,
132 Cmd::Keys(k) => account::keys(&ctx, k).await,
133 Cmd::Project(p) => project::run(&ctx, p).await,
134 Cmd::Skill => skill::run(&ctx),
135 }
136}