main.rsannotatedmain.rssource68 lines · 2.6 KB · raw
1//! Talk to Whiskers from a terminal, through the same engine the tablet uses.
2//!
3//!     whiskers-cli <data-dir> <service-url> [model]
4//!
5//! `<service-url>` is `whiskersd` (the model, the guard and the memory search all go through it).
6//! Type a line, read the answer. `/pic <file.jpg> words` shows Whiskers a picture. The log,
7//! memory, chat and pictures go in <data-dir>.
8
9use std::io::{BufRead, Write};
10
11use whiskers_core::Image;
12use log::{debug, error, info, warn};
13use whiskers_engine::{Engine, EngineConfig};
14
15fn main() {
16    whiskers_core::logging::init("whiskers-cli");
17    let args: Vec<String> = std::env::args().skip(1).collect();
18    let [dir, service, rest @ ..] = args.as_slice() else {
19        eprintln!("usage: whiskers-cli <data-dir> <service-url> [model]");
20        std::process::exit(2);
21    };
22    info!("whiskers-cli starting, data dir {dir}");
23    let engine = Engine::new(EngineConfig {
24        data_dir: dir.into(),
25        gateway_url: service.clone(),
26        guard_url: service.clone(),
27        model: rest.first().cloned().unwrap_or_else(|| "claude-haiku-4-5-20251001".to_owned()),
28        system_prompt: None,
29    })
30    .unwrap_or_else(|e| {
31        error!("cannot start: {e}");
32        std::process::exit(1);
33    });
34    if engine.greeting_due() {
35        debug!("greeting");
36        println!("Whiskers: {}", engine.greet().text);
37    }
38    let stdin = std::io::stdin();
39    loop {
40        print!("You: ");
41        std::io::stdout().flush().ok();
42        let mut line = String::new();
43        if stdin.lock().read_line(&mut line).unwrap_or(0) == 0 {
44            info!("end of input; leaving");
45            break;
46        }
47        let (words, pictures) = match line.trim().strip_prefix("/pic ") {
48            Some(rest) => {
49                let (path, words) = rest.split_once(' ').unwrap_or((rest, ""));
50                match std::fs::read(path) {
51                    Ok(bytes) => (words.to_owned(), vec![Image { media_type: "image/jpeg".into(), bytes }]),
52                    Err(e) => {
53                        warn!("cannot read the picture {path}: {e}");
54                        println!("(cannot read {path}: {e})");
55                        continue;
56                    }
57                }
58            }
59            None => (line.trim().to_owned(), vec![]),
60        };
61        debug!("heard {} chars, {} picture(s)", words.len(), pictures.len());
62        let said = engine.hear(&words, pictures);
63        println!("Whiskers [{:?}]: {}", said.outcome, said.text);
64        for fact in engine.reflect() {
65            println!("   (remembers: {} [{}])", fact.text, fact.kind.word());
66        }
67    }
68}