feat: added config auth setup ef7acd60
Steve · 2026-02-19 07:26 4 file(s) · +104 −5
Cargo.toml +2 −0
27 27
reqwest = { version = "0.12", features = ["json", "blocking"] }
28 28
serde_json = "1"
29 29
clap = { version = "4", features = ["derive", "env"] }
30 +
toml = "0.8"
31 +
rpassword = "5"
src/bin/tui.rs +70 −5
1 1
use arboard::Clipboard;
2 -
use clap::Parser;
2 +
use clap::{Parser, Subcommand};
3 3
use crossterm::event::{self, Event, KeyCode, KeyModifiers};
4 4
use ratatui::{
5 5
    DefaultTerminal,
9 9
    widgets::{Block, Borders, Clear, List, ListItem, ListState, Paragraph, Widget},
10 10
};
11 11
use sipp_rust::backend::Backend;
12 +
use sipp_rust::config;
12 13
use sipp_rust::db::Snippet;
13 14
use std::io::Cursor;
14 15
use std::time::{Duration, Instant};
27 28
    /// API key for authenticated operations
28 29
    #[arg(short = 'k', long, env = "SIPP_API_KEY")]
29 30
    api_key: Option<String>,
31 +
32 +
    #[command(subcommand)]
33 +
    command: Option<Commands>,
34 +
}
35 +
36 +
#[derive(Subcommand)]
37 +
enum Commands {
38 +
    /// Save remote URL and API key to config file
39 +
    Auth,
30 40
}
31 41
32 42
enum Focus {
272 282
    }
273 283
}
274 284
285 +
fn run_auth() -> Result<(), Box<dyn std::error::Error>> {
286 +
    use std::io::{self, Write};
287 +
288 +
    print!("Remote URL: ");
289 +
    io::stdout().flush()?;
290 +
    let mut remote_url = String::new();
291 +
    io::stdin().read_line(&mut remote_url)?;
292 +
    let remote_url = remote_url.trim().to_string();
293 +
294 +
    print!("API Key: ");
295 +
    io::stdout().flush()?;
296 +
    let api_key = rpassword::read_password()?;
297 +
    let api_key = api_key.trim().to_string();
298 +
299 +
    let cfg = config::Config {
300 +
        remote_url: if remote_url.is_empty() {
301 +
            None
302 +
        } else {
303 +
            Some(remote_url)
304 +
        },
305 +
        api_key: if api_key.is_empty() {
306 +
            None
307 +
        } else {
308 +
            Some(api_key)
309 +
        },
310 +
    };
311 +
312 +
    config::save_config(&cfg)?;
313 +
    println!("Config saved to {}", config::config_path().display());
314 +
    Ok(())
315 +
}
316 +
317 +
fn resolve_backend(cli: &Cli) -> (Backend, bool, Option<String>) {
318 +
    // 1. CLI flags / env vars take highest priority
319 +
    if let Some(url) = &cli.remote {
320 +
        return (
321 +
            Backend::remote(url.clone(), cli.api_key.clone()),
322 +
            true,
323 +
            Some(url.clone()),
324 +
        );
325 +
    }
326 +
327 +
    // 2. If no local DB exists, try config file
328 +
    if !std::path::Path::new("sipp.sqlite").exists() {
329 +
        let cfg = config::load_config();
330 +
        if let Some(url) = cfg.remote_url {
331 +
            return (Backend::remote(url.clone(), cfg.api_key), true, Some(url));
332 +
        }
333 +
    }
334 +
335 +
    // 3. Fallback to local DB (creates it if needed)
336 +
    (Backend::local(), false, None)
337 +
}
338 +
275 339
fn main() -> Result<(), Box<dyn std::error::Error>> {
276 340
    let cli = Cli::parse();
277 341
278 -
    let (backend, is_remote, remote_url) = match cli.remote {
279 -
        Some(url) => (Backend::remote(url.clone(), cli.api_key), true, Some(url)),
280 -
        None => (Backend::local(), false, None),
281 -
    };
342 +
    if let Some(Commands::Auth) = &cli.command {
343 +
        return run_auth();
344 +
    }
345 +
346 +
    let (backend, is_remote, remote_url) = resolve_backend(&cli);
282 347
283 348
    let snippets = match backend.list_snippets() {
284 349
        Ok(s) => s,
src/config.rs (added) +31 −0
1 +
use serde::{Deserialize, Serialize};
2 +
use std::path::PathBuf;
3 +
4 +
#[derive(Debug, Default, Serialize, Deserialize)]
5 +
pub struct Config {
6 +
    pub remote_url: Option<String>,
7 +
    pub api_key: Option<String>,
8 +
}
9 +
10 +
pub fn config_path() -> PathBuf {
11 +
    let home = std::env::var("HOME").unwrap_or_else(|_| ".".to_string());
12 +
    PathBuf::from(home).join(".config/sipp/config.toml")
13 +
}
14 +
15 +
pub fn load_config() -> Config {
16 +
    let path = config_path();
17 +
    match std::fs::read_to_string(&path) {
18 +
        Ok(contents) => toml::from_str(&contents).unwrap_or_default(),
19 +
        Err(_) => Config::default(),
20 +
    }
21 +
}
22 +
23 +
pub fn save_config(config: &Config) -> Result<(), Box<dyn std::error::Error>> {
24 +
    let path = config_path();
25 +
    if let Some(parent) = path.parent() {
26 +
        std::fs::create_dir_all(parent)?;
27 +
    }
28 +
    let contents = toml::to_string_pretty(config)?;
29 +
    std::fs::write(&path, contents)?;
30 +
    Ok(())
31 +
}
src/lib.rs +1 −0
1 1
pub mod backend;
2 +
pub mod config;
2 3
pub mod db;
3 4
pub mod highlight;