- colony inbox [--json] — show unacked inbox items - colony ack <id> [--all] [--quiet] — ack items - Client methods: get_inbox(), ack_inbox() - AckRequest gets Serialize derive for CLI use Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
230 lines
6.6 KiB
Rust
230 lines
6.6 KiB
Rust
mod client;
|
|
mod config;
|
|
|
|
use clap::{Parser, Subcommand};
|
|
use client::ColonyClient;
|
|
use colony_types::*;
|
|
use config::Config;
|
|
|
|
#[derive(Parser)]
|
|
#[command(name = "colony", about = "Ape Colony CLI — chat from the terminal")]
|
|
struct Cli {
|
|
#[command(subcommand)]
|
|
command: Commands,
|
|
}
|
|
|
|
#[derive(Subcommand)]
|
|
enum Commands {
|
|
/// Show current identity
|
|
Whoami {
|
|
#[arg(long)]
|
|
json: bool,
|
|
},
|
|
/// List channels
|
|
Channels {
|
|
#[arg(long)]
|
|
json: bool,
|
|
},
|
|
/// Read messages from a channel
|
|
Read {
|
|
channel: String,
|
|
#[arg(long)]
|
|
since: Option<i64>,
|
|
#[arg(long)]
|
|
json: bool,
|
|
},
|
|
/// Post a message to a channel
|
|
Post {
|
|
channel: String,
|
|
message: String,
|
|
#[arg(long, default_value = "text")]
|
|
r#type: String,
|
|
#[arg(long)]
|
|
reply_to: Option<String>,
|
|
#[arg(long)]
|
|
metadata: Option<String>,
|
|
#[arg(long)]
|
|
json: bool,
|
|
#[arg(long)]
|
|
quiet: bool,
|
|
},
|
|
/// Create a new channel
|
|
CreateChannel {
|
|
name: String,
|
|
#[arg(long, default_value = "")]
|
|
description: String,
|
|
#[arg(long)]
|
|
json: bool,
|
|
},
|
|
/// Check inbox (unacked mentions + activity)
|
|
Inbox {
|
|
#[arg(long)]
|
|
json: bool,
|
|
},
|
|
/// Acknowledge inbox items
|
|
Ack {
|
|
/// Inbox item IDs to ack
|
|
ids: Vec<i64>,
|
|
/// Ack all unacked items
|
|
#[arg(long)]
|
|
all: bool,
|
|
#[arg(long)]
|
|
quiet: bool,
|
|
},
|
|
/// Initialize .colony.toml
|
|
Init {
|
|
#[arg(long)]
|
|
api_url: String,
|
|
#[arg(long)]
|
|
user: String,
|
|
#[arg(long)]
|
|
token: Option<String>,
|
|
},
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() {
|
|
let cli = Cli::parse();
|
|
|
|
match cli.command {
|
|
Commands::Init { api_url, user, token } => {
|
|
let config = Config {
|
|
api_url,
|
|
user,
|
|
token,
|
|
password: None,
|
|
};
|
|
let toml_str = toml::to_string_pretty(&config).unwrap();
|
|
std::fs::write(".colony.toml", &toml_str).unwrap();
|
|
eprintln!("wrote .colony.toml");
|
|
}
|
|
|
|
_ => {
|
|
let config = match Config::load() {
|
|
Ok(c) => c,
|
|
Err(e) => {
|
|
eprintln!("{}", e);
|
|
std::process::exit(1);
|
|
}
|
|
};
|
|
let client = ColonyClient::new(config);
|
|
run_command(cli.command, &client).await;
|
|
}
|
|
}
|
|
}
|
|
|
|
async fn run_command(cmd: Commands, client: &ColonyClient) {
|
|
match cmd {
|
|
Commands::Whoami { json } => {
|
|
let me = client.get_me().await;
|
|
if json {
|
|
println!("{}", serde_json::to_string(&me).unwrap());
|
|
} else {
|
|
println!("{} ({}) — {}", me.username, match me.role {
|
|
UserRole::Ape => "ape",
|
|
UserRole::Agent => "agent",
|
|
}, client.config.api_url);
|
|
}
|
|
}
|
|
|
|
Commands::Channels { json } => {
|
|
let channels = client.get_channels().await;
|
|
if json {
|
|
println!("{}", serde_json::to_string(&channels).unwrap());
|
|
} else {
|
|
for ch in &channels {
|
|
println!("#{:<16} {}", ch.name, ch.description);
|
|
}
|
|
}
|
|
}
|
|
|
|
Commands::Read { channel, since, json } => {
|
|
let channel_id = client.resolve_channel(&channel).await;
|
|
let messages = client.get_messages(&channel_id, since).await;
|
|
if json {
|
|
println!("{}", serde_json::to_string(&messages).unwrap());
|
|
} else {
|
|
for msg in &messages {
|
|
println!("[{}] {}: {}", msg.seq, msg.user.username, msg.content);
|
|
}
|
|
}
|
|
}
|
|
|
|
Commands::Post { channel, message, r#type, reply_to, metadata, json, quiet } => {
|
|
let channel_id = client.resolve_channel(&channel).await;
|
|
let msg_type = match r#type.as_str() {
|
|
"code" => MessageType::Code,
|
|
"result" => MessageType::Result,
|
|
"error" => MessageType::Error,
|
|
"plan" => MessageType::Plan,
|
|
_ => MessageType::Text,
|
|
};
|
|
let meta_value = metadata.and_then(|m| serde_json::from_str(&m).ok());
|
|
let reply = reply_to.and_then(|r| uuid::Uuid::parse_str(&r).ok());
|
|
|
|
let body = PostMessage {
|
|
content: message,
|
|
r#type: msg_type,
|
|
metadata: meta_value,
|
|
reply_to: reply,
|
|
};
|
|
|
|
let msg = client.post_message(&channel_id, &body).await;
|
|
if json {
|
|
println!("{}", serde_json::to_string(&msg).unwrap());
|
|
} else if !quiet {
|
|
println!("posted message #{} to #{}", msg.seq, channel);
|
|
}
|
|
}
|
|
|
|
Commands::CreateChannel { name, description, json } => {
|
|
let body = CreateChannel { name: name.clone(), description };
|
|
let ch = client.create_channel(&body).await;
|
|
if json {
|
|
println!("{}", serde_json::to_string(&ch).unwrap());
|
|
} else {
|
|
println!("created #{}", name);
|
|
}
|
|
}
|
|
|
|
Commands::Inbox { json } => {
|
|
let items = client.get_inbox().await;
|
|
if json {
|
|
println!("{}", serde_json::to_string(&items).unwrap());
|
|
} else if items.is_empty() {
|
|
println!("inbox empty");
|
|
} else {
|
|
for item in &items {
|
|
println!("[{}] #{} [{}] {}: {} ({})",
|
|
item.id,
|
|
item.channel_name,
|
|
item.message.seq,
|
|
item.message.user.username,
|
|
item.message.content,
|
|
item.trigger,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
Commands::Ack { ids, all, quiet } => {
|
|
let to_ack = if all {
|
|
let items = client.get_inbox().await;
|
|
items.iter().map(|i| i.id).collect::<Vec<_>>()
|
|
} else {
|
|
ids
|
|
};
|
|
if to_ack.is_empty() {
|
|
if !quiet { println!("nothing to ack"); }
|
|
} else {
|
|
let result = client.ack_inbox(&to_ack).await;
|
|
if !quiet {
|
|
println!("acked {} items", result["acked"]);
|
|
}
|
|
}
|
|
}
|
|
|
|
Commands::Init { .. } => unreachable!(),
|
|
}
|
|
}
|