1#![warn(clippy::all, clippy::pedantic)]
2#![allow(
3 clippy::assigning_clones,
4 clippy::bool_to_int_with_if,
5 clippy::case_sensitive_file_extension_comparisons,
6 clippy::cast_possible_wrap,
7 clippy::doc_markdown,
8 clippy::field_reassign_with_default,
9 clippy::float_cmp,
10 clippy::implicit_clone,
11 clippy::items_after_statements,
12 clippy::map_unwrap_or,
13 clippy::manual_let_else,
14 clippy::missing_errors_doc,
15 clippy::missing_panics_doc,
16 clippy::module_name_repetitions,
17 clippy::must_use_candidate,
18 clippy::new_without_default,
19 clippy::needless_pass_by_value,
20 clippy::needless_raw_string_hashes,
21 clippy::redundant_closure_for_method_calls,
22 clippy::return_self_not_must_use,
23 clippy::similar_names,
24 clippy::single_match_else,
25 clippy::struct_field_names,
26 clippy::too_many_lines,
27 clippy::uninlined_format_args,
28 clippy::unnecessary_cast,
29 clippy::unnecessary_lazy_evaluations,
30 clippy::unnecessary_literal_bound,
31 clippy::unnecessary_map_or,
32 clippy::unused_self,
33 clippy::cast_precision_loss,
34 clippy::unnecessary_wraps
35)]
36
37use clap::Subcommand;
38use serde::{Deserialize, Serialize};
39
40#[cfg(feature = "agent-runtime")]
41pub mod agent;
42#[cfg(feature = "agent-runtime")]
43pub(crate) mod approval;
44#[cfg(feature = "agent-runtime")]
45pub mod auth;
46#[cfg(feature = "agent-runtime")]
47pub mod channels;
48pub mod commands;
49pub mod config;
50#[cfg(feature = "agent-runtime")]
51pub(crate) mod cost;
52#[cfg(feature = "agent-runtime")]
53pub mod cron;
54#[cfg(feature = "agent-runtime")]
55pub(crate) mod daemon;
56#[cfg(feature = "agent-runtime")]
57pub(crate) mod doctor;
58#[cfg(feature = "gateway")]
59pub mod gateway;
60#[cfg(feature = "agent-runtime")]
61pub(crate) mod hardware;
62#[cfg(feature = "agent-runtime")]
63pub(crate) mod health;
64#[cfg(feature = "agent-runtime")]
65pub(crate) mod heartbeat;
66#[cfg(feature = "agent-runtime")]
67pub mod hooks;
68#[cfg(feature = "agent-runtime")]
69pub(crate) mod integrations;
70pub mod memory;
71#[cfg(feature = "agent-runtime")]
72pub(crate) mod multimodal;
73#[cfg(feature = "agent-runtime")]
74pub mod nodes;
75#[cfg(feature = "agent-runtime")]
76pub mod observability;
77#[cfg(feature = "agent-runtime")]
78pub mod peripherals;
79#[cfg(feature = "agent-runtime")]
80pub mod platform;
81pub mod providers;
82#[cfg(feature = "agent-runtime")]
83pub mod rag;
84#[cfg(feature = "agent-runtime")]
85pub mod routines;
86#[cfg(feature = "agent-runtime")]
87pub(crate) mod security;
88#[cfg(feature = "agent-runtime")]
89pub(crate) mod service;
90#[cfg(feature = "agent-runtime")]
91pub(crate) mod skills;
92#[cfg(feature = "agent-runtime")]
93pub mod sop;
94#[cfg(feature = "agent-runtime")]
95pub mod tools;
96#[cfg(feature = "agent-runtime")]
97pub(crate) mod trust;
98#[cfg(feature = "agent-runtime")]
99pub(crate) mod tunnel;
100#[cfg(feature = "agent-runtime")]
101pub mod verifiable_intent;
102
103#[cfg(feature = "plugins-wasm")]
104pub mod plugins;
105
106pub use config::Config;
107
108#[derive(Subcommand, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
110pub enum GatewayCommands {
111 #[command(long_about = "\
114Start the gateway server (webhooks, websockets).
115
116Runs the HTTP/WebSocket gateway that accepts incoming webhook events \
117and WebSocket connections. Bind address defaults to the values in \
118your config file (gateway.host / gateway.port).
119
120Examples:
121 zeroclaw gateway start # use config defaults
122 zeroclaw gateway start -p 8080 # listen on port 8080
123 zeroclaw gateway start --host 0.0.0.0 # requires [gateway].allow_public_bind=true or a tunnel
124 zeroclaw gateway start -p 0 # random available port")]
125 Start {
126 #[arg(short, long)]
128 port: Option<u16>,
129
130 #[arg(long)]
133 host: Option<String>,
134
135 #[arg(long)]
138 allow_degraded_security: bool,
139 },
140 #[command(long_about = "\
143Restart the gateway server.
144
145Stops the running gateway if present, then starts a new instance \
146with the current configuration.
147
148Examples:
149 zeroclaw gateway restart # restart with config defaults
150 zeroclaw gateway restart -p 8080 # restart on port 8080")]
151 Restart {
152 #[arg(short, long)]
154 port: Option<u16>,
155
156 #[arg(long)]
159 host: Option<String>,
160
161 #[arg(long)]
164 allow_degraded_security: bool,
165 },
166 #[command(long_about = "\
169Show or generate the gateway pairing code.
170
171Displays the pairing code for connecting new clients without \
172restarting the gateway. Requires the gateway to be running.
173
174With --new, generates a fresh pairing code even if the gateway \
175was previously paired (useful for adding additional clients). This \
176does NOT revoke existing tokens.
177
178With --rotate, revokes ALL paired bearer tokens, clears the device \
179registry, and issues a fresh code. Use this after a suspected token \
180leak when you do not know which token was compromised; every client \
181must re-pair.
182
183With --rotate-device ID, revokes just that device's bearer token \
184and issues a fresh code for re-pairing that one device.
185
186Examples:
187 zeroclaw gateway get-paircode # show current pairing code
188 zeroclaw gateway get-paircode --new # add another client (no revocation)
189 zeroclaw gateway get-paircode --rotate # revoke ALL tokens, then issue a code
190 zeroclaw gateway get-paircode --rotate-device dash-1 # revoke one device's token
191 zeroclaw gateway get-paircode --new --port 3001 # target alternate-port gateway")]
192 GetPaircode {
193 #[arg(long)]
195 new: bool,
196
197 #[arg(long, conflicts_with_all = ["new", "rotate_device"])]
199 rotate: bool,
200
201 #[arg(long, value_name = "DEVICE_ID", conflicts_with_all = ["new", "rotate"])]
203 rotate_device: Option<String>,
204
205 #[arg(short, long)]
207 port: Option<u16>,
208
209 #[arg(long)]
211 host: Option<String>,
212 },
213}
214
215#[derive(Subcommand, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
217pub enum ServiceCommands {
218 Install,
220 Start,
222 Stop,
224 Restart,
226 Status,
228 Uninstall,
230 Logs {
232 #[arg(short = 'n', long, default_value = "50")]
234 lines: usize,
235 #[arg(short, long)]
237 follow: bool,
238 },
239}
240
241#[derive(Subcommand, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
243pub enum ChannelCommands {
244 List,
246 Start,
248 Doctor,
250 #[command(long_about = "\
253Add a new channel configuration.
254
255Provide the channel type and a JSON object with the required \
256configuration keys for that channel type.
257
258Supported types: telegram, discord, slack, whatsapp, matrix, imessage, email.
259
260Examples:
261 zeroclaw channel add telegram '{\"bot_token\":\"...\",\"name\":\"my-bot\"}'
262 zeroclaw channel add discord '{\"bot_token\":\"...\",\"name\":\"my-discord\"}'")]
263 Add {
264 channel_type: String,
266 config: String,
268 },
269 Remove {
271 name: String,
273 },
274 #[command(long_about = "\
277Bind a Telegram identity into the allowlist.
278
279Adds a Telegram username (without the '@' prefix) or numeric user \
280ID to the channel allowlist so the agent will respond to messages \
281from that identity.
282
283Examples:
284 zeroclaw channel bind-telegram zeroclaw_user
285 zeroclaw channel bind-telegram 123456789")]
286 BindTelegram {
287 identity: String,
289 },
290 #[command(long_about = "\
293Send a one-off message to a configured channel.
294
295Sends a text message through the specified channel without starting \
296the full agent loop. Useful for scripted notifications, hardware \
297sensor alerts, and automation pipelines.
298
299The --channel-id selects the channel by its config section name \
300(e.g. 'telegram', 'discord', 'slack'). The --recipient is the \
301platform-specific destination (e.g. a Telegram chat ID).
302
303Examples:
304 zeroclaw channel send 'Someone is near your device.' --channel-id telegram --recipient 123456789
305 zeroclaw channel send 'Build succeeded!' --channel-id discord --recipient 987654321")]
306 Send {
307 message: String,
309 #[arg(long)]
311 channel_id: String,
312 #[arg(long)]
314 recipient: String,
315 },
316}
317
318#[derive(Subcommand, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
320pub enum SkillCommands {
321 List,
323 #[command(long_about = "\
326Scaffold a new skill under a skill bundle. Writes `<bundle.directory>`/`<name>`/SKILL.md \
327plus the canonical optional subdirs (scripts/, references/, assets/). \
328Name must be lowercase + hyphens; description is required (prompted on TTY if omitted).
329
330Examples:
331 zeroclaw skills add code-review --bundle official --description \"Review PRs.\"
332 zeroclaw skills add ops-runbook --description \"Triage prod incidents.\" --edit")]
333 Add {
334 name: String,
336 #[arg(long)]
338 bundle: Option<String>,
339 #[arg(long)]
342 description: Option<String>,
343 #[arg(long)]
345 license: Option<String>,
346 #[arg(long)]
348 author: Option<String>,
349 #[arg(long)]
351 version: Option<String>,
352 #[arg(long)]
354 category: Option<String>,
355 #[arg(long)]
357 no_scaffold: bool,
358 #[arg(long)]
360 edit: bool,
361 },
362 Edit {
364 name: String,
366 #[arg(long)]
368 bundle: Option<String>,
369 #[arg(long)]
371 file: Option<String>,
372 },
373 Bundle {
375 #[command(subcommand)]
376 bundle_command: SkillBundleCommands,
377 },
378 Audit {
380 source: String,
382 },
383 Install {
385 source: String,
387 #[arg(long)]
390 no_tier_banner: bool,
391 },
392 Remove {
394 name: String,
396 },
397 Test {
399 name: Option<String>,
401 #[arg(long)]
403 verbose: bool,
404 },
405}
406
407#[derive(Subcommand, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
409pub enum SkillBundleCommands {
410 List,
412 Add {
414 alias: String,
416 #[arg(long)]
419 directory: Option<String>,
420 },
421 Remove {
423 alias: String,
425 },
426 Show {
428 alias: String,
430 },
431}
432
433#[derive(Subcommand, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
435pub enum MigrateCommands {
436 Openclaw {
438 #[arg(long)]
440 source: Option<std::path::PathBuf>,
441
442 #[arg(long)]
444 dry_run: bool,
445 },
446}
447
448#[derive(Subcommand, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
450pub enum CronCommands {
451 List,
453 #[command(long_about = "\
456Add a new recurring scheduled task.
457
458Uses standard 5-field cron syntax: 'min hour day month weekday'. \
459When --tz is omitted, cron schedules use the runtime local timezone. \
460For user-facing schedules, pass --tz with an explicit IANA timezone.
461
462Examples:
463 zeroclaw cron add '0 9 * * 1-5' 'Good morning' --tz America/New_York --agent
464 zeroclaw cron add '*/30 * * * *' 'Check system health' --agent
465 zeroclaw cron add '*/5 * * * *' 'echo ok'")]
466 Add {
467 expression: String,
469 #[arg(short = 'a', long = "agent")]
472 agent_alias: String,
473 #[arg(long)]
475 tz: Option<String>,
476 #[arg(long)]
478 prompt: bool,
479 #[arg(long = "allowed-tool")]
481 allowed_tools: Vec<String>,
482 command: String,
484 },
485 #[command(long_about = "\
488Add a one-shot task that fires at a specific RFC3339 timestamp with explicit Z or offset.
489
490The timestamp must include an explicit Z or numeric offset \
491(e.g. 2025-01-15T14:00:00Z or 2025-01-15T09:00:00-05:00).
492
493Examples:
494 zeroclaw cron add-at --agent morning-shift 2025-01-15T14:00:00Z 'Send reminder'
495 zeroclaw cron add-at --agent morning-shift --prompt 2025-12-31T23:59:00Z 'Happy New Year!'")]
496 AddAt {
497 at: String,
499 #[arg(short = 'a', long = "agent")]
501 agent_alias: String,
502 #[arg(long)]
504 prompt: bool,
505 #[arg(long = "allowed-tool")]
507 allowed_tools: Vec<String>,
508 command: String,
510 },
511 #[command(long_about = "\
514Add a task that repeats at a fixed interval.
515
516Interval is specified in milliseconds. For example, 60000 = 1 minute.
517
518Examples:
519 zeroclaw cron add-every --agent triage 60000 'Ping heartbeat'
520 zeroclaw cron add-every --agent triage 3600000 'Hourly report'")]
521 AddEvery {
522 every_ms: u64,
524 #[arg(short = 'a', long = "agent")]
526 agent_alias: String,
527 #[arg(long)]
529 prompt: bool,
530 #[arg(long = "allowed-tool")]
532 allowed_tools: Vec<String>,
533 command: String,
535 },
536 #[command(long_about = "\
539Add a one-shot task that fires after a delay from now.
540
541Accepts human-readable durations: s (seconds), m (minutes), \
542h (hours), d (days).
543
544Examples:
545 zeroclaw cron once --agent ops-bot 30m 'Run backup in 30 minutes'
546 zeroclaw cron once --agent researcher --prompt 2h 'Follow up on deployment'")]
547 Once {
548 delay: String,
550 #[arg(short = 'a', long = "agent")]
552 agent_alias: String,
553 #[arg(long)]
555 prompt: bool,
556 #[arg(long = "allowed-tool")]
558 allowed_tools: Vec<String>,
559 command: String,
561 },
562 Remove {
564 id: String,
566 },
567 #[command(long_about = "\
570Update one or more fields of an existing scheduled task.
571
572Only the fields you specify are changed; others remain unchanged.
573
574Examples:
575 zeroclaw cron update TASK_ID --expression '0 8 * * *'
576 zeroclaw cron update TASK_ID --tz Europe/London --name 'Morning check'
577 zeroclaw cron update TASK_ID --command 'Updated message'")]
578 Update {
579 id: String,
581 #[arg(short = 'a', long = "agent")]
584 agent_alias: String,
585 #[arg(long)]
587 expression: Option<String>,
588 #[arg(long)]
590 tz: Option<String>,
591 #[arg(long)]
593 command: Option<String>,
594 #[arg(long)]
596 name: Option<String>,
597 #[arg(long = "allowed-tool")]
599 allowed_tools: Vec<String>,
600 },
601 Pause {
603 id: String,
605 },
606 Resume {
608 id: String,
610 },
611}
612
613#[derive(Subcommand, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
615pub enum MemoryCommands {
616 List {
618 #[arg(long)]
620 category: Option<String>,
621 #[arg(long)]
623 session: Option<String>,
624 #[arg(long, default_value = "50")]
626 limit: usize,
627 #[arg(long, default_value = "0")]
629 offset: usize,
630 },
631 Get {
633 key: String,
635 },
636 Stats,
638 Clear {
640 #[arg(long)]
642 key: Option<String>,
643 #[arg(long)]
645 category: Option<String>,
646 #[arg(long)]
648 yes: bool,
649 },
650 Reindex,
657}
658
659#[derive(Subcommand, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
661pub enum IntegrationCommands {
662 Info {
664 name: String,
666 },
667}
668
669#[derive(Subcommand, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
671pub enum HardwareCommands {
672 #[command(long_about = "\
675Enumerate USB devices and show known boards.
676
677Scans connected USB devices by VID/PID and matches them against \
678known development boards (STM32 Nucleo, Arduino, ESP32).
679
680Examples:
681 zeroclaw hardware discover")]
682 Discover,
683 #[command(long_about = "\
686Introspect a device by its serial or device path.
687
688Opens the specified device path and queries for board information, \
689firmware version, and supported capabilities.
690
691Examples:
692 zeroclaw hardware introspect /dev/ttyACM0
693 zeroclaw hardware introspect COM3")]
694 Introspect {
695 path: String,
697 },
698 #[command(long_about = "\
701Get chip info via USB using probe-rs over ST-Link.
702
703Queries the target MCU directly through the debug probe without \
704requiring any firmware on the target board.
705
706Examples:
707 zeroclaw hardware info
708 zeroclaw hardware info --chip STM32F401RETx")]
709 Info {
710 #[arg(long, default_value = "STM32F401RETx")]
712 chip: String,
713 },
714}
715
716#[derive(Subcommand, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
718pub enum PeripheralCommands {
719 List,
721 #[command(long_about = "\
724Add a peripheral by board type and transport path.
725
726Registers a hardware board so the agent can use its tools (GPIO, \
727sensors, actuators). Use 'native' as path for local GPIO on \
728single-board computers like Raspberry Pi.
729
730Supported boards: nucleo-f401re, rpi-gpio, esp32, arduino-uno.
731
732Examples:
733 zeroclaw peripheral add nucleo-f401re /dev/ttyACM0
734 zeroclaw peripheral add rpi-gpio native
735 zeroclaw peripheral add esp32 /dev/ttyUSB0")]
736 Add {
737 board: String,
739 path: String,
741 },
742 #[command(long_about = "\
745Flash ZeroClaw firmware to an Arduino board.
746
747Generates the .ino sketch, installs arduino-cli if it is not \
748already available, compiles, and uploads the firmware.
749
750Examples:
751 zeroclaw peripheral flash
752 zeroclaw peripheral flash --port /dev/cu.usbmodem12345
753 zeroclaw peripheral flash -p COM3")]
754 Flash {
755 #[arg(short, long)]
757 port: Option<String>,
758 },
759 SetupUnoQ {
761 #[arg(long)]
763 host: Option<String>,
764 },
765 FlashNucleo,
767}
768
769#[derive(Subcommand, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
771pub enum SopCommands {
772 List,
774 Validate {
776 name: Option<String>,
778 },
779 Show {
781 name: String,
783 },
784}