mod agents;
mod cargo;
mod check;
mod config;
mod dev;
mod hints;
mod init;
mod mcp;
mod render;
mod reply;
mod snapshot;
mod ui;
mod update_check;
mod upgrade;
use std::path::PathBuf;
use std::process::ExitCode;
use clap::{Parser, Subcommand};
pub mod exit {
pub const OK: u8 = 0;
pub const BUILD: u8 = 1;
pub const ENV: u8 = 2;
pub const VERSION: u8 = 3;
}
#[derive(Parser)]
#[command(name = "oxframe", version, about = "Procedural animation in Rust")]
struct Cli {
#[arg(long, short, global = true)]
verbose: bool,
#[arg(long, global = true)]
json: bool,
#[command(subcommand)]
command: Cmd,
}
#[derive(Subcommand)]
enum Cmd {
Init {
name: String,
#[arg(long, value_enum, default_value = "default")]
template: init::Template,
#[arg(long, env = "OXFRAME_SDK_PATH")]
sdk_path: Option<PathBuf>,
},
Dev {
#[arg(long)]
port: Option<u16>,
#[arg(long)]
open: bool,
#[arg(long, conflicts_with = "open")]
no_open: bool,
#[arg(long)]
release: bool,
#[arg(long)]
ui_dir: Option<PathBuf>,
#[arg(long)]
cpu: bool,
},
Render {
#[arg(long)]
out: Option<PathBuf>,
#[arg(long)]
scene: Option<String>,
#[arg(long)]
range: Option<String>,
#[arg(long)]
format: Option<String>,
#[arg(long)]
size: Option<String>,
#[arg(long)]
aspect: Option<String>,
#[arg(long)]
cpu: bool,
#[arg(long)]
no_audio: bool,
},
Check {
#[arg(long)]
frames: bool,
},
Inspect {
#[arg(long)]
at: f64,
#[arg(long)]
scene: Option<String>,
#[arg(long)]
graph: bool,
},
Snapshot {
#[arg(long, required = true)]
at: Vec<f64>,
#[arg(long)]
out: Option<PathBuf>,
#[arg(long)]
scene: Option<String>,
#[arg(long)]
size: Option<String>,
#[arg(long)]
release: bool,
},
Upgrade,
Agents {
#[arg(long)]
force: bool,
},
Mcp,
}
fn main() -> ExitCode {
let cli = Cli::parse();
let filter = if cli.verbose { "debug" } else { "info" };
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new(filter)),
)
.with_target(false)
.without_time()
.with_writer(std::io::stderr)
.init();
if !matches!(cli.command, Cmd::Mcp) {
update_check::run();
}
let done = match cli.command {
Cmd::Init {
name,
template,
sdk_path,
} => {
let code = init::run(&name, template, sdk_path.as_deref());
reply::done(cli.json, code, serde_json::json!({"command": "init"}))
}
Cmd::Dev {
port,
open,
no_open,
release,
ui_dir,
cpu,
} => dev::run(dev::DevArgs {
port,
open,
no_open,
release,
ui_dir,
cpu,
json: cli.json,
}),
Cmd::Render {
out,
scene,
range,
format,
size,
aspect,
cpu,
no_audio,
} => render::run(render::RenderArgs {
out,
scene,
range,
format,
size,
aspect,
cpu,
no_audio,
json: cli.json,
}),
Cmd::Check { frames } => check::run(frames, cli.json),
Cmd::Inspect { at, scene, graph } => check::inspect(at, scene, graph, cli.json),
Cmd::Snapshot {
at,
out,
scene,
size,
release,
} => snapshot::run(snapshot::SnapshotArgs {
at,
out,
scene,
size,
release,
json: cli.json,
}),
Cmd::Agents { force } => {
let code = agents::run(force);
reply::done(cli.json, code, serde_json::json!({"command": "agents"}))
}
Cmd::Upgrade => {
let code = upgrade::run();
reply::done(cli.json, code, serde_json::json!({"command": "upgrade"}))
}
Cmd::Mcp => mcp::serve(),
};
if cli.json && !done.printed {
reply::emit(&done);
}
ExitCode::from(done.code)
}
pub fn error(msg: impl std::fmt::Display) {
let style = anstyle::Style::new()
.bold()
.fg_color(Some(anstyle::AnsiColor::Red.into()));
anstream::eprintln!("{style}error{style:#}: {msg}");
}
pub fn note(msg: impl std::fmt::Display) {
let style = anstyle::Style::new()
.bold()
.fg_color(Some(anstyle::AnsiColor::Cyan.into()));
anstream::eprintln!("{style}oxframe{style:#}: {msg}");
}