Added ability to execute commands in a specified directory.
This commit is contained in:
52
src/chat.rs
52
src/chat.rs
@@ -12,13 +12,15 @@ use irc::client::prelude::{Client, Command, Config as IRCConfig, Message};
|
||||
use tokio::sync::mpsc;
|
||||
use tracing::{Level, event, instrument};
|
||||
|
||||
use crate::{Event, EventManager, LLMHandle, plugin};
|
||||
use crate::{CommandDir, Event, EventManager, LLMHandle, plugin};
|
||||
|
||||
/// Chat struct that is used to interact with IRC chat.
|
||||
#[derive(Debug)]
|
||||
pub struct Chat {
|
||||
/// The actual IRC [`irc::client`](client).
|
||||
client: Client,
|
||||
/// Handle to the directory that *may* contain command scripts.
|
||||
command_dir: Option<CommandDir>,
|
||||
/// Event manager for handling plugin interaction.
|
||||
event_manager: Arc<EventManager>,
|
||||
/// Handle for whichever LLM is being used.
|
||||
@@ -52,10 +54,17 @@ impl Chat {
|
||||
..IRCConfig::default()
|
||||
};
|
||||
|
||||
let commands_dir = if let Ok(path) = settings.get_string("command-path") {
|
||||
Some(CommandDir::new(path))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
event!(Level::INFO, "IRC connection starting...");
|
||||
|
||||
Ok(Chat {
|
||||
client: Client::from_config(config).await?,
|
||||
command_dir: commands_dir,
|
||||
llm_handle: handle.clone(),
|
||||
event_manager: manager,
|
||||
})
|
||||
@@ -111,8 +120,13 @@ impl Chat {
|
||||
|
||||
// Only handle PRIVMSG for now.
|
||||
if let Command::PRIVMSG(channel, msg) = &message.command {
|
||||
// Check it's a command.
|
||||
if let Some((cmd, args)) = command_str(msg) {
|
||||
// Command handling time.
|
||||
|
||||
match cmd {
|
||||
// Just preserve the original behavior for now.
|
||||
if msg.starts_with("!gem") {
|
||||
"!gem" => {
|
||||
let mut llm_response = self.llm_handle.send_request(msg).await?;
|
||||
|
||||
event!(Level::INFO, "Asked: {message}");
|
||||
@@ -127,8 +141,42 @@ impl Chat {
|
||||
event!(Level::INFO, "Sending {llm_response} to channel {channel}");
|
||||
self.client.send_privmsg(channel, llm_response)?;
|
||||
}
|
||||
|
||||
_ => {
|
||||
if let Some(cmd_dir) = &self.command_dir {
|
||||
// Strip '!'
|
||||
let cmd_name = &cmd[1..];
|
||||
match cmd_dir.run_command(cmd_name, args).await {
|
||||
Ok(res) => {
|
||||
let output = std::str::from_utf8(&res)?.to_string();
|
||||
self.client.send_privmsg(channel, output)?;
|
||||
}
|
||||
Err(e) => {
|
||||
// Log the error but don't crash, and maybe don't even tell the
|
||||
// user unless we're sure it
|
||||
// was meant to be a command?
|
||||
// For now, let's just log it.
|
||||
event!(
|
||||
Level::DEBUG,
|
||||
"Command execution failed or not found: {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn command_str(cmd: &str) -> Option<(&str, &str)> {
|
||||
if !cmd.starts_with('!') {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(cmd.split_once(' ').unwrap_or((cmd, "")))
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ pub mod qna;
|
||||
pub mod setup;
|
||||
|
||||
pub use chat::Chat;
|
||||
pub use command::CommandDir;
|
||||
pub use event::Event;
|
||||
pub use event_manager::EventManager;
|
||||
pub use qna::LLMHandle;
|
||||
|
||||
39
src/setup.rs
39
src/setup.rs
@@ -3,11 +3,14 @@
|
||||
//! Both command line, and configuration file options are handled here.
|
||||
|
||||
use clap::Parser;
|
||||
use color_eyre::{Result, eyre::WrapErr};
|
||||
use color_eyre::{
|
||||
Result,
|
||||
eyre::{OptionExt, WrapErr},
|
||||
};
|
||||
use config::Config;
|
||||
use directories::ProjectDirs;
|
||||
use directories::{BaseDirs, ProjectDirs};
|
||||
use std::path::PathBuf;
|
||||
use tracing::{info, instrument};
|
||||
use tracing::{Level, event, info, instrument};
|
||||
|
||||
// TODO: use [clap(long, short, help_heading = Some(section))]
|
||||
/// Struct of potential arguments.
|
||||
@@ -28,7 +31,7 @@ pub struct Args {
|
||||
|
||||
/// Root directory for file based command structure.
|
||||
#[arg(long)]
|
||||
pub command_dir: Option<String>,
|
||||
pub command_path: Option<String>,
|
||||
|
||||
#[arg(long)]
|
||||
/// Instructions to the model on how to behave.
|
||||
@@ -92,6 +95,27 @@ pub async fn init() -> Result<Setup> {
|
||||
Ok(Setup { config: settings })
|
||||
}
|
||||
|
||||
/// Resolves a path, expanding `~` to the home directory.
|
||||
///
|
||||
/// If the path does not start with `~`, it is returned as is.
|
||||
pub fn resolve_path(path_str: &str) -> Result<PathBuf> {
|
||||
event!(Level::WARN, "resolve_path called with {path_str}");
|
||||
if let Some(stripped) = path_str.strip_prefix("~") {
|
||||
let base_dirs = BaseDirs::new().ok_or_eyre("Unable to expand '~'.")?;
|
||||
event!(
|
||||
Level::DEBUG,
|
||||
"home_dir() decided on {}",
|
||||
base_dirs.home_dir().display()
|
||||
);
|
||||
let relative = stripped
|
||||
.strip_prefix(std::path::MAIN_SEPARATOR_STR)
|
||||
.unwrap_or(stripped);
|
||||
return Ok(base_dirs.home_dir().join(relative));
|
||||
}
|
||||
|
||||
Ok(PathBuf::from(path_str))
|
||||
}
|
||||
|
||||
/// Create a configuration object from arguments.
|
||||
///
|
||||
/// This is exposed for testing purposes.
|
||||
@@ -117,7 +141,12 @@ pub fn make_config(args: Args) -> Result<Config> {
|
||||
.set_override_option("api-key", args.api_key.clone())?
|
||||
.set_override_option("base-url", args.base_url.clone())?
|
||||
.set_override_option("chroot-dir", args.chroot_dir.clone())?
|
||||
.set_override_option("command-path", args.command_dir.clone())?
|
||||
.set_override_option(
|
||||
"command-path",
|
||||
// A path expansion is a panic situation so just unwrap() is fine.
|
||||
args.command_path
|
||||
.map(|p| resolve_path(&p).unwrap().to_string_lossy().to_string()),
|
||||
)?
|
||||
.set_override_option("model", args.model.clone())?
|
||||
.set_override_option("nick-password", args.nick_password.clone())?
|
||||
.set_override_option("instruct", args.instruct.clone())?
|
||||
|
||||
@@ -35,7 +35,7 @@ port = 6667
|
||||
base_url: None, /* Should fail if required and not in file/env? No, base-url is optional
|
||||
* in args */
|
||||
chroot_dir: None,
|
||||
command_dir: None,
|
||||
command_path: None,
|
||||
instruct: None,
|
||||
model: None, // Should fallback to file
|
||||
channels: None,
|
||||
|
||||
Reference in New Issue
Block a user