feat(tools): notify — the first client tool

Desktop notifications are the wave-1 built-in the resident has no
native way to send: the daemon's pushes land in the poll queue, and
this is the program that turns one into an OS notification —
`notify push --title --body [--device]` on the shared plumbing, the
claw's `POST /v1/notifications` on the far end.

The binary is the template for every later client: connection from
the environment the daemon provisions, one contract-headered call,
JSON or error shape, exit code. The e2e test drives the real binary
against a mock claw — a wrong token is a 401 with the code to match,
a consent route is a 403, a good push prints the row — so a packaging
regression cannot pass silently. `skills/` ships the markdown that
teaches the agent the tool exists and what its answers mean.
This commit is contained in:
vhaudiquet committed 2026-10-24 01:02:28 +02:00
1 parent 29099add9d
commit 2fbd93fd79
7 files changed
+1199

No files matched your search

+58
View File
@@ -0,0 +1,58 @@
//! The `notify` client: push one notification to a device. The daemon
//! queues the row; the device's next poll-and-ack carries it. One
//! subcommand, one call, one JSON line out — the thin-client shape
//! every rhema tool follows (spec: rhema-tools §3).
use clap::{Parser, Subcommand};
use rhema_tools_common::{Client, Config, fail, print};
use rhema_tools_contract::NotificationPush;
/// Push a notification to the user's devices.
#[derive(Parser)]
#[command(name = "notify", version, about, long_about = None)]
struct Cli {
/// Present for readability of examples; output is always JSON.
#[arg(long)]
json: bool,
#[command(subcommand)]
command: Command,
}
#[derive(Subcommand)]
enum Command {
/// Push one notification: the daemon queues it, the device's next
/// poll-and-ack delivers it.
Push {
/// The notification's title line.
#[arg(long)]
title: String,
/// The notification's body text.
#[arg(long)]
body: String,
/// The device to deliver to; the daemon's default when unset.
#[arg(long)]
device: Option<String>,
},
}
fn main() {
let Cli { json: _, command } = Cli::parse();
let client = Client::new(Config::from_env());
match command {
Command::Push {
title,
body,
device,
} => {
let push = NotificationPush {
title,
body,
device,
};
match client.call("POST", rhema_tools_contract::NOTIFICATIONS, Some(&push)) {
Ok(record) => print(&record),
Err(error) => fail(error),
}
}
}
}