Skip to main content

vta_cli_common/commands/
device.rs

1//! `device …` operator commands (online, via the trust-task dispatcher).
2//!
3//! Thin wrappers over the `VtaClient::device_*` methods. A personal AI agent
4//! (open-claw / nano-claw / hermes) is a Service consumer
5//! (`consumerKind.serviceKind = "ai-agent"`); these commands enroll, list, and
6//! retire its `DeviceBinding`. See `docs/02-vta/personal-ai-agents.md`.
7
8use serde_json::{Value, json};
9use vta_sdk::client::VtaClient;
10
11use crate::render::{DIM, RESET, is_json_output, print_json};
12
13/// Print a trust-task result either as raw JSON (`--json`) or as a labelled,
14/// pretty-printed document for humans.
15fn print_result(label: &str, value: &Value) -> Result<(), Box<dyn std::error::Error>> {
16    if is_json_output() {
17        print_json(value)?;
18    } else {
19        println!("{label}");
20        println!("{}", serde_json::to_string_pretty(value)?);
21    }
22    Ok(())
23}
24
25/// `device list` — list the caller's registered devices. `service_kind` filters
26/// to a single Service consumer kind (e.g. `ai-agent`) when supplied.
27pub async fn cmd_device_list(
28    client: &VtaClient,
29    service_kind: Option<String>,
30) -> Result<(), Box<dyn std::error::Error>> {
31    let mut filters = json!({});
32    if let Some(sk) = service_kind {
33        filters["consumerKindFilter"] = json!("service");
34        filters["serviceKindFilter"] = json!(sk);
35    }
36    let result = client.device_list(filters).await?;
37    print_result("Registered devices:", &result)
38}
39
40/// `device register` — claim a `DeviceBinding` for the authenticated DID. The
41/// DID must already be in the ACL (provision the `ai-agent` template first).
42pub async fn cmd_device_register(
43    client: &VtaClient,
44    service_kind: String,
45    display_name: String,
46    platform: Option<String>,
47    hpke_public_key: Option<String>,
48) -> Result<(), Box<dyn std::error::Error>> {
49    let consumer_kind = json!({ "kind": "service", "serviceKind": service_kind });
50    let result = client
51        .device_register(
52            consumer_kind,
53            &display_name,
54            platform.as_deref(),
55            hpke_public_key.as_deref(),
56        )
57        .await?;
58    println!("{DIM}Device registered.{RESET}");
59    print_result("Binding:", &result)
60}
61
62/// `device disable` — disable a device by id (kept on record, can no longer
63/// authenticate). The operator kill switch.
64pub async fn cmd_device_disable(
65    client: &VtaClient,
66    device_id: String,
67) -> Result<(), Box<dyn std::error::Error>> {
68    let result = client.device_disable(&device_id).await?;
69    println!("{DIM}Device {device_id} disabled.{RESET}");
70    print_result("Result:", &result)
71}
72
73/// `device wipe` — remotely wipe a lost/compromised device. Marks it wiped +
74/// disabled and records the reason. `scope` is `cache`, `cache-and-keys`, or
75/// `full`.
76pub async fn cmd_device_wipe(
77    client: &VtaClient,
78    device_id: String,
79    reason: String,
80    scope: String,
81) -> Result<(), Box<dyn std::error::Error>> {
82    let result = client.device_wipe(&device_id, &reason, &scope).await?;
83    println!("{DIM}Device {device_id} wiped (scope: {scope}).{RESET}");
84    print_result("Result:", &result)
85}
86
87/// `device set-wake` — record the device's push `WakeHandle` (gateway DID/URL +
88/// opaque handle) and return the trigger allowlist.
89pub async fn cmd_device_set_wake(
90    client: &VtaClient,
91    gateway: String,
92    handle: String,
93    suggested_triggers: Vec<String>,
94) -> Result<(), Box<dyn std::error::Error>> {
95    let result = client
96        .device_set_wake(&gateway, &handle, suggested_triggers)
97        .await?;
98    print_result("Wake handle recorded:", &result)
99}
100
101/// `device heartbeat` — refresh `lastSeenAt`; returns server time + any queued
102/// operations for the device.
103pub async fn cmd_device_heartbeat(
104    client: &VtaClient,
105    platform: Option<String>,
106) -> Result<(), Box<dyn std::error::Error>> {
107    let result = client.device_heartbeat(platform.as_deref()).await?;
108    print_result("Heartbeat:", &result)
109}