Files
agentos/crates/agentos-cli/src/main.rs
T
2026-08-02 15:26:10 +08:00

202 lines
6.0 KiB
Rust

use agentos_core::{CommitId, WorldlineId};
use agentos_kernel::ProcessCredentials;
use agentos_runtime::{BackendSelection, Runtime, RuntimeConfig, doctor};
use agentos_worldline::WorldlineStore;
use anyhow::{Result, bail};
use clap::{Parser, Subcommand};
use serde_json::json;
use std::path::PathBuf;
#[derive(Debug, Parser)]
#[command(name = "agentos", version, about = "Bare-Linux operating-system agent")]
struct Cli {
#[arg(long, global = true)]
state_dir: Option<PathBuf>,
#[arg(long, global = true)]
backend: Option<String>,
#[arg(long, global = true)]
fake_model: bool,
#[arg(long, global = true)]
json: bool,
#[arg(long)]
agent_once: Option<String>,
#[arg(long)]
doctor: bool,
#[command(subcommand)]
command: Option<Command>,
}
#[derive(Debug, Subcommand)]
enum Command {
Doctor,
Agent {
#[command(subcommand)]
command: AgentCommand,
},
Worldline {
#[command(subcommand)]
command: WorldlineCommand,
},
}
#[derive(Debug, Subcommand)]
enum AgentCommand {
Once { prompt: String },
}
#[derive(Debug, Subcommand)]
enum WorldlineCommand {
Init,
Status,
Branch {
id: Option<String>,
},
Diff {
id: String,
},
Commit {
id: String,
#[arg(short, long)]
message: String,
},
Rollback {
commit: String,
#[arg(short, long, default_value = "rollback")]
message: String,
},
Discard {
id: String,
},
Log {
#[arg(short, long, default_value_t = 20)]
limit: usize,
},
}
#[tokio::main]
async fn main() -> Result<()> {
let cli = Cli::parse();
if (cli.agent_once.is_some() || cli.doctor) && cli.command.is_some() {
bail!("legacy flags --doctor/--agent-once cannot be combined with a subcommand");
}
let mut config = RuntimeConfig::from_env()?;
if let Some(state_dir) = &cli.state_dir {
config.state_dir.clone_from(state_dir);
}
if let Some(backend) = &cli.backend {
config.backend = backend.parse()?;
}
if cli.fake_model {
config.backend = BackendSelection::Fake;
}
if cli.doctor || matches!(cli.command, Some(Command::Doctor)) {
let report = doctor(&config)?;
print_value(&serde_json::to_value(report)?, cli.json);
return Ok(());
}
if let Some(prompt) = cli.agent_once {
return run_agent(config, &prompt, cli.json).await;
}
match cli.command {
Some(Command::Agent {
command: AgentCommand::Once { prompt },
}) => run_agent(config, &prompt, cli.json).await,
Some(Command::Worldline { command }) => run_worldline(&config, command, cli.json),
Some(Command::Doctor) => unreachable!(),
None => bail!("select --doctor, --agent-once, or a subcommand"),
}
}
async fn run_agent(config: RuntimeConfig, prompt: &str, json_output: bool) -> Result<()> {
let runtime = Runtime::initialize(config)?;
let result = runtime.run_once(prompt).await?;
if json_output {
println!("{}", serde_json::to_string_pretty(&result)?);
} else {
println!("{}", result.content);
}
Ok(())
}
fn run_worldline(
config: &RuntimeConfig,
command: WorldlineCommand,
json_output: bool,
) -> Result<()> {
let root = config.worldline_root();
let store = match command {
WorldlineCommand::Init => {
let store = WorldlineStore::initialize(&root)?;
print_value(
&json!({
"root": store.root(),
"backend": store.backend(),
"current": store.current_path(),
}),
json_output,
);
return Ok(());
}
_ if root.join("backend.json").is_file() => WorldlineStore::open(&root)?,
_ => WorldlineStore::initialize(&root)?,
};
let uid = ProcessCredentials::current().effective_uid;
match command {
WorldlineCommand::Init => unreachable!(),
WorldlineCommand::Status => print_value(
&json!({
"root": store.root(),
"backend": store.backend(),
"head": store.head()?,
"current": store.current_path(),
}),
json_output,
),
WorldlineCommand::Branch { id } => {
let id = id.map_or_else(|| Ok(WorldlineId::new()), WorldlineId::parse)?;
let path = store.create_branch(&id)?;
print_value(&json!({"worldline_id": id, "path": path}), json_output);
}
WorldlineCommand::Diff { id } => {
let id = WorldlineId::parse(id)?;
print_value(&serde_json::to_value(store.diff_branch(&id)?)?, json_output);
}
WorldlineCommand::Commit { id, message } => {
let commit = store.commit_branch(&WorldlineId::parse(id)?, message, uid)?;
print_value(&serde_json::to_value(commit)?, json_output);
}
WorldlineCommand::Rollback { commit, message } => {
let commit = store.rollback_to(&CommitId::parse(commit)?, message, uid)?;
print_value(&serde_json::to_value(commit)?, json_output);
}
WorldlineCommand::Discard { id } => {
let id = WorldlineId::parse(id)?;
print_value(
&json!({"worldline_id": id, "discarded": store.discard_branch(&id)?}),
json_output,
);
}
WorldlineCommand::Log { limit } => {
print_value(&serde_json::to_value(store.log(limit)?)?, json_output);
}
}
Ok(())
}
fn print_value(value: &serde_json::Value, pretty: bool) {
if pretty {
println!(
"{}",
serde_json::to_string_pretty(value).expect("JSON value serializes")
);
} else if let Some(content) = value.as_str() {
println!("{content}");
} else {
println!(
"{}",
serde_json::to_string_pretty(value).expect("JSON value serializes")
);
}
}