mod chat;
mod decisions;
mod gateway;
mod login;
mod model;
mod profile;
mod run;
mod tape;
use std::path::PathBuf;
use std::sync::Arc;
use clap::Subcommand;
use serde_json::Value;
use crate::builtin::BuiltinImpl;
use crate::framework::EliFramework;
#[derive(Debug, Subcommand)]
pub enum CliCommand {
Run {
message: String,
#[arg(long, default_value = "cli")]
channel: String,
#[arg(long, default_value = "local")]
chat_id: String,
#[arg(long, default_value = "human")]
sender_id: String,
#[arg(long)]
session_id: Option<String>,
},
Chat {
#[arg(long, default_value = "local")]
chat_id: String,
#[arg(long)]
session_id: Option<String>,
},
Login {
provider: String,
#[arg(long)]
codex_home: Option<PathBuf>,
#[arg(long, default_value_t = true)]
browser: bool,
#[arg(long)]
manual: bool,
#[arg(long, default_value_t = 300.0)]
timeout: f64,
#[arg(long)]
api_key: bool,
},
Use {
profile: String,
},
Status,
#[command(hide = true)]
Hooks,
Model {
name: Option<String>,
},
Gateway,
Tape {
#[arg(long, default_value_t = 7700)]
port: u16,
#[arg(long)]
dir: Option<std::path::PathBuf>,
},
Decisions {
#[command(subcommand)]
action: DecisionAction,
},
}
#[derive(Debug, Subcommand)]
pub enum DecisionAction {
List,
Remove {
index: usize,
},
Export,
}
pub async fn execute(cmd: CliCommand) -> anyhow::Result<()> {
match cmd {
CliCommand::Run {
message,
channel,
chat_id,
sender_id,
session_id,
} => run::run_command(message, channel, chat_id, sender_id, session_id).await,
CliCommand::Chat {
chat_id,
session_id,
} => chat::chat_command(chat_id, session_id).await,
CliCommand::Login {
provider,
codex_home,
browser,
manual,
timeout,
api_key,
} => login::login_command(provider, codex_home, browser, manual, timeout, api_key).await,
CliCommand::Use { profile } => profile::use_command(profile),
CliCommand::Model { name } => model::model_command(name).await,
CliCommand::Status => profile::status_command(),
CliCommand::Hooks => {
hooks_command().await;
Ok(())
}
CliCommand::Gateway => gateway::gateway_command().await,
CliCommand::Tape { port, dir } => tape::tape_command(port, dir).await,
CliCommand::Decisions { action } => match action {
DecisionAction::List => decisions::list_command().await,
DecisionAction::Remove { index } => decisions::remove_command(index).await,
DecisionAction::Export => decisions::export_command().await,
},
}
}
async fn hooks_command() {
let framework = builtin_framework().await;
let mut report: Vec<_> = framework.hook_report().await.into_iter().collect();
report.sort_by(|a, b| a.0.cmp(&b.0));
println!("Hook implementations:");
for (name, mut plugins) in report {
plugins.sort();
println!(" {name}:");
if plugins.is_empty() {
println!(" - (none)");
continue;
}
for plugin in plugins {
println!(" - {plugin}");
}
}
}
pub(crate) fn strip_fake_tool_calls(text: &str) -> String {
let re = regex::Regex::new(r"(?s)<function_calls>.*?</function_calls>").unwrap();
let cleaned = re.replace_all(text, "");
cleaned.trim().to_owned()
}
async fn builtin_framework() -> Arc<EliFramework> {
let framework = Arc::new(EliFramework::new());
framework
.register_plugin("builtin", Arc::new(BuiltinImpl::new()))
.await;
framework
}
fn print_cli_outbounds(outbounds: &[Value]) {
for outbound in outbounds {
let content = outbound_string_field(outbound, "content");
if !content.trim().is_empty() {
println!("{content}");
}
}
}
fn outbound_string_field(outbound: &Value, key: &str) -> String {
match outbound.get(key) {
Some(Value::String(value)) => value.clone(),
Some(other) => other.to_string(),
None => String::new(),
}
}