ai_memory/cli/commands/
notify.rs1use crate::models::field_names;
19use anyhow::Result;
20use clap::Args;
21use serde_json::{Value, json};
22
23use crate::cli::CliOutput;
24use crate::config::AppConfig;
25use crate::storage as db;
26
27#[derive(Args, Debug, Clone)]
29pub struct NotifyArgs {
30 #[arg(long = "target-agent-id", value_name = "AGENT_ID")]
32 pub target_agent_id: String,
33
34 #[arg(long, value_name = "TEXT")]
36 pub title: String,
37
38 #[arg(long, value_name = "TEXT")]
40 pub payload: String,
41
42 #[arg(long, value_name = "N")]
44 pub priority: Option<i64>,
45
46 #[arg(long, value_name = "TIER")]
48 pub tier: Option<String>,
49
50 #[arg(long)]
52 pub json: bool,
53}
54
55pub fn cmd_notify(
63 db_path: &std::path::Path,
64 args: &NotifyArgs,
65 app_config: &AppConfig,
66 out: &mut CliOutput<'_>,
67) -> Result<()> {
68 let conn = db::open(db_path)?;
69 let resolved_ttl = app_config.effective_ttl();
70
71 let mut params = json!({
72 (field_names::TARGET_AGENT_ID): args.target_agent_id,
73 "title": args.title,
74 "payload": args.payload,
75 });
76 if let Some(p) = args.priority {
77 params["priority"] = json!(p);
78 }
79 if let Some(t) = &args.tier {
80 params["tier"] = json!(t);
81 }
82
83 let envelope = crate::mcp::handle_notify(&conn, ¶ms, &resolved_ttl, None)
84 .map_err(|e| anyhow::anyhow!("notify: {e}"))?;
85
86 if args.json {
87 writeln!(out.stdout, "{}", serde_json::to_string(&envelope)?)?;
88 return Ok(());
89 }
90
91 let id = envelope.get("id").and_then(Value::as_str).unwrap_or("?");
92 let to = envelope.get("to").and_then(Value::as_str).unwrap_or("?");
93 writeln!(out.stdout, "notify: id={id} to={to}")?;
94 Ok(())
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100 use crate::cli::test_utils::TestEnv;
101
102 #[test]
103 fn notify_cli_invalid_target_returns_err() {
104 let mut env = TestEnv::fresh();
105 let db = env.db_path.clone();
106 let cfg = AppConfig::default();
107 let args = NotifyArgs {
108 target_agent_id: "bad agent with spaces".into(),
109 title: "subject".into(),
110 payload: "body".into(),
111 priority: None,
112 tier: None,
113 json: true,
114 };
115 let mut out = env.output();
116 let err = cmd_notify(&db, &args, &cfg, &mut out).expect_err("must fail");
117 assert!(err.to_string().contains("notify"), "got: {err}");
118 }
119
120 #[test]
121 fn notify_cli_happy_path_writes_envelope() {
122 let mut env = TestEnv::fresh();
123 let db = env.db_path.clone();
124 let cfg = AppConfig::default();
125 let args = NotifyArgs {
126 target_agent_id: "ai:bob".into(),
127 title: "subject".into(),
128 payload: "body".into(),
129 priority: Some(7),
130 tier: Some("mid".into()),
131 json: true,
132 };
133 {
134 let mut out = env.output();
135 cmd_notify(&db, &args, &cfg, &mut out).expect("notify ok");
136 }
137 let stdout = env.stdout_str();
138 let envelope: Value = serde_json::from_str(stdout.trim()).expect("parse envelope");
139 assert_eq!(envelope["to"].as_str(), Some("ai:bob"));
140 }
141}