ai_memory/cli/commands/
inbox.rs1use anyhow::Result;
12use clap::Args;
13use serde_json::{Value, json};
14
15use crate::cli::CliOutput;
16use crate::storage as db;
17
18#[derive(Args, Debug, Clone)]
20pub struct InboxArgs {
21 #[arg(long = "agent-id", value_name = "AGENT_ID")]
23 pub agent_id: Option<String>,
24
25 #[arg(long = "unread-only")]
27 pub unread_only: bool,
28
29 #[arg(long, value_name = "N")]
31 pub limit: Option<u32>,
32
33 #[arg(long)]
35 pub json: bool,
36}
37
38pub fn cmd_inbox(
46 db_path: &std::path::Path,
47 args: &InboxArgs,
48 out: &mut CliOutput<'_>,
49) -> Result<()> {
50 let conn = db::open(db_path)?;
51
52 let mut params = json!({});
53 if let Some(a) = &args.agent_id {
54 params["agent_id"] = json!(a);
55 }
56 if args.unread_only {
57 params[crate::models::field_names::UNREAD_ONLY] = json!(true);
58 }
59 if let Some(l) = args.limit {
60 params["limit"] = json!(l);
61 }
62
63 let envelope = crate::mcp::handle_inbox(&conn, ¶ms, None, None)
66 .map_err(|e| anyhow::anyhow!("inbox: {e}"))?;
67
68 if args.json {
69 writeln!(out.stdout, "{}", serde_json::to_string(&envelope)?)?;
70 return Ok(());
71 }
72
73 let count = envelope.get("count").and_then(Value::as_u64).unwrap_or(0);
74 let owner = envelope
75 .get("agent_id")
76 .and_then(Value::as_str)
77 .unwrap_or("?");
78 writeln!(out.stdout, "inbox: {count} message(s) for {owner}")?;
79 if let Some(arr) = envelope.get("messages").and_then(Value::as_array) {
80 for m in arr {
81 let id = m.get("id").and_then(Value::as_str).unwrap_or("?");
82 let from = m.get("from").and_then(Value::as_str).unwrap_or("?");
83 let title = m.get("title").and_then(Value::as_str).unwrap_or("");
84 let read = m.get("read").and_then(Value::as_bool).unwrap_or(false);
85 writeln!(out.stdout, " {id} from={from} read={read} {title}")?;
86 }
87 }
88 Ok(())
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94 use crate::cli::test_utils::TestEnv;
95
96 #[test]
97 fn inbox_cli_empty_db_returns_zero() {
98 let mut env = TestEnv::fresh();
99 let db = env.db_path.clone();
100 let args = InboxArgs {
101 agent_id: Some("ai:alice".into()),
102 unread_only: false,
103 limit: None,
104 json: true,
105 };
106 {
107 let mut out = env.output();
108 cmd_inbox(&db, &args, &mut out).expect("ok");
109 }
110 let stdout = env.stdout_str();
111 let envelope: Value = serde_json::from_str(stdout.trim()).expect("parse envelope");
112 assert_eq!(envelope["count"].as_u64(), Some(0));
113 }
114
115 #[test]
116 fn inbox_cli_text_output_lists_messages() {
117 let mut env = TestEnv::fresh();
118 let db = env.db_path.clone();
119 crate::cli::test_utils::seed_memory(
121 &db,
122 "_messages/ai:bob",
123 "hello bob",
124 "message payload",
125 );
126 let args = InboxArgs {
127 agent_id: Some("ai:bob".into()),
128 unread_only: false,
129 limit: Some(10),
130 json: false,
131 };
132 {
133 let mut out = env.output();
134 cmd_inbox(&db, &args, &mut out).expect("ok");
135 }
136 let stdout = env.stdout_str();
137 assert!(stdout.contains("1 message(s) for ai:bob"), "got: {stdout}");
138 assert!(stdout.contains("from=test-agent"), "got: {stdout}");
139 assert!(stdout.contains("hello bob"), "got: {stdout}");
140 assert!(stdout.contains("read=false"), "got: {stdout}");
141 }
142
143 #[test]
144 fn inbox_cli_unread_only_filters() {
145 let mut env = TestEnv::fresh();
146 let db = env.db_path.clone();
147 crate::cli::test_utils::seed_memory(&db, "_messages/ai:carol", "msg", "body");
148 let args = InboxArgs {
149 agent_id: Some("ai:carol".into()),
150 unread_only: true,
151 limit: None,
152 json: true,
153 };
154 {
155 let mut out = env.output();
156 cmd_inbox(&db, &args, &mut out).expect("ok");
157 }
158 let envelope: Value = serde_json::from_str(env.stdout_str().trim()).expect("json");
159 assert_eq!(envelope["count"].as_u64(), Some(1));
161 }
162}