cellstate/entity/
working_set.rs1use anyhow::Result;
6use clap::{Arg, ArgMatches, Command};
7
8use cellstate_core::api_types::{
9 ListWorkingSetResponse, PatchWorkingSetEntryRequest, UpsertWorkingSetEntryRequest,
10 WorkingSetEntryResponse,
11};
12
13use super::client::ApiClient;
14use super::output::OutputConfig;
15
16const BASE: &str = "/api/v1/working-set";
17
18pub fn build_command() -> Command {
19 Command::new("working-set")
20 .about("Manage agent working-set entries")
21 .subcommand_required(true)
22 .subcommand(
23 Command::new("list")
24 .about("List working-set entries")
25 .arg(
26 Arg::new("agent-id")
27 .long("agent-id")
28 .help("Filter by agent ID"),
29 )
30 .arg(
31 Arg::new("scope-id")
32 .long("scope-id")
33 .help("Filter by scope ID"),
34 )
35 .arg(
36 Arg::new("key-prefix")
37 .long("key-prefix")
38 .help("Filter by key prefix"),
39 )
40 .arg(
41 Arg::new("active-only")
42 .long("active-only")
43 .action(clap::ArgAction::SetTrue)
44 .help("Exclude expired entries"),
45 )
46 .arg(
47 Arg::new("limit")
48 .long("limit")
49 .default_value("20")
50 .help("Max results"),
51 )
52 .arg(
53 Arg::new("offset")
54 .long("offset")
55 .default_value("0")
56 .help("Offset"),
57 ),
58 )
59 .subcommand(
60 Command::new("upsert")
61 .about("Upsert a working-set entry")
62 .arg(
63 Arg::new("agent-id")
64 .long("agent-id")
65 .required(true)
66 .help("Agent ID"),
67 )
68 .arg(Arg::new("key").required(true).help("Entry key"))
69 .arg(
70 Arg::new("value")
71 .required(true)
72 .help("Entry value (JSON string)"),
73 )
74 .arg(
75 Arg::new("scope-id")
76 .long("scope-id")
77 .help("Optional scope ID"),
78 ),
79 )
80 .subcommand(
81 Command::new("get")
82 .about("Get a working-set entry")
83 .arg(Arg::new("agent-id").required(true).help("Agent ID"))
84 .arg(Arg::new("key").required(true).help("Entry key")),
85 )
86 .subcommand(
87 Command::new("update")
88 .about("Update a working-set entry")
89 .arg(Arg::new("agent-id").required(true).help("Agent ID"))
90 .arg(Arg::new("key").required(true).help("Entry key"))
91 .arg(
92 Arg::new("value")
93 .long("value")
94 .help("New value (JSON string)"),
95 )
96 .arg(Arg::new("scope-id").long("scope-id").help("New scope ID")),
97 )
98 .subcommand(
99 Command::new("delete")
100 .about("Delete a working-set entry")
101 .arg(Arg::new("agent-id").required(true).help("Agent ID"))
102 .arg(Arg::new("key").required(true).help("Entry key")),
103 )
104}
105
106pub async fn dispatch(
107 matches: &ArgMatches,
108 client: &ApiClient,
109 output: &OutputConfig,
110 _session: &crate::session::CliSession,
111) -> Result<()> {
112 match matches.subcommand() {
113 Some(("list", sub)) => {
114 let limit: i64 = sub.get_one::<String>("limit").unwrap().parse()?;
115 let offset: i64 = sub.get_one::<String>("offset").unwrap().parse()?;
116 let mut path = format!("{BASE}?limit={limit}&offset={offset}");
117 if let Some(agent_id) = sub.get_one::<String>("agent-id") {
118 path.push_str(&format!("&agent_id={agent_id}"));
119 }
120 if let Some(scope_id) = sub.get_one::<String>("scope-id") {
121 path.push_str(&format!("&scope_id={scope_id}"));
122 }
123 if let Some(key_prefix) = sub.get_one::<String>("key-prefix") {
124 path.push_str(&format!("&key_prefix={key_prefix}"));
125 }
126 if sub.get_flag("active-only") {
127 path.push_str("&active_only=true");
128 }
129 let resp: ListWorkingSetResponse = client.get(&path).await?;
130 output.print_list(&resp.entries, resp.total);
131 }
132 Some(("upsert", sub)) => {
133 let agent_id = sub.get_one::<String>("agent-id").unwrap().parse()?;
134 let key = sub.get_one::<String>("key").unwrap().clone();
135 let value: serde_json::Value =
136 serde_json::from_str(sub.get_one::<String>("value").unwrap())?;
137 let req = UpsertWorkingSetEntryRequest {
138 agent_id,
139 scope_id: sub
140 .get_one::<String>("scope-id")
141 .map(|s| s.parse())
142 .transpose()?,
143 key,
144 value,
145 expires_at: None,
146 metadata: None,
147 };
148 let resp: serde_json::Value = client.post_raw(BASE, &req).await?;
151 output.print_value(&resp);
152 }
153 Some(("get", sub)) => {
154 let agent_id = sub.get_one::<String>("agent-id").unwrap();
155 let key = sub.get_one::<String>("key").unwrap();
156 let resp: WorkingSetEntryResponse =
157 client.get(&format!("{BASE}/{agent_id}/{key}")).await?;
158 output.print(&resp);
159 }
160 Some(("update", sub)) => {
161 let agent_id = sub.get_one::<String>("agent-id").unwrap();
162 let key = sub.get_one::<String>("key").unwrap();
163 let req = PatchWorkingSetEntryRequest {
164 value: sub
165 .get_one::<String>("value")
166 .map(|s| serde_json::from_str(s))
167 .transpose()?,
168 scope_id: sub
169 .get_one::<String>("scope-id")
170 .map(|s| s.parse())
171 .transpose()?,
172 expires_at: None,
173 metadata: None,
174 };
175 let resp: serde_json::Value = client
176 .patch(&format!("{BASE}/{agent_id}/{key}"), &req)
177 .await?;
178 output.print_value(&resp);
179 }
180 Some(("delete", sub)) => {
181 let agent_id = sub.get_one::<String>("agent-id").unwrap();
182 let key = sub.get_one::<String>("key").unwrap();
183 client.delete(&format!("{BASE}/{agent_id}/{key}")).await?;
184 if output.json {
185 println!(r#"{{"deleted": "{agent_id}/{key}"}}"#);
186 } else {
187 println!("Deleted working-set entry {agent_id}/{key}");
188 }
189 }
190 _ => unreachable!("subcommand_required(true) prevents this"),
191 }
192 Ok(())
193}