use super::verbs::{self, children, create, id, max, moved, payload, update, work_file};
use super::{Context, args, config};
use crate::boundary::{Action, Gesture, Query, help};
pub fn parse(input: &str, ctx: &Context) -> Result<Gesture, String> {
let body = input.trim().strip_prefix('/').ok_or_else(|| {
format!(
"not a command — a line crossing the boundary starts with '/'\n{}",
help::roster()
)
})?;
let (verb, tail) = match body.split_once(char::is_whitespace) {
Some((verb, tail)) => (verb, tail.trim_start()),
None => (body, ""),
};
if let Some(query) = asks_help(verb, tail)? {
return Ok(ask(query));
}
match verb {
"message" => Ok(act(Action::Message {
workspace: args::workspace(ctx, verb)?,
agent: args::agent(ctx, verb)?,
content: args::required(tail, verb, "text to send")?,
})),
"stop" => Ok(act(Action::Stop {
workspace: args::workspace(ctx, verb)?,
agent: args::agent(ctx, verb)?,
children: children(tail)?,
})),
"scan" => {
args::none(tail, verb)?;
Ok(act(Action::Scan {
workspace: args::workspace(ctx, verb)?,
}))
}
"close" => Ok(act(Action::Close {
project: args::project(ctx, verb)?,
id: id(tail, ctx, verb)?,
name: args::name(ctx, verb)?,
})),
"assign" => Ok(act(Action::Assign {
project: args::project(ctx, verb)?,
id: id(tail, ctx, verb)?,
name: args::name(ctx, verb)?,
})),
"release" => Ok(act(Action::Release {
project: args::project(ctx, verb)?,
id: id(tail, ctx, verb)?,
name: args::name(ctx, verb)?,
})),
"move" => moved(tail, ctx, verb),
"create" => create(tail, ctx, verb),
"update" => update(tail, ctx, verb),
"prepare" => Ok(act(Action::Prepare {
workspace: args::workspace(ctx, verb)?,
payload: payload(tail, ctx, verb)?,
})),
"prompt" => Ok(act(Action::Prompt {
prepared: ctx.prepared.clone().ok_or_else(|| {
format!("/{verb}: nothing is prepared — /prepare first, then say the goal")
})?,
goal: args::required(tail, verb, "the goal")?,
})),
"delete-workspace" => Ok(act(Action::DeleteWorkspace {
workspace: args::workspace(ctx, verb)?,
typed: args::required(tail, verb, "the workspace name, typed out")?,
})),
"delete-agent" => Ok(act(Action::DeleteAgent {
workspace: args::workspace(ctx, verb)?,
agent: args::agent(ctx, verb)?,
typed: tail.trim().to_owned(),
})),
"arm" | "disarm" | "flag" => verbs::monitor(verb, tail, ctx),
crate::boundary::codec::FLEET_ARM | crate::boundary::codec::FLEET_DISARM => {
verbs::fleet(verb, tail, ctx)
}
"answer" => verbs::answer(tail, ctx, verb),
"revoke" | "restore" => verbs::floor(verb, tail, ctx),
"config" => config::config(tail, ctx, verb),
"marks" => config::marks(tail, ctx, verb),
"model" => config::model(tail, ctx, verb),
"fork" => super::fork::fork(tail, ctx, verb),
"ack" => args::none(tail, verb).map(|()| act(Action::Ack)),
"seen" => verbs::seen(tail, ctx, verb),
"clear-trail" => args::none(tail, verb).map(|()| act(Action::ClearTrail)),
other => queries(other, tail, ctx),
}
}
fn queries(verb: &str, tail: &str, ctx: &Context) -> Result<Gesture, String> {
match verb {
"workspaces" => args::none(tail, verb).map(|()| ask(Query::Workspaces)),
"conversations" => {
args::none(tail, verb)?;
Ok(ask(Query::Conversations {
workspace: args::workspace(ctx, verb)?,
}))
}
"balls" => args::none(tail, verb).map(|()| ask(Query::Balls)),
"work-diff" => Ok(ask(Query::WorkDiff {
workspace: args::workspace(ctx, verb)?,
file: work_file(tail, verb)?,
})),
"board" => args::none(tail, verb).map(|()| ask(Query::Board)),
"providers" => {
args::none(tail, verb)?;
Ok(ask(Query::Providers {
workspace: args::workspace(ctx, verb)?,
}))
}
"attention" => args::none(tail, verb).map(|()| ask(Query::Attention)),
"ops" => Ok(ask(Query::Ops { max: max(tail)? })),
"search" => Ok(ask(Query::Search {
text: tail.trim().to_owned(),
})),
other => Err(format!("unknown command /{other}\n{}", help::roster())),
}
}
fn asks_help(verb: &str, tail: &str) -> Result<Option<Query>, String> {
if matches!(tail.trim(), "--help" | "-h") && help::known(verb) {
return Ok(Some(Query::Help {
verb: Some(verb.to_owned()),
}));
}
if !(verb.is_empty() || verb == "help") {
return Ok(None);
}
match args::optional_word(tail, "help")? {
None => Ok(Some(Query::Help { verb: None })),
Some(about) if help::known(&about) => Ok(Some(Query::Help { verb: Some(about) })),
Some(about) => Err(format!("unknown command /{about}\n{}", help::roster())),
}
}
fn act(action: Action) -> Gesture {
Gesture::Act(action)
}
fn ask(query: Query) -> Gesture {
Gesture::Ask(query)
}