use super::balls::read as ball;
use super::verbs::{self, children, payload};
use super::{Context, args, config, queries};
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")?,
})),
"interrupt" => Ok(act(Action::Interrupt {
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)?,
}))
}
"nudge" => {
args::none(tail, verb)?;
Ok(act(Action::Nudge {
workspace: args::workspace(ctx, verb)?,
agent: args::agent(ctx, verb)?,
}))
}
"retarget" => verbs::retarget(tail, ctx, verb),
"close" | "assign" | "release" | "create" | "update" => ball(verb, tail, ctx),
"prepare" => Ok(act(Action::Prepare {
workspace: args::workspace(ctx, verb)?,
payload: payload(tail, ctx, verb)?,
})),
"prompt" => verbs::prompt(tail, ctx, verb),
"fan" | "retire" | "deliver" => super::fan::read(verb, tail, ctx),
"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(),
})),
"invoke" | "complete" => super::tools::route(verb, tail),
"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),
"effort" => config::effort(tail, ctx, verb),
"priority" => config::priority(tail, ctx, verb),
crate::boundary::codec::LOGIN => match args::optional_word(tail, verb)? {
Some(provider) => Ok(act(Action::Login {
workspace: args::workspace(ctx, verb)?,
provider,
})),
None => Err(format!("/{verb}: usage: /{verb} <provider>")),
},
"fork" => super::fork::fork(tail, ctx, verb),
"ack" => args::none(tail, verb).map(|()| act(Action::Ack)),
"seen" => verbs::seen(tail, ctx, verb),
crate::boundary::codec::PIN | crate::boundary::codec::UNPIN => {
args::none(tail, verb)?;
Ok(act(Action::Pin {
workspace: args::workspace(ctx, verb)?,
pinned: verb == crate::boundary::codec::PIN,
}))
}
"clear-trail" => args::none(tail, verb).map(|()| act(Action::ClearTrail)),
"advertise" => {
let text = args::required(tail, verb, "the tool set, as JSON")?;
let set = serde_json::from_str(&text).map_err(|e| format!("/{verb}: {e}"))?;
Ok(act(Action::Advertise {
tools: crate::registry::tools::decode(&set)?,
}))
}
crate::boundary::codec::ENROLL => verbs::enroll(tail, ctx, verb),
other => queries::queries(other, tail, ctx),
}
}
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())),
}
}
pub(super) fn act(action: Action) -> Gesture {
Gesture::Act(action)
}
pub(super) fn ask(query: Query) -> Gesture {
Gesture::Ask(query)
}