From 321adfb9e9d05c1bdb3d13e49d6aa416b353434b Mon Sep 17 00:00:00 2001 From: limiteinductive Date: Sun, 29 Mar 2026 22:44:21 +0200 Subject: [PATCH] Colony CLI: inbox + ack commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - colony inbox [--json] — show unacked inbox items - colony ack [--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) --- crates/colony-cli/src/client.rs | 18 ++++++++++++ crates/colony-cli/src/main.rs | 52 +++++++++++++++++++++++++++++++++ crates/colony-types/src/lib.rs | 2 +- 3 files changed, 71 insertions(+), 1 deletion(-) diff --git a/crates/colony-cli/src/client.rs b/crates/colony-cli/src/client.rs index b8b8723..b1a00df 100644 --- a/crates/colony-cli/src/client.rs +++ b/crates/colony-cli/src/client.rs @@ -79,6 +79,24 @@ impl ColonyClient { res.json().await.unwrap() } + pub async fn get_inbox(&self) -> Vec { + let res = self.http + .get(self.url(&format!("/api/inbox?{}", self.config.user_query()))) + .send().await.unwrap_or_else(|e| { eprintln!("colony unreachable: {e}"); process::exit(1); }); + if !res.status().is_success() { self.handle_error(res).await; } + res.json().await.unwrap() + } + + pub async fn ack_inbox(&self, ids: &[i64]) -> serde_json::Value { + let body = AckRequest { ids: ids.to_vec() }; + let res = self.http + .post(self.url("/api/inbox/ack")) + .json(&body) + .send().await.unwrap_or_else(|e| { eprintln!("colony unreachable: {e}"); process::exit(1); }); + if !res.status().is_success() { self.handle_error(res).await; } + res.json().await.unwrap() + } + /// Resolve channel name to ID. Fetches channel list and finds by name. pub async fn resolve_channel(&self, name: &str) -> String { let channels = self.get_channels().await; diff --git a/crates/colony-cli/src/main.rs b/crates/colony-cli/src/main.rs index a6081c2..c2132c6 100644 --- a/crates/colony-cli/src/main.rs +++ b/crates/colony-cli/src/main.rs @@ -56,6 +56,21 @@ enum Commands { #[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, + /// Ack all unacked items + #[arg(long)] + all: bool, + #[arg(long)] + quiet: bool, + }, /// Initialize .colony.toml Init { #[arg(long)] @@ -172,6 +187,43 @@ async fn run_command(cmd: Commands, client: &ColonyClient) { } } + 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::>() + } 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!(), } } diff --git a/crates/colony-types/src/lib.rs b/crates/colony-types/src/lib.rs index f0d47ae..f7f3794 100644 --- a/crates/colony-types/src/lib.rs +++ b/crates/colony-types/src/lib.rs @@ -114,7 +114,7 @@ pub struct InboxQuery { pub user: String, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] pub struct AckRequest { pub ids: Vec, }