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 dead_code
36)]
37
38use clap::Subcommand;
39use serde::{Deserialize, Serialize};
40
41pub mod agent;
42pub(crate) mod approval;
43pub(crate) mod auth;
44pub mod channels;
45pub(crate) mod cli_input;
46pub mod commands;
47pub mod config;
48pub(crate) mod cost;
49pub mod cron;
50pub(crate) mod daemon;
51pub(crate) mod doctor;
52pub mod gateway;
53pub(crate) mod hardware;
54pub(crate) mod health;
55pub(crate) mod heartbeat;
56pub mod hooks;
57pub mod i18n;
58pub(crate) mod identity;
59pub(crate) mod integrations;
60pub mod mcp_server;
61pub mod memory;
62pub(crate) mod migration;
63pub(crate) mod multimodal;
64pub mod nodes;
65pub mod observability;
66pub(crate) mod onboard;
67pub mod peripherals;
68pub mod providers;
69pub mod rag;
70pub mod runtime;
71pub(crate) mod security;
72pub(crate) mod service;
73pub(crate) mod skills;
74pub mod sop;
75pub mod tools;
76pub(crate) mod trust;
77pub(crate) mod tunnel;
78pub(crate) mod util;
79pub mod verifiable_intent;
80
81#[cfg(feature = "plugins-wasm")]
82pub mod plugins;
83
84pub use config::Config;
85
86#[derive(Subcommand, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
88pub enum GatewayCommands {
89 #[command(long_about = "\
91Start the gateway server (webhooks, websockets).
92
93Runs the HTTP/WebSocket gateway that accepts incoming webhook events \
94and WebSocket connections. Bind address defaults to the values in \
95your config file (gateway.host / gateway.port).
96
97Examples:
98 construct gateway start # use config defaults
99 construct gateway start -p 8080 # listen on port 8080
100 construct gateway start --host 0.0.0.0 # requires [gateway].allow_public_bind=true or a tunnel
101 construct gateway start -p 0 # random available port")]
102 Start {
103 #[arg(short, long)]
105 port: Option<u16>,
106
107 #[arg(long)]
110 host: Option<String>,
111 },
112 #[command(long_about = "\
114Restart the gateway server.
115
116Stops the running gateway if present, then starts a new instance \
117with the current configuration.
118
119Examples:
120 construct gateway restart # restart with config defaults
121 construct gateway restart -p 8080 # restart on port 8080")]
122 Restart {
123 #[arg(short, long)]
125 port: Option<u16>,
126
127 #[arg(long)]
130 host: Option<String>,
131 },
132 #[command(long_about = "\
134Show or generate the gateway pairing code.
135
136Displays the pairing code for connecting new clients without \
137restarting the gateway. Requires the gateway to be running.
138
139With --new, generates a fresh pairing code even if the gateway \
140was previously paired (useful for adding additional clients).
141
142Examples:
143 construct gateway get-paircode # show current pairing code
144 construct gateway get-paircode --new # generate a new pairing code")]
145 GetPaircode {
146 #[arg(long)]
148 new: bool,
149 },
150}
151
152#[derive(Subcommand, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
154pub enum ServiceCommands {
155 Install,
157 Start,
159 Stop,
161 Restart,
163 Status,
165 Uninstall,
167 Logs {
169 #[arg(short = 'n', long, default_value = "50")]
171 lines: usize,
172 #[arg(short, long)]
174 follow: bool,
175 },
176}
177
178#[derive(Subcommand, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
180pub enum ChannelCommands {
181 List,
183 Start,
185 Doctor,
187 #[command(long_about = "\
189Add a new channel configuration.
190
191Provide the channel type and a JSON object with the required \
192configuration keys for that channel type.
193
194Supported types: telegram, discord, slack, whatsapp, matrix, imessage, email.
195
196Examples:
197 construct channel add telegram '{\"bot_token\":\"...\",\"name\":\"my-bot\"}'
198 construct channel add discord '{\"bot_token\":\"...\",\"name\":\"my-discord\"}'")]
199 Add {
200 channel_type: String,
202 config: String,
204 },
205 Remove {
207 name: String,
209 },
210 #[command(long_about = "\
212Bind a Telegram identity into the allowlist.
213
214Adds a Telegram username (without the '@' prefix) or numeric user \
215ID to the channel allowlist so the agent will respond to messages \
216from that identity.
217
218Examples:
219 construct channel bind-telegram construct_user
220 construct channel bind-telegram 123456789")]
221 BindTelegram {
222 identity: String,
224 },
225 #[command(long_about = "\
227Send a one-off message to a configured channel.
228
229Sends a text message through the specified channel without starting \
230the full agent loop. Useful for scripted notifications, hardware \
231sensor alerts, and automation pipelines.
232
233The --channel-id selects the channel by its config section name \
234(e.g. 'telegram', 'discord', 'slack'). The --recipient is the \
235platform-specific destination (e.g. a Telegram chat ID).
236
237Examples:
238 construct channel send 'Someone is near your device.' --channel-id telegram --recipient 123456789
239 construct channel send 'Build succeeded!' --channel-id discord --recipient 987654321")]
240 Send {
241 message: String,
243 #[arg(long)]
245 channel_id: String,
246 #[arg(long)]
248 recipient: String,
249 },
250}
251
252#[derive(Subcommand, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
254pub enum SkillCommands {
255 List,
257 Audit {
259 source: String,
261 },
262 Install {
264 source: String,
266 },
267 Remove {
269 name: String,
271 },
272 Test {
274 name: Option<String>,
276 #[arg(long)]
278 verbose: bool,
279 },
280}
281
282#[derive(Subcommand, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
284pub enum WorkflowCommands {
285 List,
287 #[command(long_about = "\
289Copy the workflow YAMLs bundled with this binary into the active \
290workspace at `operator_mcp/workflow/builtins/`. Existing files are left \
291alone unless --force is passed.")]
292 Sync {
293 #[arg(long)]
295 force: bool,
296 },
297}
298
299#[derive(Subcommand, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
301pub enum MigrateCommands {
302 Openclaw {
304 #[arg(long)]
306 source: Option<std::path::PathBuf>,
307
308 #[arg(long)]
310 dry_run: bool,
311 },
312}
313
314#[derive(Subcommand, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
316pub enum CronCommands {
317 List,
319 #[command(long_about = "\
321Add a new recurring scheduled task.
322
323Uses standard 5-field cron syntax: 'min hour day month weekday'. \
324Times are evaluated in UTC by default; use --tz with an IANA \
325timezone name to override.
326
327Examples:
328 construct cron add '0 9 * * 1-5' 'Good morning' --tz America/New_York --agent
329 construct cron add '*/30 * * * *' 'Check system health' --agent
330 construct cron add '*/5 * * * *' 'echo ok'")]
331 Add {
332 expression: String,
334 #[arg(long)]
336 tz: Option<String>,
337 #[arg(long)]
339 agent: bool,
340 #[arg(long = "allowed-tool")]
342 allowed_tools: Vec<String>,
343 command: String,
345 },
346 #[command(long_about = "\
348Add a one-shot task that fires at a specific UTC timestamp.
349
350The timestamp must be in RFC 3339 format (e.g. 2025-01-15T14:00:00Z).
351
352Examples:
353 construct cron add-at 2025-01-15T14:00:00Z 'Send reminder'
354 construct cron add-at 2025-12-31T23:59:00Z 'Happy New Year!'")]
355 AddAt {
356 at: String,
358 #[arg(long)]
360 agent: bool,
361 #[arg(long = "allowed-tool")]
363 allowed_tools: Vec<String>,
364 command: String,
366 },
367 #[command(long_about = "\
369Add a task that repeats at a fixed interval.
370
371Interval is specified in milliseconds. For example, 60000 = 1 minute.
372
373Examples:
374 construct cron add-every 60000 'Ping heartbeat' # every minute
375 construct cron add-every 3600000 'Hourly report' # every hour")]
376 AddEvery {
377 every_ms: u64,
379 #[arg(long)]
381 agent: bool,
382 #[arg(long = "allowed-tool")]
384 allowed_tools: Vec<String>,
385 command: String,
387 },
388 #[command(long_about = "\
390Add a one-shot task that fires after a delay from now.
391
392Accepts human-readable durations: s (seconds), m (minutes), \
393h (hours), d (days).
394
395Examples:
396 construct cron once 30m 'Run backup in 30 minutes'
397 construct cron once 2h 'Follow up on deployment'
398 construct cron once 1d 'Daily check'")]
399 Once {
400 delay: String,
402 #[arg(long)]
404 agent: bool,
405 #[arg(long = "allowed-tool")]
407 allowed_tools: Vec<String>,
408 command: String,
410 },
411 Remove {
413 id: String,
415 },
416 #[command(long_about = "\
418Update one or more fields of an existing scheduled task.
419
420Only the fields you specify are changed; others remain unchanged.
421
422Examples:
423 construct cron update <task-id> --expression '0 8 * * *'
424 construct cron update <task-id> --tz Europe/London --name 'Morning check'
425 construct cron update <task-id> --command 'Updated message'")]
426 Update {
427 id: String,
429 #[arg(long)]
431 expression: Option<String>,
432 #[arg(long)]
434 tz: Option<String>,
435 #[arg(long)]
437 command: Option<String>,
438 #[arg(long)]
440 name: Option<String>,
441 #[arg(long = "allowed-tool")]
443 allowed_tools: Vec<String>,
444 },
445 Pause {
447 id: String,
449 },
450 Resume {
452 id: String,
454 },
455}
456
457#[derive(Subcommand, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
459pub enum MemoryCommands {
460 List {
462 #[arg(long)]
464 category: Option<String>,
465 #[arg(long)]
467 session: Option<String>,
468 #[arg(long, default_value = "50")]
470 limit: usize,
471 #[arg(long, default_value = "0")]
473 offset: usize,
474 },
475 Get {
477 key: String,
479 },
480 Stats,
482 Clear {
484 #[arg(long)]
486 key: Option<String>,
487 #[arg(long)]
489 category: Option<String>,
490 #[arg(long)]
492 yes: bool,
493 },
494}
495
496#[derive(Subcommand, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
498pub enum IntegrationCommands {
499 Info {
501 name: String,
503 },
504}
505
506#[derive(Subcommand, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
508pub enum HardwareCommands {
509 #[command(long_about = "\
511Enumerate USB devices and show known boards.
512
513Scans connected USB devices by VID/PID and matches them against \
514known development boards (STM32 Nucleo, Arduino, ESP32).
515
516Examples:
517 construct hardware discover")]
518 Discover,
519 #[command(long_about = "\
521Introspect a device by its serial or device path.
522
523Opens the specified device path and queries for board information, \
524firmware version, and supported capabilities.
525
526Examples:
527 construct hardware introspect /dev/ttyACM0
528 construct hardware introspect COM3")]
529 Introspect {
530 path: String,
532 },
533 #[command(long_about = "\
535Get chip info via USB using probe-rs over ST-Link.
536
537Queries the target MCU directly through the debug probe without \
538requiring any firmware on the target board.
539
540Examples:
541 construct hardware info
542 construct hardware info --chip STM32F401RETx")]
543 Info {
544 #[arg(long, default_value = "STM32F401RETx")]
546 chip: String,
547 },
548}
549
550#[derive(Subcommand, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
552pub enum PeripheralCommands {
553 List,
555 #[command(long_about = "\
557Add a peripheral by board type and transport path.
558
559Registers a hardware board so the agent can use its tools (GPIO, \
560sensors, actuators). Use 'native' as path for local GPIO on \
561single-board computers like Raspberry Pi.
562
563Supported boards: nucleo-f401re, rpi-gpio, esp32, arduino-uno.
564
565Examples:
566 construct peripheral add nucleo-f401re /dev/ttyACM0
567 construct peripheral add rpi-gpio native
568 construct peripheral add esp32 /dev/ttyUSB0")]
569 Add {
570 board: String,
572 path: String,
574 },
575 #[command(long_about = "\
577Flash Construct firmware to an Arduino board.
578
579Generates the .ino sketch, installs arduino-cli if it is not \
580already available, compiles, and uploads the firmware.
581
582Examples:
583 construct peripheral flash
584 construct peripheral flash --port /dev/cu.usbmodem12345
585 construct peripheral flash -p COM3")]
586 Flash {
587 #[arg(short, long)]
589 port: Option<String>,
590 },
591 SetupUnoQ {
593 #[arg(long)]
595 host: Option<String>,
596 },
597 FlashNucleo,
599}
600
601#[derive(Subcommand, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
603pub enum SopCommands {
604 List,
606 Validate {
608 name: Option<String>,
610 },
611 Show {
613 name: String,
615 },
616}