1use vta_sdk::client::VtaClient;
18use vta_sdk::protocol::services::{
19 DisableRestRequest, EnableRestRequest, RollbackDidcommRequest, RollbackRestRequest,
20 UpdateRestRequest,
21};
22use vta_sdk::protocol::{DisableDidcommRequest, EnableDidcommRequest, UpdateDidcommRequest};
23
24pub async fn cmd_services_list(client: &VtaClient) -> Result<(), Box<dyn std::error::Error>> {
28 let response = client.list_services().await?;
29
30 println!("Services advertised on this VTA's DID document:");
31 println!();
32 for state in &response.services {
33 match state {
34 vta_sdk::protocol::services::ServiceState::Didcomm {
35 enabled,
36 mediator_did,
37 routing_keys,
38 } => {
39 let on = if *enabled { "on" } else { "off" };
40 println!(" DIDComm: {on}");
41 if let Some(m) = mediator_did {
42 println!(" Mediator: {m}");
43 }
44 if !routing_keys.is_empty() {
45 println!(" Routing keys: {}", routing_keys.join(", "));
46 }
47 }
48 vta_sdk::protocol::services::ServiceState::Rest { enabled, url } => {
49 let on = if *enabled { "on" } else { "off" };
50 println!(" REST: {on}");
51 if let Some(u) = url {
52 println!(" URL: {u}");
53 }
54 }
55 vta_sdk::protocol::services::ServiceState::Webauthn { enabled, url } => {
56 let on = if *enabled { "on" } else { "off" };
57 println!(" WebAuthn: {on}");
58 if let Some(u) = url {
59 println!(" URL: {u}");
60 }
61 }
62 }
63 }
64 Ok(())
65}
66
67pub async fn cmd_services_rest_enable(
70 client: &VtaClient,
71 url: String,
72) -> Result<(), Box<dyn std::error::Error>> {
73 let req = EnableRestRequest::new(url);
74 let resp = client.enable_rest(req).await?;
75 println!("REST enabled.");
76 println!(" New version ID: {}", resp.log_entry_version_id);
77 println!(" Effective at: {}", resp.effective_at);
78 print_serverless_hint(resp.serverless, &resp.vta_did);
79 Ok(())
80}
81
82pub async fn cmd_services_rest_update(
83 client: &VtaClient,
84 url: String,
85) -> Result<(), Box<dyn std::error::Error>> {
86 let req = UpdateRestRequest::new(url);
87 let resp = client.update_rest(req).await?;
88 println!("REST URL updated.");
89 println!(" New version ID: {}", resp.log_entry_version_id);
90 println!(" Effective at: {}", resp.effective_at);
91 print_serverless_hint(resp.serverless, &resp.vta_did);
92 Ok(())
93}
94
95pub async fn cmd_services_rest_disable(
96 client: &VtaClient,
97) -> Result<(), Box<dyn std::error::Error>> {
98 let resp = client.disable_rest(DisableRestRequest::default()).await?;
99 println!("REST disabled.");
100 println!(" New version ID: {}", resp.log_entry_version_id);
101 println!(" Effective at: {}", resp.effective_at);
102 print_serverless_hint(resp.serverless, &resp.vta_did);
103 Ok(())
104}
105
106pub async fn cmd_services_rest_rollback(
107 client: &VtaClient,
108) -> Result<(), Box<dyn std::error::Error>> {
109 let resp = client.rollback_rest(RollbackRestRequest::default()).await?;
110 print_rollback_result("REST", &resp);
111 Ok(())
112}
113
114pub async fn cmd_services_webauthn_enable(
117 client: &VtaClient,
118 url: String,
119) -> Result<(), Box<dyn std::error::Error>> {
120 let req = vta_sdk::protocol::services::EnableWebauthnRequest::new(url);
121 let resp = client.enable_webauthn(req).await?;
122 println!("WebAuthn enabled.");
123 println!(" New version ID: {}", resp.log_entry_version_id);
124 println!(" Effective at: {}", resp.effective_at);
125 print_serverless_hint(resp.serverless, &resp.vta_did);
126 Ok(())
127}
128
129pub async fn cmd_services_webauthn_update(
130 client: &VtaClient,
131 url: String,
132) -> Result<(), Box<dyn std::error::Error>> {
133 let req = vta_sdk::protocol::services::UpdateWebauthnRequest::new(url);
134 let resp = client.update_webauthn(req).await?;
135 println!("WebAuthn URL updated.");
136 println!(" New version ID: {}", resp.log_entry_version_id);
137 println!(" Effective at: {}", resp.effective_at);
138 print_serverless_hint(resp.serverless, &resp.vta_did);
139 Ok(())
140}
141
142pub async fn cmd_services_webauthn_disable(
143 client: &VtaClient,
144) -> Result<(), Box<dyn std::error::Error>> {
145 eprintln!(
146 "WARNING: disabling WebAuthn will also REMOVE passkey verificationMethods from every DID \
147 this VTA controls. Any operator currently using passkey login will need to re-enrol \
148 after the next `services webauthn enable`."
149 );
150 let resp = client
151 .disable_webauthn(vta_sdk::protocol::services::DisableWebauthnRequest::default())
152 .await?;
153 println!("WebAuthn disabled.");
154 println!(" New version ID: {}", resp.log_entry_version_id);
155 println!(" Effective at: {}", resp.effective_at);
156 print_serverless_hint(resp.serverless, &resp.vta_did);
157 Ok(())
158}
159
160pub async fn cmd_services_webauthn_rollback(
161 client: &VtaClient,
162) -> Result<(), Box<dyn std::error::Error>> {
163 let resp = client
164 .rollback_webauthn(vta_sdk::protocol::services::RollbackWebauthnRequest::default())
165 .await?;
166 print_rollback_result("WebAuthn", &resp);
167 Ok(())
168}
169
170pub async fn cmd_services_didcomm_enable(
173 client: &VtaClient,
174 mediator_did: String,
175 force: bool,
176 handshake_timeout_secs: Option<u64>,
177) -> Result<(), Box<dyn std::error::Error>> {
178 let mut req = EnableDidcommRequest::new(&mediator_did);
179 req.force = force;
180 req.handshake_timeout_secs = handshake_timeout_secs;
181 let resp = client.enable_didcomm(req).await?;
182 println!("DIDComm enabled.");
183 println!(" Mediator DID: {}", resp.mediator_did);
184 if !resp.mediator_endpoint.is_empty() {
185 println!(" Mediator URL: {}", resp.mediator_endpoint);
186 }
187 println!(" New version ID: {}", resp.new_version_id);
188 if force {
189 println!();
190 println!(" Note: --force was set; mediator handshake steps 2-5 were bypassed.");
191 }
192 print_serverless_hint(resp.serverless, &resp.vta_did);
193 Ok(())
194}
195
196pub async fn cmd_services_didcomm_update(
197 client: &VtaClient,
198 new_mediator_did: String,
199 drain_ttl_secs: u64,
200 force: bool,
201 handshake_timeout_secs: Option<u64>,
202) -> Result<(), Box<dyn std::error::Error>> {
203 let mut req = UpdateDidcommRequest::new(&new_mediator_did, drain_ttl_secs);
204 req.force = force;
205 req.handshake_timeout_secs = handshake_timeout_secs;
206 let resp = client.update_didcomm(req).await?;
207 println!("DIDComm mediator updated.");
208 println!(" Prior mediator: {}", resp.prior_mediator_did);
209 println!(" Active mediator: {}", resp.active_mediator_did);
210 if !resp.active_mediator_endpoint.is_empty() {
211 println!(" Active endpoint: {}", resp.active_mediator_endpoint);
212 }
213 println!(" New version ID: {}", resp.new_version_id);
214 println!(
215 " Drain deadline: {} (prior listener stays up until then)",
216 resp.drains_until
217 );
218 print_serverless_hint(resp.serverless, &resp.vta_did);
219 Ok(())
220}
221
222pub async fn cmd_services_didcomm_disable(
223 client: &VtaClient,
224 drain_ttl_secs: u64,
225) -> Result<(), Box<dyn std::error::Error>> {
226 let req = DisableDidcommRequest::new(drain_ttl_secs);
227 let resp = client.disable_didcomm(req).await?;
228 println!("DIDComm disabled.");
229 println!(" Prior mediator: {}", resp.prior_mediator_did);
230 println!(" New version ID: {}", resp.new_version_id);
231 match resp.drains_until {
232 Some(deadline) => {
233 println!(" Drain deadline: {deadline}");
234 println!();
235 println!(" The listener stays up until the deadline so in-flight messages can drain.");
236 println!(
237 " Cancel early with `pnm services didcomm drain cancel --mediator-did <did>`."
238 );
239 }
240 None => println!(" Listener torn down immediately (drain TTL was 0)."),
241 }
242 print_serverless_hint(resp.serverless, &resp.vta_did);
243 Ok(())
244}
245
246pub async fn cmd_services_didcomm_rollback(
247 client: &VtaClient,
248 drain_ttl_secs: Option<u64>,
249) -> Result<(), Box<dyn std::error::Error>> {
250 let req = RollbackDidcommRequest { drain_ttl_secs };
251 let resp = client.rollback_didcomm(req).await?;
252 print_rollback_result("DIDComm", &resp);
253 Ok(())
254}
255
256pub async fn cmd_services_didcomm_drain_list(
259 client: &VtaClient,
260) -> Result<(), Box<dyn std::error::Error>> {
261 let resp = client.list_drain().await?;
262 if resp.entries.is_empty() {
263 println!("No mediators currently in drain.");
264 return Ok(());
265 }
266 println!("Drain set ({} mediator(s)):", resp.entries.len());
267 println!();
268 let header_did = "MEDIATOR DID";
269 let header_until = "DRAIN UNTIL";
270 println!(" {header_did:<60} {header_until}");
271 for e in &resp.entries {
272 println!(
273 " {:<60} {}",
274 truncate(&e.mediator_did, 60),
275 e.drains_until
276 );
277 }
278 Ok(())
279}
280
281pub async fn cmd_services_didcomm_drain_cancel(
282 client: &VtaClient,
283 mediator_did: String,
284) -> Result<(), Box<dyn std::error::Error>> {
285 let req = vta_sdk::protocol::DrainCancelRequest { mediator_did };
286 let resp = client.drain_cancel(req).await?;
287 println!("Drain cancelled for {}.", resp.mediator_did);
288 println!(" Listener was torn down immediately.");
289 Ok(())
290}
291
292pub async fn cmd_services_report(
295 client: &VtaClient,
296 since: Option<String>,
297 until: Option<String>,
298 format: ReportFormat,
299) -> Result<(), Box<dyn std::error::Error>> {
300 let report = client
301 .mediator_report(since.as_deref(), until.as_deref())
302 .await?;
303
304 match format {
305 ReportFormat::Json => {
306 println!("{}", serde_json::to_string_pretty(&report)?);
307 }
308 ReportFormat::Table => {
309 println!("Service-management report");
310 if let Some(ref s) = report.since {
311 println!(" Window: {s} → {}", report.until);
312 } else {
313 println!(" Window: (all time) → {}", report.until);
314 }
315 println!();
316 if report.mediators.is_empty() {
317 println!(" No inbound DIDComm messages recorded.");
318 } else {
319 println!(" Per-mediator inbound counts (most recent first):");
320 let header_did = "MEDIATOR DID";
321 let header_count = "INBOUND";
322 println!(" {header_did:<60} {header_count:>10} LAST SEEN");
323 for m in &report.mediators {
324 println!(
325 " {:<60} {:>10} {}",
326 truncate(&m.mediator_did, 60),
327 m.inbound_count,
328 m.last_seen
329 );
330 }
331 }
332 if !report.senders.is_empty() {
333 println!();
334 println!(" Senders by last-seen mediator:");
335 for s in &report.senders {
336 println!(
337 " {} → {} (at {})",
338 truncate(&s.sender_did, 50),
339 truncate(&s.last_seen_mediator, 50),
340 s.last_seen_at
341 );
342 }
343 }
344 }
345 }
346 Ok(())
347}
348
349fn print_rollback_result(kind: &str, resp: &vta_sdk::protocol::services::RollbackResponse) {
352 if resp.kind == "no_op" {
353 println!("{kind} rollback: no change required.");
354 println!(" Snapshot matches current state — nothing to do.");
355 return;
356 }
357 println!("{kind} rolled back.");
358 println!(" Action: {}", resp.kind);
359 if !resp.log_entry_version_id.is_empty() {
360 println!(" New version ID: {}", resp.log_entry_version_id);
361 }
362 println!(" Effective at: {}", resp.effective_at);
363 if let Some(ref drain_until) = resp.drain_until {
364 println!(" Drain deadline: {drain_until}");
365 }
366 if let Some(ref draining) = resp.draining_mediator {
367 println!(" Draining: {draining}");
368 }
369 print_serverless_hint(resp.serverless, &resp.vta_did);
370}
371
372pub fn print_serverless_hint(serverless: bool, vta_did: &str) {
385 if !serverless || vta_did.is_empty() {
386 return;
387 }
388 println!();
389 println!(" This VTA's DID is self-hosted. Fetch the updated log:");
390 println!(" pnm did-mgmt dids get-log {vta_did} --out did.jsonl");
391 println!(" then redeploy did.jsonl to your host. Until you do,");
392 println!(" resolvers will keep returning the prior version.");
393}
394
395#[derive(Debug, Clone, Copy)]
396pub enum ReportFormat {
397 Json,
398 Table,
399}
400
401impl std::str::FromStr for ReportFormat {
402 type Err = String;
403 fn from_str(s: &str) -> Result<Self, Self::Err> {
404 match s {
405 "json" => Ok(Self::Json),
406 "table" => Ok(Self::Table),
407 other => Err(format!("unknown format `{other}` — use `json` or `table`")),
408 }
409 }
410}
411
412fn truncate(s: &str, max: usize) -> String {
413 if s.chars().count() <= max {
414 s.to_string()
415 } else {
416 let mut out: String = s.chars().take(max - 1).collect();
417 out.push('…');
418 out
419 }
420}