mod commands;
mod help;
mod types;
mod validate;
use anyhow::Result;
use clap::{Parser, Subcommand};
pub(crate) use types::{
map_merge_policy, map_open_pr_policy, OpenPrFormat, OpenPrPolicy, OutputFormat,
};
use validate::{
resolve_format, validate_budget_time, validate_decision_text, validate_goal_id,
validate_goal_text, validate_optional_budget_tokens, validate_optional_budget_usd,
validate_optional_max_agents,
};
#[derive(Parser, Debug)]
#[command(
about = "Goal runtime (durable autonomous controller scaffold)",
long_about = help::GOAL_LONG_ABOUT,
after_help = help::GOAL_TOP_AFTER_HELP
)]
pub struct Args {
#[command(subcommand)]
pub(crate) command: GoalCommands,
}
#[derive(Subcommand, Debug)]
pub(crate) enum GoalCommands {
#[command(after_help = help::GOAL_RUN_AFTER_HELP)]
Run {
#[arg(value_name = "GOAL")]
goal: String,
#[arg(long)]
until_ready: bool,
#[arg(long, value_name = "DURATION")]
budget_time: Option<String>,
#[arg(long, value_name = "TOKENS")]
budget_tokens: Option<u64>,
#[arg(long, value_name = "USD")]
budget_usd: Option<f64>,
#[arg(long, value_name = "N")]
max_agents: Option<usize>,
#[arg(long, value_enum, default_value = "local")]
policy: types::OpenPrPolicy,
#[arg(long, value_enum, default_value = "disabled")]
merge_policy: types::MergePolicy,
#[arg(long)]
slice_execution: bool,
#[arg(long)]
enforce_protection: bool,
#[arg(long)]
no_llm_planner: bool,
#[arg(long, default_value = "8000")]
planner_token_budget: u32,
},
#[command(after_help = help::GOAL_PLAN_AFTER_HELP)]
Plan {
#[arg(value_name = "GOAL")]
goal: String,
},
#[command(after_help = help::GOAL_LIST_AFTER_HELP)]
List,
#[command(after_help = help::GOAL_STATUS_AFTER_HELP)]
Status {
#[arg(default_value = "latest", value_name = "GOAL_ID")]
goal_id: String,
},
#[command(after_help = help::GOAL_SHOW_AFTER_HELP)]
Show {
#[arg(default_value = "latest", value_name = "GOAL_ID")]
goal_id: String,
#[arg(short, long, value_enum, default_value = "text")]
format: types::OutputFormat,
#[arg(long, conflicts_with = "format")]
json: bool,
},
#[command(after_help = help::GOAL_PROOF_AFTER_HELP)]
Proof {
#[arg(default_value = "latest", value_name = "GOAL_ID")]
goal_id: String,
#[arg(short, long, value_enum, default_value = "text")]
format: types::OutputFormat,
#[arg(long, conflicts_with = "format")]
json: bool,
},
#[command(after_help = help::GOAL_OPEN_PR_AFTER_HELP)]
OpenPr {
#[arg(default_value = "latest", value_name = "GOAL_ID")]
goal_id: String,
#[arg(long)]
dry_run: bool,
#[arg(long)]
draft: bool,
#[arg(long, value_enum, default_value = "local")]
policy: types::OpenPrPolicy,
#[arg(long, value_name = "BRANCH")]
base_branch: Option<String>,
#[arg(short, long, value_enum, default_value = "markdown")]
format: types::OpenPrFormat,
},
#[command(after_help = help::GOAL_ACCEPT_AFTER_HELP)]
Accept {
#[arg(default_value = "latest", value_name = "GOAL_ID")]
goal_id: String,
#[arg(long, value_name = "TEXT")]
summary: String,
},
#[command(after_help = help::GOAL_REJECT_AFTER_HELP)]
Reject {
#[arg(default_value = "latest", value_name = "GOAL_ID")]
goal_id: String,
#[arg(long, value_name = "TEXT")]
reason: String,
},
#[command(after_help = help::GOAL_REPLAY_AFTER_HELP)]
Replay {
#[arg(default_value = "latest", value_name = "GOAL_ID")]
goal_id: String,
#[arg(short, long, value_enum, default_value = "text")]
format: types::OutputFormat,
#[arg(long, conflicts_with = "format")]
json: bool,
},
#[command(after_help = help::GOAL_BUDGET_AFTER_HELP)]
Budget {
#[arg(default_value = "latest", value_name = "GOAL_ID")]
goal_id: String,
#[arg(short, long, value_enum, default_value = "text")]
format: types::OutputFormat,
#[arg(long, conflicts_with = "format")]
json: bool,
},
#[command(after_help = help::GOAL_BUDGET_ADD_AFTER_HELP)]
BudgetAdd {
#[arg(default_value = "latest", value_name = "GOAL_ID")]
goal_id: String,
#[arg(long, value_name = "DURATION")]
time: Option<String>,
#[arg(long, value_name = "TOKENS")]
tokens: Option<u64>,
#[arg(long, value_name = "USD")]
usd: Option<f64>,
},
#[command(after_help = help::GOAL_VERIFY_AFTER_HELP)]
Verify {
#[arg(default_value = "latest", value_name = "GOAL_ID")]
goal_id: String,
},
#[command(after_help = help::GOAL_EXECUTE_AFTER_HELP)]
Execute {
#[arg(default_value = "latest", value_name = "GOAL_ID")]
goal_id: String,
},
#[command(after_help = help::GOAL_REVIEW_AFTER_HELP)]
Review {
#[arg(default_value = "latest", value_name = "GOAL_ID")]
goal_id: String,
},
#[command(after_help = help::GOAL_PAUSE_AFTER_HELP)]
Pause {
#[arg(default_value = "latest", value_name = "GOAL_ID")]
goal_id: String,
},
#[command(after_help = help::GOAL_RESUME_AFTER_HELP)]
Resume {
#[arg(default_value = "latest", value_name = "GOAL_ID")]
goal_id: String,
#[arg(long)]
auto: bool,
},
#[command(after_help = help::GOAL_CANCEL_AFTER_HELP)]
Cancel {
#[arg(default_value = "latest", value_name = "GOAL_ID")]
goal_id: String,
},
#[command(after_help = help::GOAL_MERGE_AFTER_HELP)]
Merge {
#[arg(default_value = "latest", value_name = "GOAL_ID")]
goal_id: String,
},
}
pub(crate) async fn run(args: Args) -> Result<()> {
match args.command {
GoalCommands::Run {
goal,
until_ready,
budget_time,
budget_tokens,
budget_usd,
max_agents,
policy,
merge_policy,
slice_execution,
enforce_protection,
no_llm_planner,
planner_token_budget,
} => {
let goal = validate_goal_text(&goal)?;
let budget_time = validate_budget_time(budget_time.as_deref(), "--budget-time", false)?;
validate_optional_budget_tokens(budget_tokens)?;
validate_optional_budget_usd(budget_usd)?;
validate_optional_max_agents(max_agents)?;
commands::cmd_run(
goal,
crate::runtime::goal::CreateGoalOptions {
until_ready,
budget_time,
budget_tokens,
budget_usd,
max_agents,
delivery_policy: map_open_pr_policy(policy),
merge_policy: map_merge_policy(merge_policy),
slice_execution,
enforce_protection,
},
no_llm_planner,
planner_token_budget,
)
.await
}
GoalCommands::Plan { goal } => {
let goal = validate_goal_text(&goal)?;
commands::cmd_plan(goal).await
}
GoalCommands::List => commands::cmd_list().await,
GoalCommands::Status { goal_id } => {
let goal_id = validate_goal_id(&goal_id)?;
commands::cmd_status(goal_id).await
}
GoalCommands::Show {
goal_id,
format,
json,
} => {
let goal_id = validate_goal_id(&goal_id)?;
commands::cmd_show(goal_id, resolve_format(format, json)).await
}
GoalCommands::Proof {
goal_id,
format,
json,
} => {
let goal_id = validate_goal_id(&goal_id)?;
commands::cmd_proof(goal_id, resolve_format(format, json)).await
}
GoalCommands::OpenPr {
goal_id,
dry_run,
draft,
policy,
base_branch,
format,
} => {
let goal_id = validate_goal_id(&goal_id)?;
commands::cmd_open_pr(goal_id, dry_run, draft, policy, base_branch, format).await
}
GoalCommands::Accept { goal_id, summary } => {
let goal_id = validate_goal_id(&goal_id)?;
let summary = validate_decision_text(&summary, "--summary")?;
commands::cmd_accept(goal_id, summary).await
}
GoalCommands::Reject { goal_id, reason } => {
let goal_id = validate_goal_id(&goal_id)?;
let reason = validate_decision_text(&reason, "--reason")?;
commands::cmd_reject(goal_id, reason).await
}
GoalCommands::Replay {
goal_id,
format,
json,
} => {
let goal_id = validate_goal_id(&goal_id)?;
commands::cmd_replay(goal_id, resolve_format(format, json)).await
}
GoalCommands::Budget {
goal_id,
format,
json,
} => {
let goal_id = validate_goal_id(&goal_id)?;
commands::cmd_budget(goal_id, resolve_format(format, json)).await
}
GoalCommands::BudgetAdd {
goal_id,
time,
tokens,
usd,
} => {
let goal_id = validate_goal_id(&goal_id)?;
let time = validate_budget_time(time.as_deref(), "--time", true)?;
validate_optional_budget_tokens(tokens)?;
validate_optional_budget_usd(usd)?;
if time.is_none() && tokens.is_none() && usd.is_none() {
anyhow::bail!(
"Provide at least one budget extension: --time, --tokens, or --usd.\n\n\
Examples:\n \
omk goal budget-add latest --time 1h\n \
omk goal budget-add latest --tokens 500000\n \
omk goal budget-add latest --usd 5"
);
}
commands::cmd_budget_add(goal_id, time, tokens, usd).await
}
GoalCommands::Verify { goal_id } => {
let goal_id = validate_goal_id(&goal_id)?;
commands::cmd_verify(goal_id).await
}
GoalCommands::Execute { goal_id } => {
let goal_id = validate_goal_id(&goal_id)?;
commands::cmd_execute(goal_id).await
}
GoalCommands::Review { goal_id } => {
let goal_id = validate_goal_id(&goal_id)?;
commands::cmd_review(goal_id).await
}
GoalCommands::Pause { goal_id } => {
let goal_id = validate_goal_id(&goal_id)?;
commands::cmd_pause(goal_id).await
}
GoalCommands::Resume { goal_id, auto } => {
if auto {
commands::cmd_resume_auto().await
} else {
let goal_id = validate_goal_id(&goal_id)?;
commands::cmd_resume(goal_id).await
}
}
GoalCommands::Cancel { goal_id } => {
let goal_id = validate_goal_id(&goal_id)?;
commands::cmd_cancel(goal_id).await
}
GoalCommands::Merge { goal_id } => {
let goal_id = validate_goal_id(&goal_id)?;
commands::cmd_merge(goal_id).await
}
}
}